Compare commits

..

6 Commits

Author SHA1 Message Date
Samuel Attard
2da0fe3fa1 ci: reduce checkout and build log verbosity
- Drop -vv from gclient sync in checkout and build-git-cache actions;
  default verbosity is sufficient and the double-verbose output floods
  the step log
- Pass --quiet --batch=false --heartbeat_period=30s to siso for all
  e build invocations so progress is a periodic heartbeat instead of
  per-action output
2026-04-05 11:51:14 -07:00
Charles Kerr
4d8fd31e5f refactor: replace calls to NotifyAccessibilityEventDeprecated() (#50662) 2026-04-05 10:41:57 -05:00
Charles Kerr
96486a4102 refactor: remove raw_ptr<content::StoragePartition> from ServiceWorkerContext and ServiceWorkerKey (#50663)
This removes two `raw_ptr<context::StoragePartition>` instances.

These pointers were used to build a ServiceWorkerMain* lookup key.
The key was built from [version_id, raw_ptr<StoragePartition>].
Unfortunately these keys could be dangling on shutdown.

This PR now uses stable, immutable fields for building the key:
[version_id, BrowserContext::UniqueId(), context::StoragePartitionConfig].
context::StoragePartitionConfig is a unique lookup key for StoragePartition
within a BrowserContext.
2026-04-05 10:41:35 -05:00
Samuel Attard
3f8238b92c fix: defer Wrappable destruction in SecondWeakCallback to a posted task (#50688)
V8's second-pass weak callbacks run inside a
DisallowJavascriptExecutionScope: they may touch the V8 API but must
not invoke JS, directly or indirectly. Several Electron Wrappables
(WebContents in particular) emit JS events from their destructors,
so deleting synchronously inside SecondWeakCallback can crash with
"Invoke in DisallowJavascriptExecutionScope" when GC happens to
collect the JS wrapper during a foreground GC task — typically during
shutdown's uv_run drain after a leaked WebContentsView.

This was previously latent and timing-dependent (electron/electron#47420,
electron/electron#45416, podman-desktop/podman-desktop#12409). The
esbuild migration's keepNames option (which wraps every function/class
with an Object.defineProperty call) shifted heap layout enough to make
the spec/fixtures/crash-cases/webcontentsview-create-leak-exit case
reliably reproduce it on every run, giving a clean signal for the fix.

Both WrappableBase and DeprecatedWrappableBase SecondWeakCallback now
post the deletion via base::SequencedTaskRunner::GetCurrentDefault()
so the destructor (and any Emit it does) runs once V8 has left the GC
scope. Falls back to synchronous deletion if no task runner is
available (early/late process lifetime).

Fixes electron/electron#47420.
2026-04-05 07:38:08 +00:00
Charles Kerr
64c5440eec fix: dangling raw_ptr MicrotasksRunner::isolate_ (#50676) 2026-04-04 23:03:14 -05:00
Samuel Attard
30cf60a935 fix: propagate requesting frame through sync permission checks (#50679)
WebContentsPermissionHelper::CheckPermission was hardcoding
GetPrimaryMainFrame() and deriving the requesting origin from
web_contents_->GetLastCommittedURL(), so the setPermissionCheckHandler
callback always received the top frame's origin and
details.isMainFrame/details.requestingUrl always reflected the main
frame, even when a cross-origin subframe with allow="serial" or
allow="camera; microphone" triggered the check.

Thread the requesting RenderFrameHost through CheckPermission,
CheckSerialAccessPermission, and CheckMediaAccessPermission so the
permission manager receives the real requesting frame. Update the
serial delegate and WebContents::CheckMediaAccessPermission callers to
pass the frame they already have.

Adds a regression test that loads a cross-origin iframe with
allow="camera; microphone", calls enumerateDevices() from within the
iframe, and asserts the permission check handler receives the iframe
origin for requestingOrigin, isMainFrame, and requestingUrl.
2026-04-04 15:59:25 -07:00
59 changed files with 1599 additions and 1343 deletions

View File

@@ -75,9 +75,9 @@ runs:
fi
if [ "${{ inputs.is-release }}" = "true" ]; then
NINJA_SUMMARIZE_BUILD=1 e build --target electron:release_build
NINJA_SUMMARIZE_BUILD=1 e build --target electron:release_build -- --quiet --batch=false --heartbeat_period=30s
else
NINJA_SUMMARIZE_BUILD=1 e build --target electron:testing_build
NINJA_SUMMARIZE_BUILD=1 e build --target electron:testing_build -- --quiet --batch=false --heartbeat_period=30s
fi
cp out/Default/.ninja_log out/electron_ninja_log
node electron/script/check-symlinks.js
@@ -102,10 +102,10 @@ runs:
cd ..
$env:NINJA_SUMMARIZE_BUILD = 1
if ("${{ inputs.is-release }}" -eq "true") {
e build --target electron:release_build
if ("${{ inputs.is-release }}" -eq "true") {
e build --target electron:release_build -- --quiet --batch=false --heartbeat_period=30s
} else {
e build --target electron:testing_build
e build --target electron:testing_build -- --quiet --batch=false --heartbeat_period=30s
}
Copy-Item out\Default\.ninja_log out\electron_ninja_log
node electron\script\check-symlinks.js
@@ -185,7 +185,7 @@ runs:
shell: bash
run: |
cd src
e build --target electron:electron_chromedriver_zip
e build --target electron:electron_chromedriver_zip -- --quiet --batch=false --heartbeat_period=30s
if [ "${{ inputs.is-asan }}" != "true" ]; then
target_os=${{ inputs.target-platform == 'macos' && 'mac' || inputs.target-platform }}
@@ -229,7 +229,7 @@ runs:
run: |
cd src
gn gen out/ffmpeg --args="import(\"//electron/build/args/ffmpeg.gn\") use_remoteexec=true use_siso=true $GN_EXTRA_ARGS"
e build --target electron:electron_ffmpeg_zip -C ../../out/ffmpeg
e build --target electron:electron_ffmpeg_zip -C ../../out/ffmpeg --quiet --batch=false --heartbeat_period=30s
- name: Remove Clang problem matcher
shell: bash
run: echo "::remove-matcher owner=clang::"

View File

@@ -58,7 +58,7 @@ runs:
echo "target_os=['$TARGET_OS']" >> ./.gclient
fi
ELECTRON_USE_THREE_WAY_MERGE_FOR_PATCHES=1 e d gclient sync --with_branch_heads --with_tags --nohooks -vv
ELECTRON_USE_THREE_WAY_MERGE_FOR_PATCHES=1 e d gclient sync --with_branch_heads --with_tags --nohooks
- name: Compress Git Cache Directory
shell: bash
run: |

View File

@@ -109,7 +109,7 @@ runs:
echo "target_os=['$TARGET_OS']" >> ./.gclient
fi
ELECTRON_USE_THREE_WAY_MERGE_FOR_PATCHES=1 e d gclient sync --with_branch_heads --with_tags -vv
ELECTRON_USE_THREE_WAY_MERGE_FOR_PATCHES=1 e d gclient sync --with_branch_heads --with_tags
if [[ "${{ inputs.is-release }}" != "true" ]]; then
# Re-export all the patches to check if there were changes.
python3 src/electron/script/export_all_patches.py src/electron/patches/config.json

View File

@@ -18,12 +18,12 @@ import("//tools/v8_context_snapshot/v8_context_snapshot.gni")
import("//v8/gni/snapshot_toolchain.gni")
import("build/asar.gni")
import("build/electron_paks.gni")
import("build/esbuild/esbuild.gni")
import("build/extract_symbols.gni")
import("build/js2c_toolchain.gni")
import("build/npm.gni")
import("build/templated_file.gni")
import("build/tsc.gni")
import("build/webpack/webpack.gni")
import("buildflags/buildflags.gni")
import("filenames.auto.gni")
import("filenames.gni")
@@ -162,81 +162,75 @@ npm_action("build_electron_definitions") {
outputs = [ "$target_gen_dir/tsc/typings/electron.d.ts" ]
}
typescript_check("electron_lib_typecheck") {
deps = [ ":build_electron_definitions" ]
tsconfig = "//electron/tsconfig.electron.json"
sources = auto_filenames.typecheck_sources
}
esbuild_build("electron_browser_bundle") {
webpack_build("electron_browser_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.browser_bundle_deps
config_file = "//electron/build/esbuild/configs/browser.js"
config_file = "//electron/build/webpack/webpack.config.browser.js"
out_file = "$target_gen_dir/js2c/browser_init.js"
}
esbuild_build("electron_renderer_bundle") {
webpack_build("electron_renderer_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.renderer_bundle_deps
config_file = "//electron/build/esbuild/configs/renderer.js"
config_file = "//electron/build/webpack/webpack.config.renderer.js"
out_file = "$target_gen_dir/js2c/renderer_init.js"
}
esbuild_build("electron_worker_bundle") {
webpack_build("electron_worker_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.worker_bundle_deps
config_file = "//electron/build/esbuild/configs/worker.js"
config_file = "//electron/build/webpack/webpack.config.worker.js"
out_file = "$target_gen_dir/js2c/worker_init.js"
}
esbuild_build("electron_sandboxed_renderer_bundle") {
webpack_build("electron_sandboxed_renderer_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.sandbox_bundle_deps
config_file = "//electron/build/esbuild/configs/sandboxed_renderer.js"
config_file = "//electron/build/webpack/webpack.config.sandboxed_renderer.js"
out_file = "$target_gen_dir/js2c/sandbox_bundle.js"
}
esbuild_build("electron_isolated_renderer_bundle") {
webpack_build("electron_isolated_renderer_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.isolated_bundle_deps
config_file = "//electron/build/esbuild/configs/isolated_renderer.js"
config_file = "//electron/build/webpack/webpack.config.isolated_renderer.js"
out_file = "$target_gen_dir/js2c/isolated_bundle.js"
}
esbuild_build("electron_node_bundle") {
webpack_build("electron_node_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.node_bundle_deps
config_file = "//electron/build/esbuild/configs/node.js"
config_file = "//electron/build/webpack/webpack.config.node.js"
out_file = "$target_gen_dir/js2c/node_init.js"
}
esbuild_build("electron_utility_bundle") {
webpack_build("electron_utility_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.utility_bundle_deps
config_file = "//electron/build/esbuild/configs/utility.js"
config_file = "//electron/build/webpack/webpack.config.utility.js"
out_file = "$target_gen_dir/js2c/utility_init.js"
}
esbuild_build("electron_preload_realm_bundle") {
webpack_build("electron_preload_realm_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.preload_realm_bundle_deps
config_file = "//electron/build/esbuild/configs/preload_realm.js"
config_file = "//electron/build/webpack/webpack.config.preload_realm.js"
out_file = "$target_gen_dir/js2c/preload_realm_bundle.js"
}
@@ -244,7 +238,6 @@ action("electron_js2c") {
deps = [
":electron_browser_bundle",
":electron_isolated_renderer_bundle",
":electron_lib_typecheck",
":electron_node_bundle",
":electron_preload_realm_bundle",
":electron_renderer_bundle",

View File

@@ -1,326 +0,0 @@
#!/usr/bin/env node
// Driver script that replaces webpack for building Electron's internal
// JS bundles. Each bundle is a single esbuild invocation parameterized by
// the per-target configuration files under build/esbuild/configs.
//
// Invoked by the GN `esbuild_build` template via `npm run bundle -- …`.
'use strict';
const esbuild = require('esbuild');
const fs = require('node:fs');
const path = require('node:path');
const electronRoot = path.resolve(__dirname, '../..');
function parseArgs (argv) {
const args = {};
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a.startsWith('--')) {
const key = a.slice(2);
const next = argv[i + 1];
if (next === undefined || next.startsWith('--')) {
args[key] = true;
} else {
args[key] = next;
i++;
}
}
}
return args;
}
// Parse $target_gen_dir/buildflags/buildflags.h (a C++ header containing
// `#define BUILDFLAG_INTERNAL_NAME() (0|1)` lines) into a map of flag name
// to boolean. Used to seed the `define` table so that `BUILDFLAG(NAME)` call
// sites can be statically folded to `true`/`false` at build time.
function parseBuildflags (buildflagsPath) {
const flags = {};
if (!buildflagsPath) return flags;
const source = fs.readFileSync(buildflagsPath, 'utf8');
const re = /#define BUILDFLAG_INTERNAL_(.+?)\(\) \(([01])\)/g;
let match;
while ((match = re.exec(source)) !== null) {
const [, name, value] = match;
flags[name] = value === '1';
}
return flags;
}
// Return the list of esbuild `alias` entries used by every bundle. esbuild's
// alias matches the full module specifier (no `$` suffix trickery like
// webpack), so the bare `electron` alias also matches `electron/main`, etc.,
// because esbuild matches the leftmost segment first.
function buildAliases (electronAPIFile, { aliasTimers }) {
const aliases = {
electron: electronAPIFile,
'electron/main': electronAPIFile,
'electron/renderer': electronAPIFile,
'electron/common': electronAPIFile,
'electron/utility': electronAPIFile
};
// Only browser-platform bundles (sandboxed_renderer, isolated_renderer,
// preload_realm) need the timers shim — Node's `timers` builtin is not
// available there. For node-platform bundles (browser, renderer, worker,
// utility, node) the alias MUST NOT apply: lib/common/init.ts wraps the
// real Node timers and then assigns the wrappers onto globalThis. If
// those bundles saw the shim, the wrappers would recursively call back
// into globalThis.setTimeout and blow the stack.
if (aliasTimers) {
aliases.timers = path.resolve(electronRoot, 'lib', 'common', 'timers-shim.ts');
}
return aliases;
}
// esbuild's `alias` does not support wildcard prefixes like `@electron/internal/*`.
// We instead install a tiny resolve plugin that rewrites any import starting
// with that prefix to an absolute path under `lib/`. The plugin must also
// replicate esbuild's extension/index resolution because returning a path
// from onResolve bypasses the default resolver.
function internalAliasPlugin () {
const candidates = (base) => [
base,
`${base}.ts`,
`${base}.js`,
path.join(base, 'index.ts'),
path.join(base, 'index.js')
];
return {
name: 'electron-internal-alias',
setup (build) {
build.onResolve({ filter: /^@electron\/internal(\/|$)/ }, (args) => {
// Tolerate stray double slashes in import paths (webpack was lenient).
const rel = args.path.replace(/^@electron\/internal\/?/, '').replace(/^\/+/, '');
const base = path.resolve(electronRoot, 'lib', rel);
for (const c of candidates(base)) {
try {
if (fs.statSync(c).isFile()) return { path: c };
} catch { /* keep looking */ }
}
return { errors: [{ text: `Cannot resolve @electron/internal path: ${args.path}` }] };
});
}
};
}
// Rewrites `BUILDFLAG(NAME)` call-sites to `(true)` or `(false)` at load
// time, equivalent to the combination of webpack's DefinePlugin substitution
// (BUILDFLAG -> "" and NAME -> "true"/"false") that the old config used.
// Doing it in a single regex pass keeps the semantics identical and avoids
// fighting with esbuild's AST-level `define` quoting rules.
function buildflagPlugin (buildflags, { allowUnknown = false } = {}) {
return {
name: 'electron-buildflag',
setup (build) {
build.onLoad({ filter: /\.(ts|js)$/ }, async (args) => {
const source = await fs.promises.readFile(args.path, 'utf8');
if (!source.includes('BUILDFLAG(')) {
return { contents: source, loader: args.path.endsWith('.ts') ? 'ts' : 'js' };
}
const rewritten = source.replace(/BUILDFLAG\(([A-Z0-9_]+)\)/g, (_, name) => {
if (!Object.prototype.hasOwnProperty.call(buildflags, name)) {
if (allowUnknown) return '(false)';
throw new Error(`Unknown BUILDFLAG: ${name} (in ${args.path})`);
}
return `(${buildflags[name]})`;
});
return { contents: rewritten, loader: args.path.endsWith('.ts') ? 'ts' : 'js' };
});
}
};
}
// TODO(MarshallOfSound): drop this patch once evanw/esbuild#4441 lands and
// we bump esbuild — that PR adds a `__toCommonJSCached` helper for the
// inline-require path so identity is preserved upstream. Tracked at
// https://github.com/evanw/esbuild/issues/4440.
//
// esbuild's runtime emits `__toCommonJS = (mod) => __copyProps(__defProp({},
// "__esModule", { value: true }), mod)`, which allocates a fresh wrapper
// object every time `require()` resolves to a bundled ESM module. That
// breaks identity expectations our code relies on (e.g. sandboxed preloads
// expecting `require('timers') === require('node:timers')`, and the
// defineProperties getters in lib/common/define-properties.ts expecting
// stable namespaces). A cached WeakMap-backed version of __toCommonJS
// existed in older esbuild releases (see evanw/esbuild#2126) but was
// removed in evanw/esbuild@f4ff26d3 (0.14.27). Substitute a memoized
// variant in post-processing so every call site returns the same wrapper
// for the same underlying namespace, matching webpack's
// `__webpack_require__` cache semantics.
const ESBUILD_TO_COMMONJS_PATTERN =
/var __toCommonJS = \(mod\) => __copyProps\(__defProp\(\{\}, "__esModule", \{ value: true \}\), mod\);/;
const ESBUILD_TO_COMMONJS_REPLACEMENT =
'var __toCommonJS = /* @__PURE__ */ ((cache) => (mod) => {\n' +
' var cached = cache.get(mod);\n' +
' if (cached) return cached;\n' +
' var result = __copyProps(__defProp({}, "__esModule", { value: true }), mod);\n' +
' cache.set(mod, result);\n' +
' return result;\n' +
' })(new WeakMap());';
function patchToCommonJS (source) {
// Once evanw/esbuild#4441 lands, esbuild will emit `__toCommonJSCached`
// for inline require() — when we see that helper in the output, the
// upstream fix is active and this whole patch is a no-op (and should be
// deleted on the next esbuild bump).
if (source.includes('__toCommonJSCached')) {
return source;
}
if (!ESBUILD_TO_COMMONJS_PATTERN.test(source)) {
// Some bundles may not contain any ESM-shaped modules, in which case
// esbuild omits the helper entirely and there is nothing to patch.
if (source.includes('__toCommonJS')) {
throw new Error(
'esbuild bundle contains __toCommonJS but did not match the ' +
'expected pattern; the runtime helper has likely changed upstream. ' +
'Update ESBUILD_TO_COMMONJS_PATTERN in build/esbuild/bundle.js, or ' +
'delete patchToCommonJS entirely if evanw/esbuild#4441 has landed.'
);
}
return source;
}
return source.replace(ESBUILD_TO_COMMONJS_PATTERN, ESBUILD_TO_COMMONJS_REPLACEMENT);
}
// Wrap bundle source text in the same header/footer pairs webpack's
// wrapper-webpack-plugin used. The try/catch wrapper is load-bearing:
// shell/common/node_util.cc's CompileAndCall relies on it to prevent
// exceptions from tearing down bootstrap.
function applyWrappers (source, opts, outputFilename) {
let wrapped = patchToCommonJS(source);
if (opts.wrapInitWithProfilingTimeout) {
const header = 'function ___electron_webpack_init__() {';
const footer = '\n};\nif ((globalThis.process || binding.process).argv.includes("--profile-electron-init")) {\n setTimeout(___electron_webpack_init__, 0);\n} else {\n ___electron_webpack_init__();\n}';
wrapped = header + wrapped + footer;
}
if (opts.wrapInitWithTryCatch) {
const header = 'try {';
const footer = `\n} catch (err) {\n console.error('Electron ${outputFilename} script failed to run');\n console.error(err);\n}`;
wrapped = header + wrapped + footer;
}
return wrapped;
}
async function buildBundle (opts, cliArgs) {
const {
target,
alwaysHasNode,
loadElectronFromAlternateTarget,
wrapInitWithProfilingTimeout,
wrapInitWithTryCatch
} = opts;
const outputFilename = cliArgs['output-filename'] || `${target}.bundle.js`;
const outputPath = cliArgs['output-path'] || path.resolve(electronRoot, 'out');
const mode = cliArgs.mode || 'development';
const minify = mode === 'production';
const printGraph = !!cliArgs['print-graph'];
let entry = path.resolve(electronRoot, 'lib', target, 'init.ts');
if (!fs.existsSync(entry)) {
entry = path.resolve(electronRoot, 'lib', target, 'init.js');
}
const electronAPIFile = path.resolve(
electronRoot,
'lib',
loadElectronFromAlternateTarget || target,
'api',
'exports',
'electron.ts'
);
const buildflags = parseBuildflags(cliArgs.buildflags);
// Shims that stand in for webpack ProvidePlugin. Each target gets the
// minimum set of globals it needs; the capture files mirror the originals
// under lib/common so the behavior (grab globals before user code can
// delete them) is preserved exactly.
const inject = [];
if (opts.targetDeletesNodeGlobals) {
inject.push(path.resolve(__dirname, 'shims', 'node-globals-shim.js'));
}
if (!alwaysHasNode) {
inject.push(path.resolve(__dirname, 'shims', 'browser-globals-shim.js'));
}
inject.push(path.resolve(__dirname, 'shims', 'promise-shim.js'));
const result = await esbuild.build({
entryPoints: [entry],
bundle: true,
format: 'iife',
platform: alwaysHasNode ? 'node' : 'browser',
target: 'es2022',
minify,
// Preserve class/function names in both development and production so
// gin_helper-surfaced constructor names and stack traces stay readable.
// (Under webpack this only mattered when terser ran in is_official_build;
// esbuild applies the same rename pressure in dev too, so keep it on
// unconditionally for consistency.)
keepNames: true,
sourcemap: false,
logLevel: 'warning',
metafile: true,
write: false,
resolveExtensions: ['.ts', '.js'],
alias: buildAliases(electronAPIFile, { aliasTimers: !alwaysHasNode }),
inject,
define: {
__non_webpack_require__: 'require'
},
// Node internal modules we pull through __non_webpack_require__ at runtime.
// These must not be bundled — esbuild should leave the literal require()
// call alone so the outer Node scope resolves them.
external: [
'internal/modules/helpers',
'internal/modules/run_main',
'internal/fs/utils',
'internal/util',
'internal/validators',
'internal/url'
],
plugins: [
internalAliasPlugin(),
buildflagPlugin(buildflags, { allowUnknown: printGraph })
]
});
if (printGraph) {
const inputs = Object.keys(result.metafile.inputs)
.filter((p) => !p.includes('node_modules') && !p.startsWith('..'))
.map((p) => path.relative(electronRoot, path.resolve(electronRoot, p)));
process.stdout.write(JSON.stringify(inputs) + '\n');
return;
}
if (result.outputFiles.length !== 1) {
throw new Error(`Expected exactly one output file, got ${result.outputFiles.length}`);
}
const wrapped = applyWrappers(
result.outputFiles[0].text,
{ wrapInitWithProfilingTimeout, wrapInitWithTryCatch },
outputFilename
);
await fs.promises.mkdir(outputPath, { recursive: true });
await fs.promises.writeFile(path.join(outputPath, outputFilename), wrapped);
}
async function main () {
const cliArgs = parseArgs(process.argv.slice(2));
if (!cliArgs.config) {
console.error('Usage: bundle.js --config <path> [--output-filename X] [--output-path Y] [--mode development|production] [--buildflags path/to/buildflags.h] [--print-graph]');
process.exit(1);
}
const configPath = path.resolve(cliArgs.config);
const opts = require(configPath);
await buildBundle(opts, cliArgs);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -1,4 +0,0 @@
module.exports = {
target: 'browser',
alwaysHasNode: true
};

View File

@@ -1,4 +0,0 @@
module.exports = {
target: 'node',
alwaysHasNode: true
};

View File

@@ -1,4 +0,0 @@
module.exports = {
target: 'utility',
alwaysHasNode: true
};

View File

@@ -1,18 +0,0 @@
// Injected into browser-platform bundles (sandboxed_renderer, isolated_renderer,
// preload_realm) where Node globals are not implicitly available. Supplies
// `Buffer`, `process`, and `global` — replacing webpack's ProvidePlugin
// polyfill injection plus webpack 5's built-in `global -> globalThis` rewrite
// that `target: 'web'` performed automatically.
//
// The `buffer` and `process/browser` imports below intentionally use the
// npm polyfill packages, not Node's built-in `node:buffer` / `node:process`
// modules, because these shims ship into browser-platform bundles that do
// not have Node globals available at runtime.
/* eslint-disable import/order, import/enforce-node-protocol-usage */
import { Buffer as _Buffer } from 'buffer';
import _process from 'process/browser';
const _global = globalThis;
export { _Buffer as Buffer, _process as process, _global as global };

View File

@@ -1,14 +0,0 @@
// Injected into renderer/worker bundles to replace webpack's ProvidePlugin
// that captured `Buffer`, `global`, and `process` before user code could
// delete them from the global scope. The Module.wrapper override in
// lib/renderer/init.ts re-injects these into user preload scripts later.
// Rip globals off of globalThis/self/window so they are captured in this
// module's closure and retained even if the caller later deletes them.
const _global = typeof globalThis !== 'undefined'
? globalThis.global
: (self || window).global;
const _process = _global.process;
const _Buffer = _global.Buffer;
export { _global as global, _process as process, _Buffer as Buffer };

View File

@@ -1,7 +0,0 @@
// Captures the original `Promise` constructor so that userland mutations of
// `global.Promise.resolve` do not affect Electron's internal code. Mirrors
// webpack's ProvidePlugin reference to lib/common/webpack-globals-provider.
const _Promise = globalThis.Promise;
export { _Promise as Promise };

View File

@@ -1,42 +1,5 @@
import("npm.gni")
# Runs `tsgo --noEmit` over a tsconfig via the `tsc-check` npm script (which
# wraps script/typecheck.js) and writes a stamp on success. Use this to gate
# downstream targets on a successful typecheck without emitting JS.
template("typescript_check") {
assert(defined(invoker.tsconfig), "Need tsconfig name to run")
assert(defined(invoker.sources), "Need tsc sources to run")
npm_action(target_name) {
forward_variables_from(invoker,
[
"deps",
"public_deps",
])
script = "tsc-check"
sources = invoker.sources
inputs = [
invoker.tsconfig,
"//electron/tsconfig.json",
"//electron/yarn.lock",
"//electron/script/typecheck.js",
"//electron/typings/internal-ambient.d.ts",
"//electron/typings/internal-electron.d.ts",
]
stamp_file = "$target_gen_dir/$target_name.stamp"
outputs = [ stamp_file ]
args = [
"--tsconfig",
rebase_path(invoker.tsconfig),
"--stamp",
rebase_path(stamp_file),
]
}
}
template("typescript_build") {
assert(defined(invoker.tsconfig), "Need tsconfig name to run")
assert(defined(invoker.sources), "Need tsc sources to run")

View File

@@ -0,0 +1,172 @@
const TerserPlugin = require('terser-webpack-plugin');
const webpack = require('webpack');
const WrapperPlugin = require('wrapper-webpack-plugin');
const fs = require('node:fs');
const path = require('node:path');
const electronRoot = path.resolve(__dirname, '../..');
class AccessDependenciesPlugin {
apply (compiler) {
compiler.hooks.compilation.tap('AccessDependenciesPlugin', compilation => {
compilation.hooks.finishModules.tap('AccessDependenciesPlugin', modules => {
const filePaths = modules.map(m => m.resource).filter(p => p).map(p => path.relative(electronRoot, p));
console.info(JSON.stringify(filePaths));
});
});
}
}
module.exports = ({
alwaysHasNode,
loadElectronFromAlternateTarget,
targetDeletesNodeGlobals,
target,
wrapInitWithProfilingTimeout,
wrapInitWithTryCatch
}) => {
let entry = path.resolve(electronRoot, 'lib', target, 'init.ts');
if (!fs.existsSync(entry)) {
entry = path.resolve(electronRoot, 'lib', target, 'init.js');
}
const electronAPIFile = path.resolve(electronRoot, 'lib', loadElectronFromAlternateTarget || target, 'api', 'exports', 'electron.ts');
return (env = {}, argv = {}) => {
const onlyPrintingGraph = !!env.PRINT_WEBPACK_GRAPH;
const outputFilename = argv['output-filename'] || `${target}.bundle.js`;
const defines = {
BUILDFLAG: onlyPrintingGraph ? '(a => a)' : ''
};
if (env.buildflags) {
const flagFile = fs.readFileSync(env.buildflags, 'utf8');
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;
defines[flagName] = JSON.stringify(Boolean(parseInt(flagValue, 10)));
}
}
}
const ignoredModules = [];
const plugins = [];
if (onlyPrintingGraph) {
plugins.push(new AccessDependenciesPlugin());
}
if (targetDeletesNodeGlobals) {
plugins.push(new webpack.ProvidePlugin({
Buffer: ['@electron/internal/common/webpack-provider', 'Buffer'],
global: ['@electron/internal/common/webpack-provider', '_global'],
process: ['@electron/internal/common/webpack-provider', 'process']
}));
}
// Webpack 5 no longer polyfills process or Buffer.
if (!alwaysHasNode) {
plugins.push(new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
process: 'process/browser'
}));
}
plugins.push(new webpack.ProvidePlugin({
Promise: ['@electron/internal/common/webpack-globals-provider', 'Promise']
}));
plugins.push(new webpack.DefinePlugin(defines));
if (wrapInitWithProfilingTimeout) {
plugins.push(new WrapperPlugin({
header: 'function ___electron_webpack_init__() {',
footer: `
};
if ((globalThis.process || binding.process).argv.includes("--profile-electron-init")) {
setTimeout(___electron_webpack_init__, 0);
} else {
___electron_webpack_init__();
}`
}));
}
if (wrapInitWithTryCatch) {
plugins.push(new WrapperPlugin({
header: 'try {',
footer: `
} catch (err) {
console.error('Electron ${outputFilename} script failed to run');
console.error(err);
}`
}));
}
return {
mode: 'development',
devtool: false,
entry,
target: alwaysHasNode ? 'node' : 'web',
output: {
filename: outputFilename
},
resolve: {
alias: {
'@electron/internal': path.resolve(electronRoot, 'lib'),
electron$: electronAPIFile,
'electron/main$': electronAPIFile,
'electron/renderer$': electronAPIFile,
'electron/common$': electronAPIFile,
'electron/utility$': electronAPIFile,
// Force timers to resolve to our own shim that doesn't use window.postMessage
timers: path.resolve(electronRoot, 'lib', 'common', 'timers-shim.ts')
},
extensions: ['.ts', '.js'],
fallback: {
// We provide our own "timers" import above, any usage of setImmediate inside
// one of our renderer bundles should import it from the 'timers' package
setImmediate: false
}
},
module: {
rules: [{
test: (moduleName) => !onlyPrintingGraph && ignoredModules.includes(moduleName),
loader: 'null-loader'
}, {
test: /\.ts$/,
loader: 'ts-loader',
options: {
configFile: path.resolve(electronRoot, 'tsconfig.electron.json'),
transpileOnly: onlyPrintingGraph,
ignoreDiagnostics: [
// File '{0}' is not under 'rootDir' '{1}'.
6059,
// Private field '{0}' must be declared in an enclosing class.
1111
]
}
}]
},
node: {
__dirname: false,
__filename: false
},
optimization: {
minimize: env.mode === 'production',
minimizer: [
new TerserPlugin({
terserOptions: {
keep_classnames: true,
keep_fnames: true
}
})
]
},
plugins
};
};
};

View File

@@ -0,0 +1,4 @@
module.exports = require('./webpack.config.base')({
target: 'browser',
alwaysHasNode: true
});

View File

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

View File

@@ -0,0 +1,4 @@
module.exports = require('./webpack.config.base')({
target: 'node',
alwaysHasNode: true
});

View File

@@ -1,6 +1,6 @@
module.exports = {
module.exports = require('./webpack.config.base')({
target: 'preload_realm',
alwaysHasNode: false,
wrapInitWithProfilingTimeout: true,
wrapInitWithTryCatch: true
};
});

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
module.exports = require('./webpack.config.base')({
target: 'utility',
alwaysHasNode: true
});

View File

@@ -1,7 +1,7 @@
module.exports = {
module.exports = require('./webpack.config.base')({
target: 'worker',
loadElectronFromAlternateTarget: 'renderer',
alwaysHasNode: true,
targetDeletesNodeGlobals: true,
wrapInitWithTryCatch: true
};
});

View File

@@ -1,9 +1,9 @@
import("../npm.gni")
template("esbuild_build") {
assert(defined(invoker.config_file), "Need esbuild config file to run")
template("webpack_build") {
assert(defined(invoker.config_file), "Need webpack config file to run")
assert(defined(invoker.out_file), "Need output file to run")
assert(defined(invoker.inputs), "Need esbuild inputs to run")
assert(defined(invoker.inputs), "Need webpack inputs to run")
npm_action(target_name) {
forward_variables_from(invoker,
@@ -11,14 +11,11 @@ template("esbuild_build") {
"deps",
"public_deps",
])
script = "bundle"
script = "webpack"
inputs = [
invoker.config_file,
"//electron/build/esbuild/bundle.js",
"//electron/build/esbuild/shims/node-globals-shim.js",
"//electron/build/esbuild/shims/browser-globals-shim.js",
"//electron/build/esbuild/shims/promise-shim.js",
"//electron/build/webpack/webpack.config.base.js",
"//electron/tsconfig.json",
"//electron/yarn.lock",
"//electron/typings/internal-ambient.d.ts",
@@ -37,10 +34,10 @@ template("esbuild_build") {
get_path_info(invoker.out_file, "file"),
"--output-path",
rebase_path(get_path_info(invoker.out_file, "dir")),
"--buildflags",
rebase_path("$target_gen_dir/buildflags/buildflags.h"),
"--mode",
mode,
"--env",
"buildflags=" + rebase_path("$target_gen_dir/buildflags/buildflags.h"),
"--env",
"mode=" + mode,
]
deps += [ "//electron/buildflags" ]

View File

@@ -174,140 +174,14 @@ auto_filenames = {
"docs/api/structures/window-session-end-event.md",
]
typecheck_sources = [
"build/esbuild/shims/browser-globals-shim.js",
"build/esbuild/shims/node-globals-shim.js",
"build/esbuild/shims/promise-shim.js",
"lib/browser/api/app.ts",
"lib/browser/api/auto-updater.ts",
"lib/browser/api/auto-updater/auto-updater-msix.ts",
"lib/browser/api/auto-updater/auto-updater-native.ts",
"lib/browser/api/auto-updater/auto-updater-win.ts",
"lib/browser/api/auto-updater/msix-update-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/clipboard.ts",
"lib/browser/api/content-tracing.ts",
"lib/browser/api/crash-reporter.ts",
"lib/browser/api/desktop-capturer.ts",
"lib/browser/api/dialog.ts",
"lib/browser/api/exports/electron.ts",
"lib/browser/api/global-shortcut.ts",
"lib/browser/api/in-app-purchase.ts",
"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/message-channel.ts",
"lib/browser/api/module-list.ts",
"lib/browser/api/native-theme.ts",
"lib/browser/api/net-fetch.ts",
"lib/browser/api/net-log.ts",
"lib/browser/api/net.ts",
"lib/browser/api/notification.ts",
"lib/browser/api/power-monitor.ts",
"lib/browser/api/power-save-blocker.ts",
"lib/browser/api/protocol.ts",
"lib/browser/api/push-notifications.ts",
"lib/browser/api/safe-storage.ts",
"lib/browser/api/screen.ts",
"lib/browser/api/service-worker-main.ts",
"lib/browser/api/session.ts",
"lib/browser/api/share-menu.ts",
"lib/browser/api/shared-texture.ts",
"lib/browser/api/system-preferences.ts",
"lib/browser/api/touch-bar.ts",
"lib/browser/api/tray.ts",
"lib/browser/api/utility-process.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/api/web-frame-main.ts",
"lib/browser/default-menu.ts",
"lib/browser/devtools.ts",
"lib/browser/guest-view-manager.ts",
"lib/browser/guest-window-manager.ts",
"lib/browser/init.ts",
"lib/browser/ipc-dispatch.ts",
"lib/browser/ipc-main-impl.ts",
"lib/browser/ipc-main-internal-utils.ts",
"lib/browser/ipc-main-internal.ts",
"lib/browser/message-port-main.ts",
"lib/browser/parse-features-string.ts",
"lib/browser/rpc-server.ts",
"lib/browser/web-view-events.ts",
"lib/common/api/module-list.ts",
"lib/common/api/native-image.ts",
"lib/common/api/net-client-request.ts",
"lib/common/api/shell.ts",
"lib/common/define-properties.ts",
"lib/common/deprecate.ts",
"lib/common/init.ts",
"lib/common/ipc-messages.ts",
"lib/common/timers-shim.ts",
"lib/common/web-view-methods.ts",
"lib/isolated_renderer/init.ts",
"lib/node/asar-fs-wrapper.ts",
"lib/node/init.ts",
"lib/preload_realm/api/exports/electron.ts",
"lib/preload_realm/api/module-list.ts",
"lib/preload_realm/init.ts",
"lib/renderer/api/clipboard.ts",
"lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/exports/electron.ts",
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/module-list.ts",
"lib/renderer/api/shared-texture.ts",
"lib/renderer/api/web-frame.ts",
"lib/renderer/api/web-utils.ts",
"lib/renderer/common-init.ts",
"lib/renderer/init.ts",
"lib/renderer/inspector.ts",
"lib/renderer/ipc-native-setup.ts",
"lib/renderer/ipc-renderer-bindings.ts",
"lib/renderer/ipc-renderer-internal-utils.ts",
"lib/renderer/ipc-renderer-internal.ts",
"lib/renderer/security-warnings.ts",
"lib/renderer/web-frame-init.ts",
"lib/renderer/web-view/guest-view-internal.ts",
"lib/renderer/web-view/web-view-attributes.ts",
"lib/renderer/web-view/web-view-constants.ts",
"lib/renderer/web-view/web-view-element.ts",
"lib/renderer/web-view/web-view-impl.ts",
"lib/renderer/web-view/web-view-init.ts",
"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/pre-init.ts",
"lib/sandboxed_renderer/preload.ts",
"lib/utility/api/exports/electron.ts",
"lib/utility/api/module-list.ts",
"lib/utility/api/net.ts",
"lib/utility/init.ts",
"lib/utility/parent-port.ts",
"lib/worker/init.ts",
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
"typings/internal-ambient.d.ts",
"typings/internal-electron.d.ts",
]
sandbox_bundle_deps = [
"build/esbuild/shims/browser-globals-shim.js",
"build/esbuild/shims/promise-shim.js",
"lib/common/api/native-image.ts",
"lib/common/define-properties.ts",
"lib/common/deprecate.ts",
"lib/common/ipc-messages.ts",
"lib/common/timers-shim.ts",
"lib/common/web-view-methods.ts",
"lib/common/webpack-globals-provider.ts",
"lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/ipc-renderer.ts",
@@ -342,8 +216,6 @@ auto_filenames = {
]
isolated_bundle_deps = [
"build/esbuild/shims/browser-globals-shim.js",
"build/esbuild/shims/promise-shim.js",
"lib/common/web-view-methods.ts",
"lib/isolated_renderer/init.ts",
"lib/renderer/web-view/web-view-attributes.ts",
@@ -358,7 +230,6 @@ auto_filenames = {
]
browser_bundle_deps = [
"build/esbuild/shims/promise-shim.js",
"lib/browser/api/app.ts",
"lib/browser/api/auto-updater.ts",
"lib/browser/api/auto-updater/auto-updater-msix.ts",
@@ -429,8 +300,8 @@ auto_filenames = {
"lib/common/deprecate.ts",
"lib/common/init.ts",
"lib/common/ipc-messages.ts",
"lib/common/timers-shim.ts",
"lib/common/web-view-methods.ts",
"lib/common/webpack-globals-provider.ts",
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
@@ -439,8 +310,6 @@ auto_filenames = {
]
renderer_bundle_deps = [
"build/esbuild/shims/node-globals-shim.js",
"build/esbuild/shims/promise-shim.js",
"lib/common/api/module-list.ts",
"lib/common/api/native-image.ts",
"lib/common/api/shell.ts",
@@ -448,8 +317,8 @@ auto_filenames = {
"lib/common/deprecate.ts",
"lib/common/init.ts",
"lib/common/ipc-messages.ts",
"lib/common/timers-shim.ts",
"lib/common/web-view-methods.ts",
"lib/common/webpack-provider.ts",
"lib/renderer/api/clipboard.ts",
"lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts",
@@ -483,8 +352,6 @@ auto_filenames = {
]
worker_bundle_deps = [
"build/esbuild/shims/node-globals-shim.js",
"build/esbuild/shims/promise-shim.js",
"lib/common/api/module-list.ts",
"lib/common/api/native-image.ts",
"lib/common/api/shell.ts",
@@ -492,7 +359,7 @@ auto_filenames = {
"lib/common/deprecate.ts",
"lib/common/init.ts",
"lib/common/ipc-messages.ts",
"lib/common/timers-shim.ts",
"lib/common/webpack-provider.ts",
"lib/renderer/api/clipboard.ts",
"lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts",
@@ -514,7 +381,6 @@ auto_filenames = {
]
node_bundle_deps = [
"build/esbuild/shims/promise-shim.js",
"lib/node/asar-fs-wrapper.ts",
"lib/node/init.ts",
"package.json",
@@ -525,7 +391,6 @@ auto_filenames = {
]
utility_bundle_deps = [
"build/esbuild/shims/promise-shim.js",
"lib/browser/api/net-fetch.ts",
"lib/browser/api/system-preferences.ts",
"lib/browser/message-port-main.ts",
@@ -533,7 +398,7 @@ auto_filenames = {
"lib/common/define-properties.ts",
"lib/common/deprecate.ts",
"lib/common/init.ts",
"lib/common/timers-shim.ts",
"lib/common/webpack-globals-provider.ts",
"lib/utility/api/exports/electron.ts",
"lib/utility/api/module-list.ts",
"lib/utility/api/net.ts",
@@ -547,11 +412,10 @@ auto_filenames = {
]
preload_realm_bundle_deps = [
"build/esbuild/shims/browser-globals-shim.js",
"build/esbuild/shims/promise-shim.js",
"lib/common/api/native-image.ts",
"lib/common/define-properties.ts",
"lib/common/ipc-messages.ts",
"lib/common/webpack-globals-provider.ts",
"lib/preload_realm/api/exports/electron.ts",
"lib/preload_realm/api/module-list.ts",
"lib/preload_realm/init.ts",

View File

@@ -5,27 +5,23 @@ import type { ClientRequestConstructorOptions } from 'electron/main';
const { isOnline } = process._linkedBinding('electron_common_net');
function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
export function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
if (!app.isReady()) {
throw new Error('net module can only be used after app is ready');
}
return new ClientRequest(options, callback);
}
function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
export function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
return session.defaultSession.fetch(input, init);
}
function resolveHost (host: string, options?: Electron.ResolveHostOptions): Promise<Electron.ResolvedHost> {
export function resolveHost (host: string, options?: Electron.ResolveHostOptions): Promise<Electron.ResolvedHost> {
return session.defaultSession.resolveHost(host, options);
}
module.exports = {
request,
fetch,
resolveHost,
isOnline,
get online () {
return isOnline();
}
};
exports.isOnline = isOnline;
Object.defineProperty(exports, 'online', {
get: () => isOnline()
});

View File

@@ -181,7 +181,7 @@ delete process.appCodeLoaded;
if (packagePath) {
// Finally load app's main.js and transfer control to C++.
if ((packageJson.type === 'module' && !mainStartupScript.endsWith('.cjs')) || mainStartupScript.endsWith('.mjs')) {
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main');
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main') as typeof import('@node/lib/internal/modules/run_main');
const main = (require('url') as typeof url).pathToFileURL(path.join(packagePath, mainStartupScript));
runEntryPointWithESMLoader(async (cascadedLoader: any) => {
try {

View File

@@ -1,4 +1,4 @@
import * as timers from 'timers';
import timers = require('timers');
import * as util from 'util';
import type * as stream from 'stream';
@@ -41,15 +41,15 @@ function wrap <T extends AnyFn> (func: T, wrapper: (fn: AnyFn) => T) {
// initiatively activate the uv loop once process.nextTick and setImmediate is
// called.
process.nextTick = wrapWithActivateUvLoop(process.nextTick);
global.setImmediate = wrapWithActivateUvLoop(timers.setImmediate);
global.setImmediate = timers.setImmediate = wrapWithActivateUvLoop(timers.setImmediate);
global.clearImmediate = timers.clearImmediate;
// setTimeout needs to update the polling timeout of the event loop, when
// called under Chromium's event loop the node's event loop won't get a chance
// to update the timeout, so we have to force the node's event loop to
// recalculate the timeout in the process.
const wrappedSetTimeout = wrapWithActivateUvLoop(timers.setTimeout);
const wrappedSetInterval = wrapWithActivateUvLoop(timers.setInterval);
timers.setTimeout = wrapWithActivateUvLoop(timers.setTimeout);
timers.setInterval = wrapWithActivateUvLoop(timers.setInterval);
// Update the global version of the timer apis to use the above wrapper
// only in the process that runs node event loop alongside chromium
@@ -57,8 +57,8 @@ const wrappedSetInterval = wrapWithActivateUvLoop(timers.setInterval);
// are deleted in these processes, see renderer/init.js for reference.
if (process.type === 'browser' ||
process.type === 'utility') {
global.setTimeout = wrappedSetTimeout;
global.setInterval = wrappedSetInterval;
global.setTimeout = timers.setTimeout;
global.setInterval = timers.setInterval;
}
if (process.platform === 'win32') {

View File

@@ -1,5 +1,5 @@
// Drop-in replacement for timers-browserify@1.4.2.
// Provides the Node.js 'timers' API surface for renderer/web bundles
// Provides the Node.js 'timers' API surface for renderer/web webpack bundles
// without relying on window.postMessage (which the newer timers-browserify 2.x
// polyfill uses and can interfere with Electron IPC).

View File

@@ -0,0 +1,8 @@
// Captures original globals into a scope to ensure that userland modifications do
// not impact Electron. Note that users doing:
//
// global.Promise.resolve = myFn
//
// Will mutate this captured one as well and that is OK.
export const Promise = global.Promise;

View File

@@ -0,0 +1,18 @@
// This file provides the global, process and Buffer variables to internal
// Electron code once they have been deleted from the global scope.
//
// It does this through the ProvidePlugin in the webpack.config.base.js file
// Check out the Module.wrapper override in renderer/init.ts for more
// information on how this works and why we need it
// Rip global off of window (which is also global) so that webpack doesn't
// auto replace it with a looped reference to this file
const _global = typeof globalThis !== 'undefined' ? globalThis.global : (self || window).global;
const process = _global.process;
const Buffer = _global.Buffer;
export {
_global,
process,
Buffer
};

View File

@@ -52,20 +52,20 @@ const {
getValidatedPath,
getOptions,
getDirent
} = __non_webpack_require__('internal/fs/utils');
} = __non_webpack_require__('internal/fs/utils') as typeof import('@node/lib/internal/fs/utils');
const {
assignFunctionName
} = __non_webpack_require__('internal/util');
} = __non_webpack_require__('internal/util') as typeof import('@node/lib/internal/util');
const {
validateBoolean,
validateFunction
} = __non_webpack_require__('internal/validators');
} = __non_webpack_require__('internal/validators') as typeof import('@node/lib/internal/validators');
// In the renderer node internals use the node global URL but we do not set that to be
// the global URL instance. We need to do instanceof checks against the internal URL impl.
const { URL: NodeURL } = __non_webpack_require__('internal/url');
// the global URL instance. We need to do instanceof checks against the internal URL impl
const { URL: NodeURL } = __non_webpack_require__('internal/url') as typeof import('@node/lib/internal/url');
// Separate asar package's path from full path.
const splitPath = (archivePathOrBuffer: string | Buffer | URL) => {

View File

@@ -29,9 +29,8 @@ Module._load = function (request: string) {
// code with JavaScript.
//
// Note 3: We provide the equivalent extra variables internally through the
// esbuild inject shim in build/esbuild/shims/node-globals-shim.js. If you
// add any extra variables to this wrapper please ensure to update that shim
// as well.
// webpack ProvidePlugin in webpack.config.base.js. If you add any extra
// variables to this wrapper please ensure to update that plugin as well.
Module.wrapper = [
'(function (exports, require, module, __filename, __dirname, process, global, Buffer) { ' +
// By running the code in a new closure, it would be possible for the module
@@ -66,9 +65,9 @@ require('@electron/internal/renderer/common-init');
if (nodeIntegration) {
// Export node bindings to global.
const { makeRequireFunction } = __non_webpack_require__('internal/modules/helpers');
const { makeRequireFunction } = __non_webpack_require__('internal/modules/helpers') as typeof import('@node/lib/internal/modules/helpers');
global.module = new Module('electron/js2c/renderer_init');
global.require = makeRequireFunction(global.module);
global.require = makeRequireFunction(global.module) as NodeRequire;
// Set the __filename to the path of html file if it is file: protocol.
if (window.location.protocol === 'file:') {
@@ -153,7 +152,7 @@ if (cjsPreloads.length) {
}
}
if (esmPreloads.length) {
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main');
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main') as typeof import('@node/lib/internal/modules/run_main');
runEntryPointWithESMLoader(async (cascadedLoader: any) => {
// Load the preload scripts.

View File

@@ -5,20 +5,18 @@ import type { ClientRequestConstructorOptions, IncomingMessage } from 'electron/
const { isOnline, resolveHost } = process._linkedBinding('electron_common_net');
function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
export function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
return new ClientRequest(options, callback);
}
function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
export function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
return fetchWithSession(input, init, undefined, request);
}
module.exports = {
request,
fetch,
resolveHost,
isOnline,
get online () {
return isOnline();
}
};
exports.resolveHost = resolveHost;
exports.isOnline = isOnline;
Object.defineProperty(exports, 'online', {
get: () => isOnline()
});

View File

@@ -36,7 +36,7 @@ parentPort.on('removeListener', (name: string) => {
});
// Finally load entry script.
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main');
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main') as typeof import('@node/lib/internal/modules/run_main');
const mainEntry = pathToFileURL(entryScript);
runEntryPointWithESMLoader(async (cascadedLoader: any) => {

View File

@@ -13,9 +13,9 @@ require('@electron/internal/common/init');
const { hasSwitch, getSwitchValue } = process._linkedBinding('electron_common_command_line');
// Export node bindings to global.
const { makeRequireFunction } = __non_webpack_require__('internal/modules/helpers');
const { makeRequireFunction } = __non_webpack_require__('internal/modules/helpers') as typeof import('@node/lib/internal/modules/helpers');
global.module = new Module('electron/js2c/worker_init');
global.require = makeRequireFunction(global.module);
global.require = makeRequireFunction(global.module) as NodeRequire;
// See WebWorkerObserver::WorkerScriptReadyForEvaluation.
if ((globalThis as any).blinkfetch) {

View File

@@ -23,12 +23,10 @@
"@types/temp": "^0.9.4",
"@typescript-eslint/eslint-plugin": "^8.32.1",
"@typescript-eslint/parser": "^8.7.0",
"@typescript/native-preview": "^7.0.0-dev.20260324.1",
"@xmldom/xmldom": "^0.8.11",
"buffer": "^6.0.3",
"chalk": "^4.1.0",
"check-for-leaks": "^1.2.1",
"esbuild": "^0.25.0",
"eslint": "^8.57.1",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.32.0",
@@ -45,15 +43,20 @@
"markdownlint-cli2": "^0.18.0",
"minimist": "^1.2.8",
"node-gyp": "^11.4.2",
"null-loader": "^4.0.1",
"pre-flight": "^2.0.0",
"process": "^0.11.10",
"semver": "^7.6.3",
"stream-json": "^1.9.1",
"tap-xunit": "^2.4.1",
"temp": "^0.9.4",
"ts-loader": "^8.0.2",
"ts-node": "6.2.0",
"typescript": "^5.8.3",
"url": "^0.11.4",
"webpack": "^5.104.1",
"webpack-cli": "^6.0.1",
"wrapper-webpack-plugin": "^2.2.0",
"yaml": "^2.8.1"
},
"private": true,
@@ -90,9 +93,8 @@
"repl": "node ./script/start.js --interactive",
"start": "node ./script/start.js",
"test": "node ./script/spec-runner.js",
"tsc": "tsgo",
"tsc-check": "node script/typecheck.js",
"bundle": "node build/esbuild/bundle.js"
"tsc": "tsc",
"webpack": "webpack"
},
"license": "MIT",
"author": "Electron Community",

View File

@@ -1,5 +1,6 @@
import * as cp from 'node:child_process';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
const rootPath = path.resolve(__dirname, '..');
@@ -15,24 +16,52 @@ const allDocs = fs.readdirSync(path.resolve(__dirname, '../docs/api'))
const typingFiles = fs.readdirSync(path.resolve(__dirname, '../typings')).map(child => `typings/${child}`);
const main = async () => {
const bundleTargets = [
{ name: 'sandbox_bundle_deps', config: 'sandboxed_renderer.js' },
{ name: 'isolated_bundle_deps', config: 'isolated_renderer.js' },
{ name: 'browser_bundle_deps', config: 'browser.js' },
{ name: 'renderer_bundle_deps', config: 'renderer.js' },
{ name: 'worker_bundle_deps', config: 'worker.js' },
{ name: 'node_bundle_deps', config: 'node.js' },
{ name: 'utility_bundle_deps', config: 'utility.js' },
{ name: 'preload_realm_bundle_deps', config: 'preload_realm.js' }
const webpackTargets = [
{
name: 'sandbox_bundle_deps',
config: 'webpack.config.sandboxed_renderer.js'
},
{
name: 'isolated_bundle_deps',
config: 'webpack.config.isolated_renderer.js'
},
{
name: 'browser_bundle_deps',
config: 'webpack.config.browser.js'
},
{
name: 'renderer_bundle_deps',
config: 'webpack.config.renderer.js'
},
{
name: 'worker_bundle_deps',
config: 'webpack.config.worker.js'
},
{
name: 'node_bundle_deps',
config: 'webpack.config.node.js'
},
{
name: 'utility_bundle_deps',
config: 'webpack.config.utility.js'
},
{
name: 'preload_realm_bundle_deps',
config: 'webpack.config.preload_realm.js'
}
];
const targetsWithDeps = await Promise.all(bundleTargets.map(async bundleTarget => {
const webpackTargetsWithDeps = await Promise.all(webpackTargets.map(async webpackTarget => {
const tmpDir = await fs.promises.mkdtemp(path.resolve(os.tmpdir(), 'electron-filenames-'));
const child = cp.spawn('node', [
'./build/esbuild/bundle.js',
'--config', `./build/esbuild/configs/${bundleTarget.config}`,
'--print-graph'
'./node_modules/webpack-cli/bin/cli.js',
'--config', `./build/webpack/${webpackTarget.config}`,
'--stats', 'errors-only',
'--output-path', tmpDir,
'--output-filename', `${webpackTarget.name}.measure.js`,
'--env', 'PRINT_WEBPACK_GRAPH'
], {
cwd: rootPath
cwd: path.resolve(__dirname, '..')
});
let output = '';
child.stdout.on('data', chunk => {
@@ -42,33 +71,32 @@ const main = async () => {
await new Promise<void>((resolve, reject) => child.on('exit', (code) => {
if (code !== 0) {
console.error(output);
return reject(new Error(`Failed to list bundle dependencies for entry: ${bundleTarget.name}`));
return reject(new Error(`Failed to list webpack dependencies for entry: ${webpackTarget.name}`));
}
resolve();
}));
return {
...bundleTarget,
const webpackTargetWithDeps = {
...webpackTarget,
dependencies: (JSON.parse(output) as string[])
// Remove whitespace
.map(line => line.trim())
.map(line => path.relative(rootPath, path.resolve(rootPath, line)).replace(/\\/g, '/'))
// Get the relative path
.map(line => path.relative(rootPath, line).replace(/\\/g, '/'))
// Only care about files in //electron
.filter(line => !line.startsWith('..'))
// Only care about our own files
.filter(line => !line.startsWith('node_modules'))
// All webpack builds depend on the tsconfig and package json files
.concat(['tsconfig.json', 'tsconfig.electron.json', 'package.json', ...typingFiles])
// Make the generated list easier to read
.sort()
};
await fs.promises.rm(tmpDir, { force: true, recursive: true });
return webpackTargetWithDeps;
}));
// The typecheck step runs tsgo over tsconfig.electron.json which includes
// the whole lib/ + typings/ trees. For GN dependency tracking, list the
// union of every bundle's dependency set (lib files) plus typings, and
// dedupe.
const typecheckSources = Array.from(new Set([
...targetsWithDeps.flatMap(t => t.dependencies),
...typingFiles
])).sort();
fs.writeFileSync(
gniPath,
`# THIS FILE IS AUTO-GENERATED, PLEASE DO NOT EDIT BY HAND
@@ -77,11 +105,7 @@ auto_filenames = {
${allDocs.map(doc => ` "${doc}",`).join('\n')}
]
typecheck_sources = [
${typecheckSources.map(src => ` "${src}",`).join('\n')}
]
${targetsWithDeps.map(target => ` ${target.name} = [
${webpackTargetsWithDeps.map(target => ` ${target.name} = [
${target.dependencies.map(dep => ` "${dep}",`).join('\n')}
]`).join('\n\n')}
}

View File

@@ -1,6 +1,6 @@
import { Octokit } from '@octokit/rest';
import { strict as assert } from 'node:assert';
import * as assert from 'node:assert';
import { createGitHubTokenStrategy } from './github-token';
import { ELECTRON_ORG, ELECTRON_REPO } from './types';

View File

@@ -1,6 +1,6 @@
import minimist = require('minimist');
import streamChain = require('stream-chain');
import streamJson = require('stream-json');
import * as minimist from 'minimist';
import * as streamChain from 'stream-chain';
import * as streamJson from 'stream-json';
import { ignore as streamJsonIgnore } from 'stream-json/filters/Ignore';
import { streamArray as streamJsonStreamArray } from 'stream-json/streamers/StreamArray';

View File

@@ -1,58 +0,0 @@
#!/usr/bin/env node
// Runs `tsgo --noEmit -p <tsconfig>` and writes a stamp file on success,
// so GN can track typecheck results as a build output.
//
// Usage: node script/typecheck.js --tsconfig <path> --stamp <path>
'use strict';
const cp = require('node:child_process');
const fs = require('node:fs');
const path = require('node:path');
function parseArgs (argv) {
const out = {};
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a.startsWith('--')) {
const next = argv[i + 1];
if (next === undefined || next.startsWith('--')) {
out[a.slice(2)] = true;
} else {
out[a.slice(2)] = next;
i++;
}
}
}
return out;
}
const args = parseArgs(process.argv.slice(2));
if (!args.tsconfig || !args.stamp) {
console.error('Usage: typecheck.js --tsconfig <path> --stamp <path>');
process.exit(1);
}
const electronRoot = path.resolve(__dirname, '..');
// Resolve tsgo's bin entry directly from the package's `bin` map and run it
// via the current Node executable. We can't `require.resolve` the bin path
// (the package's `exports` field doesn't expose it) and we can't spawn
// `node_modules/.bin/tsgo` directly on Windows (it's a `.cmd` shim).
const tsgoPkgPath = require.resolve('@typescript/native-preview/package.json', {
paths: [electronRoot]
});
const tsgoPkg = JSON.parse(fs.readFileSync(tsgoPkgPath, 'utf8'));
const tsgoEntry = path.resolve(path.dirname(tsgoPkgPath), tsgoPkg.bin.tsgo);
const child = cp.spawnSync(
process.execPath,
[tsgoEntry, '--noEmit', '-p', path.resolve(args.tsconfig)],
{ cwd: electronRoot, stdio: 'inherit' }
);
if (child.status !== 0) {
process.exit(child.status || 1);
}
fs.mkdirSync(path.dirname(args.stamp), { recursive: true });
fs.writeFileSync(args.stamp, '');

View File

@@ -80,9 +80,12 @@ gin::DeprecatedWrapperInfo ServiceWorkerContext::kWrapperInfo = {
ServiceWorkerContext::ServiceWorkerContext(
v8::Isolate* isolate,
ElectronBrowserContext* browser_context) {
storage_partition_ = browser_context->GetDefaultStoragePartition();
service_worker_context_ = storage_partition_->GetServiceWorkerContext();
ElectronBrowserContext* browser_context)
: service_worker_context_{browser_context->GetDefaultStoragePartition()
->GetServiceWorkerContext()},
browser_context_id_{browser_context->UniqueId()},
storage_partition_config_{
browser_context->GetDefaultStoragePartition()->GetConfig()} {
service_worker_context_->AddObserver(this);
}
@@ -93,9 +96,8 @@ ServiceWorkerContext::~ServiceWorkerContext() {
void ServiceWorkerContext::OnRunningStatusChanged(
int64_t version_id,
blink::EmbeddedWorkerStatus running_status) {
ServiceWorkerMain* worker =
ServiceWorkerMain::FromVersionID(version_id, storage_partition_);
if (worker)
if (auto* worker = ServiceWorkerMain::FromVersionID(
browser_context_id_, storage_partition_config_, version_id))
worker->OnRunningStatusChanged(running_status);
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
@@ -133,9 +135,8 @@ void ServiceWorkerContext::OnRegistrationCompleted(const GURL& scope) {
void ServiceWorkerContext::OnVersionRedundant(int64_t version_id,
const GURL& scope) {
ServiceWorkerMain* worker =
ServiceWorkerMain::FromVersionID(version_id, storage_partition_);
if (worker)
if (auto* worker = ServiceWorkerMain::FromVersionID(
browser_context_id_, storage_partition_config_, version_id))
worker->OnVersionRedundant();
}
@@ -206,18 +207,19 @@ v8::Local<v8::Value> ServiceWorkerContext::GetWorkerFromVersionID(
v8::Isolate* isolate,
int64_t version_id) {
return ServiceWorkerMain::From(isolate, service_worker_context_,
storage_partition_, version_id)
browser_context_id_, storage_partition_config_,
version_id)
.ToV8();
}
gin_helper::Handle<ServiceWorkerMain>
ServiceWorkerContext::GetWorkerFromVersionIDIfExists(v8::Isolate* isolate,
int64_t version_id) {
ServiceWorkerMain* worker =
ServiceWorkerMain::FromVersionID(version_id, storage_partition_);
if (!worker)
return gin_helper::Handle<ServiceWorkerMain>();
return gin_helper::CreateHandle(isolate, worker);
if (auto* worker = ServiceWorkerMain::FromVersionID(
browser_context_id_, storage_partition_config_, version_id))
return gin_helper::CreateHandle(isolate, worker);
return {};
}
v8::Local<v8::Promise> ServiceWorkerContext::StartWorkerForScope(

View File

@@ -5,18 +5,17 @@
#ifndef ELECTRON_SHELL_BROWSER_API_ELECTRON_API_SERVICE_WORKER_CONTEXT_H_
#define ELECTRON_SHELL_BROWSER_API_ELECTRON_API_SERVICE_WORKER_CONTEXT_H_
#include <string>
#include "base/memory/raw_ptr.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/service_worker_context_observer.h"
#include "content/public/browser/storage_partition_config.h"
#include "shell/browser/event_emitter_mixin.h"
#include "shell/common/gin_helper/wrappable.h"
#include "third_party/blink/public/common/service_worker/embedded_worker_status.h"
#include "third_party/blink/public/common/tokens/tokens.h"
namespace content {
class StoragePartition;
}
namespace gin_helper {
template <typename T>
class Handle;
@@ -99,9 +98,13 @@ class ServiceWorkerContext final
raw_ptr<content::ServiceWorkerContext> service_worker_context_;
// Service worker registration and versions are unique to a storage partition.
// Keep a reference to the storage partition to be used for lookups.
raw_ptr<content::StoragePartition> storage_partition_;
// A key identifying the owning BrowserContext.
// Used in ServiceWorkerMain lookups.
const std::string browser_context_id_;
// A key identifying a StoragePartition within a BrowserContext.
// Used in ServiceWorkerMain lookups.
const content::StoragePartitionConfig storage_partition_config_;
base::WeakPtrFactory<ServiceWorkerContext> weak_ptr_factory_{this};
};

View File

@@ -7,6 +7,8 @@
#include <string>
#include <utility>
#include "base/containers/flat_map.h"
#include "base/containers/map_util.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h" // nogncheck
@@ -27,7 +29,6 @@
#include "shell/common/gin_helper/promise.h"
#include "shell/common/node_includes.h"
#include "shell/common/v8_util.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
namespace {
@@ -58,27 +59,23 @@ std::optional<content::ServiceWorkerVersionBaseInfo> GetLiveVersionInfo(
namespace electron::api {
// ServiceWorkerKey -> ServiceWorkerMain*
using VersionIdMap = absl::flat_hash_map<ServiceWorkerKey,
ServiceWorkerMain*,
ServiceWorkerKey::Hasher>;
VersionIdMap& GetVersionIdMap() {
static base::NoDestructor<VersionIdMap> instance;
auto& GetVersionIdMap() {
using Map = base::flat_map<ServiceWorkerKey, ServiceWorkerMain*>;
static base::NoDestructor<Map> instance;
return *instance;
}
ServiceWorkerMain* FromServiceWorkerKey(const ServiceWorkerKey& key) {
VersionIdMap& version_map = GetVersionIdMap();
auto iter = version_map.find(key);
auto* service_worker = iter == version_map.end() ? nullptr : iter->second;
return service_worker;
return base::FindPtrOrNull(GetVersionIdMap(), key);
}
// static
ServiceWorkerMain* ServiceWorkerMain::FromVersionID(
int64_t version_id,
const content::StoragePartition* storage_partition) {
ServiceWorkerKey key(version_id, storage_partition);
std::string browser_context_id,
content::StoragePartitionConfig storage_partition_config,
int64_t version_id) {
const ServiceWorkerKey key{std::move(browser_context_id),
std::move(storage_partition_config), version_id};
return FromServiceWorkerKey(key);
}
@@ -87,8 +84,10 @@ gin::DeprecatedWrapperInfo ServiceWorkerMain::kWrapperInfo = {
ServiceWorkerMain::ServiceWorkerMain(content::ServiceWorkerContext* sw_context,
int64_t version_id,
const ServiceWorkerKey& key)
: version_id_(version_id), key_(key), service_worker_context_(sw_context) {
ServiceWorkerKey key)
: version_id_{version_id},
key_{std::move(key)},
service_worker_context_{sw_context} {
GetVersionIdMap().emplace(key_, this);
InvalidateVersionInfo();
}
@@ -298,12 +297,14 @@ gin_helper::Handle<ServiceWorkerMain> ServiceWorkerMain::New(
gin_helper::Handle<ServiceWorkerMain> ServiceWorkerMain::From(
v8::Isolate* isolate,
content::ServiceWorkerContext* sw_context,
const content::StoragePartition* storage_partition,
std::string browser_context_id,
content::StoragePartitionConfig storage_partition_config,
int64_t version_id) {
ServiceWorkerKey service_worker_key(version_id, storage_partition);
ServiceWorkerKey service_worker_key{std::move(browser_context_id),
std::move(storage_partition_config),
version_id};
auto* service_worker = FromServiceWorkerKey(service_worker_key);
if (service_worker)
if (auto* service_worker = FromServiceWorkerKey(service_worker_key))
return gin_helper::CreateHandle(isolate, service_worker);
// Ensure ServiceWorkerVersion exists and is not redundant (pending deletion)
@@ -313,8 +314,8 @@ gin_helper::Handle<ServiceWorkerMain> ServiceWorkerMain::From(
}
auto handle = gin_helper::CreateHandle(
isolate,
new ServiceWorkerMain(sw_context, version_id, service_worker_key));
isolate, new ServiceWorkerMain{sw_context, version_id,
std::move(service_worker_key)});
// Prevent garbage collection of worker until it has been deleted internally.
handle->Pin(isolate);

View File

@@ -5,13 +5,13 @@
#ifndef ELECTRON_SHELL_BROWSER_API_ELECTRON_API_SERVICE_WORKER_MAIN_H_
#define ELECTRON_SHELL_BROWSER_API_ELECTRON_API_SERVICE_WORKER_MAIN_H_
#include <compare>
#include <string>
#include "base/memory/raw_ptr.h"
#include "base/process/process.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/service_worker_version_base_info.h"
#include "content/public/browser/storage_partition_config.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -24,10 +24,6 @@
class GURL;
namespace content {
class StoragePartition;
}
namespace gin {
class Arguments;
} // namespace gin
@@ -42,41 +38,21 @@ class Promise;
namespace electron::api {
// Key to uniquely identify a ServiceWorkerMain by its Version ID within the
// associated StoragePartition.
// Key to uniquely identify a ServiceWorkerMain by its
// BrowserContext ID, the StoragePartition key, and version id.
struct ServiceWorkerKey {
std::string browser_context_id;
content::StoragePartitionConfig storage_partition_config;
int64_t version_id;
raw_ptr<const content::StoragePartition> storage_partition;
ServiceWorkerKey(int64_t id, const content::StoragePartition* partition)
: version_id(id), storage_partition(partition) {}
bool operator<(const ServiceWorkerKey& other) const {
return std::tie(version_id, storage_partition) <
std::tie(other.version_id, other.storage_partition);
}
bool operator==(const ServiceWorkerKey& other) const {
return version_id == other.version_id &&
storage_partition == other.storage_partition;
}
struct Hasher {
std::size_t operator()(const ServiceWorkerKey& key) const {
return std::hash<const content::StoragePartition*>()(
key.storage_partition) ^
std::hash<int64_t>()(key.version_id);
}
};
auto operator<=>(const ServiceWorkerKey&) const = default;
};
// Creates a wrapper to align with the lifecycle of the non-public
// content::ServiceWorkerVersion. Object instances are pinned for the lifetime
// of the underlying SW such that registered IPC handlers continue to dispatch.
//
// Instances are uniquely identified by pairing their version ID and the
// StoragePartition in which they're registered. In Electron, this is always
// the default StoragePartition for the associated BrowserContext.
// Instances are uniquely identified by pairing their version ID with the
// BrowserContext and StoragePartition in which they're registered.
class ServiceWorkerMain final
: public gin_helper::DeprecatedWrappable<ServiceWorkerMain>,
public gin_helper::Pinnable<ServiceWorkerMain>,
@@ -88,11 +64,13 @@ class ServiceWorkerMain final
static gin_helper::Handle<ServiceWorkerMain> From(
v8::Isolate* isolate,
content::ServiceWorkerContext* sw_context,
const content::StoragePartition* storage_partition,
std::string browser_context_id,
content::StoragePartitionConfig storage_partition_config,
int64_t version_id);
static ServiceWorkerMain* FromVersionID(
int64_t version_id,
const content::StoragePartition* storage_partition);
std::string browser_context_id,
content::StoragePartitionConfig storage_partition_config,
int64_t version_id);
// gin_helper::Constructible
static void FillObjectTemplate(v8::Isolate*, v8::Local<v8::ObjectTemplate>);
@@ -112,7 +90,7 @@ class ServiceWorkerMain final
protected:
explicit ServiceWorkerMain(content::ServiceWorkerContext* sw_context,
int64_t version_id,
const ServiceWorkerKey& key);
ServiceWorkerKey key);
~ServiceWorkerMain() override;
private:
@@ -146,11 +124,12 @@ class ServiceWorkerMain final
GURL ScopeURL() const;
GURL ScriptURL() const;
// Version ID unique only to the StoragePartition.
int64_t version_id_;
// Version ID assigned by the service worker storage.
const int64_t version_id_;
// Unique identifier pairing the Version ID and StoragePartition.
ServiceWorkerKey key_;
// Unique identifier pairing the Version ID, BrowserContext, and
// StoragePartition.
const ServiceWorkerKey key_;
// Whether the Service Worker version has been destroyed.
bool version_destroyed_ = false;

View File

@@ -1767,7 +1767,8 @@ bool WebContents::CheckMediaAccessPermission(
content::WebContents::FromRenderFrameHost(render_frame_host);
auto* permission_helper =
WebContentsPermissionHelper::FromWebContents(web_contents);
return permission_helper->CheckMediaAccessPermission(security_origin, type);
return permission_helper->CheckMediaAccessPermission(render_frame_host,
security_origin, type);
}
void WebContents::RequestMediaAccessPermission(

View File

@@ -86,6 +86,7 @@ JavascriptEnvironment::~JavascriptEnvironment() {
// Otherwise cppgc::internal::Sweeper::Start will try to request a task runner
// from the NodePlatform with an already unregistered isolate.
locker_.reset();
DCHECK(!microtasks_runner_);
isolate_holder_.reset();
platform_->UnregisterIsolate(isolate_);
@@ -159,6 +160,7 @@ void JavascriptEnvironment::DestroyMicrotasksRunner() {
gin_helper::CleanedUpAtExit::DoCleanup();
}
base::CurrentThread::Get()->RemoveTaskObserver(microtasks_runner_.get());
microtasks_runner_.reset();
}
} // namespace electron

View File

@@ -51,8 +51,7 @@ bool ElectronSerialDelegate::CanRequestPortPermission(
auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
auto* permission_helper =
WebContentsPermissionHelper::FromWebContents(web_contents);
return permission_helper->CheckSerialAccessPermission(
frame->GetLastCommittedOrigin());
return permission_helper->CheckSerialAccessPermission(frame);
}
bool ElectronSerialDelegate::HasPortPermission(

View File

@@ -111,7 +111,7 @@ void AutofillPopupView::Show() {
auto* host = popup_->frame_host_->GetRenderViewHost()->GetWidget();
host->AddKeyPressEventCallback(keypress_callback_);
NotifyAccessibilityEventDeprecated(ax::mojom::Event::kMenuStart, true);
GetViewAccessibility().NotifyEvent(ax::mojom::Event::kMenuStart, true);
}
void AutofillPopupView::Hide() {
@@ -122,7 +122,7 @@ void AutofillPopupView::Hide() {
}
RemoveObserver();
NotifyAccessibilityEventDeprecated(ax::mojom::Event::kMenuEnd, true);
GetViewAccessibility().NotifyEvent(ax::mojom::Event::kMenuEnd, true);
if (GetWidget()) {
GetWidget()->Close();
@@ -165,7 +165,7 @@ void AutofillPopupView::OnSelectedRowChanged(
int selected = current_row_selection.value_or(-1);
if (selected == -1 || static_cast<size_t>(selected) >= children().size())
return;
children().at(selected)->NotifyAccessibilityEventDeprecated(
children().at(selected)->GetViewAccessibility().NotifyEvent(
ax::mojom::Event::kSelection, true);
}
}

View File

@@ -228,14 +228,14 @@ void WebContentsPermissionHelper::RequestPermission(
}
bool WebContentsPermissionHelper::CheckPermission(
content::RenderFrameHost* requesting_frame,
blink::PermissionType permission,
base::DictValue details) const {
auto* rfh = web_contents_->GetPrimaryMainFrame();
auto* permission_manager = static_cast<ElectronPermissionManager*>(
web_contents_->GetBrowserContext()->GetPermissionControllerDelegate());
auto origin = web_contents_->GetLastCommittedURL();
return permission_manager->CheckPermissionWithDetails(permission, rfh, origin,
std::move(details));
auto origin = requesting_frame->GetLastCommittedOrigin().GetURL();
return permission_manager->CheckPermissionWithDetails(
permission, requesting_frame, origin, std::move(details));
}
void WebContentsPermissionHelper::RequestFullscreenPermission(
@@ -313,6 +313,7 @@ void WebContentsPermissionHelper::RequestOpenExternalPermission(
}
bool WebContentsPermissionHelper::CheckMediaAccessPermission(
content::RenderFrameHost* requesting_frame,
const url::Origin& security_origin,
blink::mojom::MediaStreamType type) const {
base::DictValue details;
@@ -321,14 +322,16 @@ bool WebContentsPermissionHelper::CheckMediaAccessPermission(
auto blink_type = type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE
? blink::PermissionType::AUDIO_CAPTURE
: blink::PermissionType::VIDEO_CAPTURE;
return CheckPermission(blink_type, std::move(details));
return CheckPermission(requesting_frame, blink_type, std::move(details));
}
bool WebContentsPermissionHelper::CheckSerialAccessPermission(
const url::Origin& embedding_origin) const {
content::RenderFrameHost* requesting_frame) const {
base::DictValue details;
details.Set("securityOrigin", embedding_origin.GetURL().spec());
return CheckPermission(blink::PermissionType::SERIAL, std::move(details));
details.Set("securityOrigin",
requesting_frame->GetLastCommittedOrigin().GetURL().spec());
return CheckPermission(requesting_frame, blink::PermissionType::SERIAL,
std::move(details));
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(WebContentsPermissionHelper);

View File

@@ -47,9 +47,11 @@ class WebContentsPermissionHelper
const GURL& url);
// Synchronous Checks
bool CheckMediaAccessPermission(const url::Origin& security_origin,
bool CheckMediaAccessPermission(content::RenderFrameHost* requesting_frame,
const url::Origin& security_origin,
blink::mojom::MediaStreamType type) const;
bool CheckSerialAccessPermission(const url::Origin& embedding_origin) const;
bool CheckSerialAccessPermission(
content::RenderFrameHost* requesting_frame) const;
private:
explicit WebContentsPermissionHelper(content::WebContents* web_contents);
@@ -61,7 +63,8 @@ class WebContentsPermissionHelper
bool user_gesture = false,
base::DictValue details = {});
bool CheckPermission(blink::PermissionType permission,
bool CheckPermission(content::RenderFrameHost* requesting_frame,
blink::PermissionType permission,
base::DictValue details) const;
// TODO(clavin): refactor to use the WebContents provided by the

View File

@@ -53,8 +53,7 @@ v8::MaybeLocal<v8::Value> CompileAndCall(
context, v8::Null(isolate), arguments->size(), arguments->data());
// This will only be caught when something has gone terrible wrong as all
// electron scripts are wrapped in a try {} catch {} by the esbuild bundler
// (see build/esbuild/bundle.js applyWrappers).
// electron scripts are wrapped in a try {} catch {} by webpack
if (try_catch.HasCaught()) {
std::string msg = "no error message";
if (!try_catch.Message().IsEmpty()) {

View File

@@ -1941,6 +1941,60 @@ describe('session module', () => {
expect(handlerDetails!.isMainFrame).to.be.false();
expect(handlerDetails!.embeddingOrigin).to.equal('file:///');
});
it('provides iframe origin as requestingOrigin for media check from cross-origin subFrame', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: {
partition: 'very-temp-permission-handler-media'
}
});
const ses = w.webContents.session;
const iframeUrl = 'https://myfakesite/';
let capturedOrigin: string | undefined;
let capturedIsMainFrame: boolean | undefined;
let capturedRequestingUrl: string | undefined;
let capturedSecurityOrigin: string | undefined;
ses.protocol.interceptStringProtocol('https', (req, cb) => {
cb('<html><body>iframe</body></html>');
});
ses.setPermissionCheckHandler((wc, permission, requestingOrigin, details) => {
if (permission === 'media') {
capturedOrigin = requestingOrigin;
capturedIsMainFrame = details.isMainFrame;
capturedRequestingUrl = details.requestingUrl;
capturedSecurityOrigin = (details as any).securityOrigin;
}
return false;
});
try {
await w.loadFile(path.join(fixtures, 'api', 'blank.html'));
w.webContents.executeJavaScript(`
var iframe = document.createElement('iframe');
iframe.src = '${iframeUrl}';
iframe.allow = 'camera; microphone';
document.body.appendChild(iframe);
null;
`);
const [,, frameProcessId, frameRoutingId] = await once(w.webContents, 'did-frame-finish-load');
const frame = webFrameMain.fromId(frameProcessId, frameRoutingId)!;
await frame.executeJavaScript(
'navigator.mediaDevices.enumerateDevices().then(() => {}).catch(() => {});',
true
);
expect(capturedOrigin).to.equal(iframeUrl);
expect(capturedIsMainFrame).to.be.false();
expect(capturedRequestingUrl).to.equal(iframeUrl);
expect(capturedSecurityOrigin).to.equal(iframeUrl);
} finally {
ses.protocol.uninterceptProtocol('https');
ses.setPermissionCheckHandler(null);
}
});
});
describe('ses.isPersistent()', () => {

View File

@@ -1,18 +0,0 @@
/* eslint-disable no-self-compare, import/enforce-node-protocol-usage */
// Regression test fixture for the esbuild __toCommonJS identity break.
// Each pair must be the exact same object reference — see the
// "module identity" describe block in spec/modules-spec.ts. The comparisons
// are intentionally self-referential and intentionally use both the bare and
// `node:`-prefixed module names, which is why no-self-compare and
// import/enforce-node-protocol-usage are disabled for this file.
const { ipcRenderer } = require('electron');
ipcRenderer.send('require-identity', {
electron: require('electron') === require('electron'),
electronCommon: require('electron/common') === require('electron'),
events: require('events') === require('node:events'),
timers: require('timers') === require('node:timers'),
url: require('url') === require('node:url'),
ipcRenderer: require('electron').ipcRenderer === require('electron').ipcRenderer,
contextBridge: require('electron').contextBridge === require('electron').contextBridge
});

View File

@@ -284,41 +284,6 @@ describe('modules support', () => {
expect(result).to.equal('function');
});
});
// Regression test for the esbuild __toCommonJS identity break: esbuild's
// runtime helper for `require()`-of-ESM allocates a fresh wrapper on
// every call (see evanw/esbuild@f4ff26d3). The bundle driver patches it
// with a WeakMap-memoized variant so module identity matches webpack's
// __webpack_require__ cache semantics. Without the patch, every getter
// on `require('electron')` that resolves to a non-default-exporting ESM
// module would yield a fresh namespace on each access.
describe('module identity', () => {
it('returns the same object for repeated require("electron") accesses in the main process', () => {
const electron = require('electron');
for (const key of Object.keys(electron)) {
// Touching `electron.net` before app ready throws — handled by the
// sandboxed-preload identity test below; everything else must be
// strictly equal across two reads.
if (key === 'net') continue;
expect((electron as any)[key]).to.equal((electron as any)[key],
`require('electron').${key} must be identity-stable across reads`);
}
});
it('returns the same object for repeated require() in a sandboxed preload', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: { sandbox: true, contextIsolation: false, preload: path.join(fixtures, 'module', 'preload-require-identity.js') }
});
const result = once(w.webContents.ipc, 'require-identity');
await w.loadURL('about:blank');
const [, identities] = await result;
for (const [name, same] of Object.entries(identities)) {
expect(same).to.equal(true, `${name} must be identity-stable in sandboxed preload`);
}
w.destroy();
});
});
});
describe('esm', () => {

View File

@@ -2,8 +2,8 @@ const childProcess = require('node:child_process');
const path = require('node:path');
const typeCheck = () => {
const tsgoExec = path.resolve(require.resolve('@typescript/native-preview/package.json'), '..', 'bin', 'tsgo.js');
const tscChild = childProcess.spawn(process.execPath, [tsgoExec, '--project', './ts-smoke/tsconfig.json'], {
const tscExec = path.resolve(require.resolve('typescript'), '../../bin/tsc');
const tscChild = childProcess.spawn(process.execPath, [tscExec, '--project', './ts-smoke/tsconfig.json'], {
cwd: path.resolve(__dirname, '../')
});
tscChild.stdout.on('data', d => console.log(d.toString()));

View File

@@ -3,7 +3,7 @@
"compilerOptions": {
"rootDir": "default_app",
"module": "ESNext",
"moduleResolution": "bundler"
"moduleResolution": "node"
},
"include": [
"default_app",

View File

@@ -1,10 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
// rootDir intentionally omitted: some lib/ files pull type declarations
// out of ../third_party/electron_node via the `@node/*` path alias (see
// lib/node/asar-fs-wrapper.ts) and a rootDir of 'lib' rejects those
// as outside-rootDir imports.
"rootDir": "lib"
},
"include": [
"lib",

View File

@@ -10,12 +10,14 @@
"sourceMap": true,
"experimentalDecorators": true,
"strict": true,
"baseUrl": ".",
"allowJs": true,
"noUnusedLocals": true,
"outDir": "ts-gen",
"typeRoots" : ["./node_modules/@types", "./spec/node_modules/@types"],
"paths": {
"@electron/internal/*": ["./lib/*"]
"@electron/internal/*": ["lib/*"],
"@node/*": ["../third_party/electron_node/*"]
}
},
"exclude": [

View File

@@ -1,36 +1,6 @@
declare const BUILDFLAG: (flag: boolean) => boolean;
/// <reference types="webpack/module" />
// esbuild build/esbuild/bundle.js rewrites calls to this identifier into
// literal `require()` calls so that consumers can reach Node internal modules
// (e.g. 'internal/modules/helpers') without the bundler trying to resolve
// them. Overloads below pin the known internal-module IDs to narrow types;
// the final catch-all signature keeps less-common paths usable at the cost
// of no static type information.
interface NodeInternalModules {
'internal/modules/helpers': {
makeRequireFunction: (mod: NodeModule) => NodeRequire;
};
'internal/modules/run_main': {
runEntryPointWithESMLoader: (cb: (cascadedLoader: any) => any) => Promise<void>;
};
'internal/fs/utils': {
getValidatedPath: (path: any, ...args: any[]) => string;
getOptions: (options: any, defaultOptions?: any) => any;
getDirent: (path: string, name: string | Buffer, type: number, callback?: Function) => any;
};
'internal/util': {
assignFunctionName: (name: string | symbol, fn: Function) => Function;
};
'internal/validators': {
validateBoolean: (value: unknown, name: string) => asserts value is boolean;
validateFunction: (value: unknown, name: string) => void;
};
'internal/url': {
URL: typeof URL;
};
}
declare function __non_webpack_require__<K extends keyof NodeInternalModules>(id: K): NodeInternalModules[K];
declare function __non_webpack_require__(id: string): unknown;
declare const BUILDFLAG: (flag: boolean) => boolean;
declare namespace NodeJS {
interface ModuleInternal extends NodeJS.Module {
@@ -312,7 +282,7 @@ declare namespace NodeJS {
}
}
declare namespace NodeJS {
declare module NodeJS {
interface Global {
require: NodeRequire;
module: NodeModule;

1463
yarn.lock

File diff suppressed because it is too large Load Diff