mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
24 Commits
feat/promp
...
replace-we
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
225fc5aa08 | ||
|
|
43d61d4cd0 | ||
|
|
646dfd24f7 | ||
|
|
f89c8efc9d | ||
|
|
ec30e4cdae | ||
|
|
40033db422 | ||
|
|
c3d441cf7d | ||
|
|
14583d22e6 | ||
|
|
68bfe49120 | ||
|
|
2c6332a7d6 | ||
|
|
ddc1bd9553 | ||
|
|
12109371d3 | ||
|
|
69891d04bf | ||
|
|
188813e206 | ||
|
|
8b768b8211 | ||
|
|
82b97ddf5b | ||
|
|
16f408a502 | ||
|
|
246aa63910 | ||
|
|
230f02faf2 | ||
|
|
1362d7b94d | ||
|
|
877fe479b5 | ||
|
|
f41438ff73 | ||
|
|
c6e201c965 | ||
|
|
156a4e610c |
20
.github/actions/build-electron/action.yml
vendored
20
.github/actions/build-electron/action.yml
vendored
@@ -48,19 +48,15 @@ runs:
|
||||
shell: bash
|
||||
run: echo "::add-matcher::src/electron/.github/problem-matchers/clang.json"
|
||||
- name: Download previous object checksums
|
||||
uses: dawidd6/action-download-artifact@09b07ec687d10771279a426c79925ee415c12906 # v17
|
||||
if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') && inputs.is-asan != 'true' }}
|
||||
with:
|
||||
name: object_checksums_${{ inputs.artifact-platform }}_${{ inputs.target-arch }}
|
||||
commit: ${{ case(github.event_name == 'push', github.event.before, github.event.pull_request.base.sha) }}
|
||||
path: src
|
||||
if_no_artifact_found: ignore
|
||||
- name: Move previous object checksums
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -f src/object-checksums_${{ inputs.artifact-platform }}_${{ inputs.target-arch }}.json ]; then
|
||||
mv src/object-checksums_${{ inputs.artifact-platform }}_${{ inputs.target-arch }}.json src/previous-object-checksums.json
|
||||
fi
|
||||
if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') && inputs.is-asan != 'true' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
ARTIFACT_NAME: object-checksums.${{ inputs.artifact-platform }}_${{ inputs.target-arch }}.json
|
||||
SEARCH_BRANCH: ${{ case(github.event_name == 'push', github.ref_name, github.event.pull_request.base.ref) }}
|
||||
REPO: ${{ github.repository }}
|
||||
OUTPUT_PATH: src/previous-object-checksums.json
|
||||
run: node src/electron/.github/actions/build-electron/download-previous-object-checksums.mjs
|
||||
- name: Build Electron ${{ inputs.step-suffix }}
|
||||
if: ${{ inputs.target-platform != 'win' }}
|
||||
shell: bash
|
||||
|
||||
82
.github/actions/build-electron/download-previous-object-checksums.mjs
vendored
Normal file
82
.github/actions/build-electron/download-previous-object-checksums.mjs
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Octokit } from '@octokit/rest';
|
||||
|
||||
import { writeFileSync } from 'node:fs';
|
||||
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
const repo = process.env.REPO;
|
||||
const artifactName = process.env.ARTIFACT_NAME;
|
||||
const branch = process.env.SEARCH_BRANCH;
|
||||
const outputPath = process.env.OUTPUT_PATH;
|
||||
|
||||
const required = { GITHUB_TOKEN: token, REPO: repo, ARTIFACT_NAME: artifactName, SEARCH_BRANCH: branch, OUTPUT_PATH: outputPath };
|
||||
const missing = Object.entries(required).filter(([, v]) => !v).map(([k]) => k);
|
||||
if (missing.length > 0) {
|
||||
console.error(`Missing required environment variables: ${missing.join(', ')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [owner, repoName] = repo.split('/');
|
||||
const octokit = new Octokit({ auth: token });
|
||||
|
||||
async function main () {
|
||||
console.log(`Searching for artifact '${artifactName}' on branch '${branch}'...`);
|
||||
|
||||
// Resolve the "Build" workflow name to an ID, mirroring how `gh run list --workflow` works
|
||||
// under the hood (it uses /repos/{owner}/{repo}/actions/workflows/{id}/runs).
|
||||
const { data: workflows } = await octokit.actions.listRepoWorkflows({ owner, repo: repoName });
|
||||
const buildWorkflow = workflows.workflows.find((w) => w.name === 'Build');
|
||||
if (!buildWorkflow) {
|
||||
console.log('Could not find "Build" workflow, continuing without previous checksums');
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: runs } = await octokit.actions.listWorkflowRuns({
|
||||
owner,
|
||||
repo: repoName,
|
||||
workflow_id: buildWorkflow.id,
|
||||
branch,
|
||||
status: 'completed',
|
||||
event: 'push',
|
||||
per_page: 20,
|
||||
exclude_pull_requests: true
|
||||
});
|
||||
|
||||
for (const run of runs.workflow_runs) {
|
||||
const { data: artifacts } = await octokit.actions.listWorkflowRunArtifacts({
|
||||
owner,
|
||||
repo: repoName,
|
||||
run_id: run.id,
|
||||
name: artifactName
|
||||
});
|
||||
|
||||
if (artifacts.artifacts.length > 0) {
|
||||
const artifact = artifacts.artifacts[0];
|
||||
console.log(`Found artifact in run ${run.id} (artifact ID: ${artifact.id}), downloading...`);
|
||||
|
||||
// Non-archived artifacts are still downloaded from the /zip endpoint
|
||||
const response = await octokit.actions.downloadArtifact({
|
||||
owner,
|
||||
repo: repoName,
|
||||
artifact_id: artifact.id,
|
||||
archive_format: 'zip'
|
||||
});
|
||||
|
||||
if (response.headers['content-type'] !== 'application/json') {
|
||||
console.error(`Unexpected content type for artifact download: ${response.headers['content-type']}`);
|
||||
console.error('Expected application/json, continuing without previous checksums');
|
||||
return;
|
||||
}
|
||||
|
||||
writeFileSync(outputPath, JSON.stringify(response.data));
|
||||
console.log('Downloaded previous object checksums successfully');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`No previous object checksums found in last ${runs.workflow_runs.length} runs, continuing without them`);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Failed to download previous object checksums, continuing without them:', err.message);
|
||||
process.exit(0);
|
||||
});
|
||||
127
.github/workflows/clean-src-cache.yml
vendored
127
.github/workflows/clean-src-cache.yml
vendored
@@ -7,6 +7,7 @@ name: Clean Source Cache
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
@@ -16,6 +17,8 @@ jobs:
|
||||
runs-on: electron-arc-centralus-linux-amd64-32core
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
DD_API_KEY: ${{ secrets.DD_API_KEY }}
|
||||
container:
|
||||
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
|
||||
options: --user root
|
||||
@@ -23,12 +26,130 @@ jobs:
|
||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||
- /mnt/win-cache:/mnt/win-cache
|
||||
steps:
|
||||
- name: Get Disk Space Before Cleanup
|
||||
id: disk-before
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Disk space before cleanup:"
|
||||
df -h /mnt/cross-instance-cache
|
||||
df -h /mnt/win-cache
|
||||
CROSS_FREE_BEFORE=$(df -k /mnt/cross-instance-cache | tail -1 | awk '{print $4}')
|
||||
CROSS_TOTAL=$(df -k /mnt/cross-instance-cache | tail -1 | awk '{print $2}')
|
||||
WIN_FREE_BEFORE=$(df -k /mnt/win-cache | tail -1 | awk '{print $4}')
|
||||
WIN_TOTAL=$(df -k /mnt/win-cache | tail -1 | awk '{print $2}')
|
||||
echo "cross_free_kb=$CROSS_FREE_BEFORE" >> $GITHUB_OUTPUT
|
||||
echo "cross_total_kb=$CROSS_TOTAL" >> $GITHUB_OUTPUT
|
||||
echo "win_free_kb=$WIN_FREE_BEFORE" >> $GITHUB_OUTPUT
|
||||
echo "win_total_kb=$WIN_TOTAL" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cleanup Source Cache
|
||||
shell: bash
|
||||
run: |
|
||||
df -h /mnt/cross-instance-cache
|
||||
find /mnt/cross-instance-cache -type f -mtime +15 -delete
|
||||
find /mnt/win-cache -type f -mtime +15 -delete
|
||||
|
||||
- name: Get Disk Space After Cleanup
|
||||
id: disk-after
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Disk space after cleanup:"
|
||||
df -h /mnt/cross-instance-cache
|
||||
df -h /mnt/win-cache
|
||||
find /mnt/win-cache -type f -mtime +15 -delete
|
||||
df -h /mnt/win-cache
|
||||
CROSS_FREE_AFTER=$(df -k /mnt/cross-instance-cache | tail -1 | awk '{print $4}')
|
||||
WIN_FREE_AFTER=$(df -k /mnt/win-cache | tail -1 | awk '{print $4}')
|
||||
echo "cross_free_kb=$CROSS_FREE_AFTER" >> $GITHUB_OUTPUT
|
||||
echo "win_free_kb=$WIN_FREE_AFTER" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Log Disk Space to Datadog
|
||||
if: ${{ env.DD_API_KEY != '' }}
|
||||
shell: bash
|
||||
env:
|
||||
CROSS_FREE_BEFORE: ${{ steps.disk-before.outputs.cross_free_kb }}
|
||||
CROSS_FREE_AFTER: ${{ steps.disk-after.outputs.cross_free_kb }}
|
||||
CROSS_TOTAL: ${{ steps.disk-before.outputs.cross_total_kb }}
|
||||
WIN_FREE_BEFORE: ${{ steps.disk-before.outputs.win_free_kb }}
|
||||
WIN_FREE_AFTER: ${{ steps.disk-after.outputs.win_free_kb }}
|
||||
WIN_TOTAL: ${{ steps.disk-before.outputs.win_total_kb }}
|
||||
run: |
|
||||
TIMESTAMP=$(date +%s)
|
||||
|
||||
CROSS_FREE_BEFORE_GB=$(awk "BEGIN {printf \"%.2f\", $CROSS_FREE_BEFORE / 1024 / 1024}")
|
||||
CROSS_FREE_AFTER_GB=$(awk "BEGIN {printf \"%.2f\", $CROSS_FREE_AFTER / 1024 / 1024}")
|
||||
CROSS_FREED_GB=$(awk "BEGIN {printf \"%.2f\", ($CROSS_FREE_AFTER - $CROSS_FREE_BEFORE) / 1024 / 1024}")
|
||||
CROSS_TOTAL_GB=$(awk "BEGIN {printf \"%.2f\", $CROSS_TOTAL / 1024 / 1024}")
|
||||
|
||||
WIN_FREE_BEFORE_GB=$(awk "BEGIN {printf \"%.2f\", $WIN_FREE_BEFORE / 1024 / 1024}")
|
||||
WIN_FREE_AFTER_GB=$(awk "BEGIN {printf \"%.2f\", $WIN_FREE_AFTER / 1024 / 1024}")
|
||||
WIN_FREED_GB=$(awk "BEGIN {printf \"%.2f\", ($WIN_FREE_AFTER - $WIN_FREE_BEFORE) / 1024 / 1024}")
|
||||
WIN_TOTAL_GB=$(awk "BEGIN {printf \"%.2f\", $WIN_TOTAL / 1024 / 1024}")
|
||||
|
||||
echo "cross-instance-cache: free before=${CROSS_FREE_BEFORE_GB}GB, after=${CROSS_FREE_AFTER_GB}GB, freed=${CROSS_FREED_GB}GB, total=${CROSS_TOTAL_GB}GB"
|
||||
echo "win-cache: free before=${WIN_FREE_BEFORE_GB}GB, after=${WIN_FREE_AFTER_GB}GB, freed=${WIN_FREED_GB}GB, total=${WIN_TOTAL_GB}GB"
|
||||
|
||||
curl -s -X POST "https://api.datadoghq.com/api/v2/series" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "DD-API-KEY: ${DD_API_KEY}" \
|
||||
-d @- << EOF
|
||||
{
|
||||
"series": [
|
||||
{
|
||||
"metric": "electron.src_cache.disk.free_space_before_cleanup_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${CROSS_FREE_BEFORE_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:cross-instance-cache", "platform:linux"]
|
||||
},
|
||||
{
|
||||
"metric": "electron.src_cache.disk.free_space_after_cleanup_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${CROSS_FREE_AFTER_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:cross-instance-cache", "platform:linux"]
|
||||
},
|
||||
{
|
||||
"metric": "electron.src_cache.disk.space_freed_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${CROSS_FREED_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:cross-instance-cache", "platform:linux"]
|
||||
},
|
||||
{
|
||||
"metric": "electron.src_cache.disk.total_space_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${CROSS_TOTAL_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:cross-instance-cache", "platform:linux"]
|
||||
},
|
||||
{
|
||||
"metric": "electron.src_cache.disk.free_space_before_cleanup_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${WIN_FREE_BEFORE_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:win-cache", "platform:linux"]
|
||||
},
|
||||
{
|
||||
"metric": "electron.src_cache.disk.free_space_after_cleanup_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${WIN_FREE_AFTER_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:win-cache", "platform:linux"]
|
||||
},
|
||||
{
|
||||
"metric": "electron.src_cache.disk.space_freed_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${WIN_FREED_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:win-cache", "platform:linux"]
|
||||
},
|
||||
{
|
||||
"metric": "electron.src_cache.disk.total_space_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${WIN_TOTAL_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:win-cache", "platform:linux"]
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Disk space metrics logged to Datadog"
|
||||
|
||||
2
.github/workflows/pull-request-labeled.yml
vendored
2
.github/workflows/pull-request-labeled.yml
vendored
@@ -72,7 +72,7 @@ jobs:
|
||||
|
||||
Hello @${{ github.event.pull_request.user.login }}. Due to the high amount of AI spam PRs we receive, if a PR is detected to be majority AI-generated without disclosure and untested, we will automatically close the PR.
|
||||
|
||||
We welcome the use of AI tools, as long as the PR meets our quality standards and has clearly been built and tested. If you believe your PR was closed in error, we welcome you to resubmit. However, please read our [CONTRIBUTING.md](http://contributing.md/) carefully before reopening. Thanks for your contribution.
|
||||
We welcome the use of AI tools, as long as the PR meets our quality standards and has clearly been built and tested. If you believe your PR was closed in error, we welcome you to resubmit. However, please read our [CONTRIBUTING.md](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) and [AI Tool Policy](https://github.com/electron/governance/blob/main/policy/ai.md) carefully before reopening. Thanks for your contribution.
|
||||
- name: Close the pull request
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
41
BUILD.gn
41
BUILD.gn
@@ -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,75 +162,81 @@ npm_action("build_electron_definitions") {
|
||||
outputs = [ "$target_gen_dir/tsc/typings/electron.d.ts" ]
|
||||
}
|
||||
|
||||
webpack_build("electron_browser_bundle") {
|
||||
typescript_check("electron_lib_typecheck") {
|
||||
deps = [ ":build_electron_definitions" ]
|
||||
tsconfig = "//electron/tsconfig.electron.json"
|
||||
sources = auto_filenames.typecheck_sources
|
||||
}
|
||||
|
||||
esbuild_build("electron_browser_bundle") {
|
||||
deps = [ ":build_electron_definitions" ]
|
||||
|
||||
inputs = auto_filenames.browser_bundle_deps
|
||||
|
||||
config_file = "//electron/build/webpack/webpack.config.browser.js"
|
||||
config_file = "//electron/build/esbuild/configs/browser.js"
|
||||
out_file = "$target_gen_dir/js2c/browser_init.js"
|
||||
}
|
||||
|
||||
webpack_build("electron_renderer_bundle") {
|
||||
esbuild_build("electron_renderer_bundle") {
|
||||
deps = [ ":build_electron_definitions" ]
|
||||
|
||||
inputs = auto_filenames.renderer_bundle_deps
|
||||
|
||||
config_file = "//electron/build/webpack/webpack.config.renderer.js"
|
||||
config_file = "//electron/build/esbuild/configs/renderer.js"
|
||||
out_file = "$target_gen_dir/js2c/renderer_init.js"
|
||||
}
|
||||
|
||||
webpack_build("electron_worker_bundle") {
|
||||
esbuild_build("electron_worker_bundle") {
|
||||
deps = [ ":build_electron_definitions" ]
|
||||
|
||||
inputs = auto_filenames.worker_bundle_deps
|
||||
|
||||
config_file = "//electron/build/webpack/webpack.config.worker.js"
|
||||
config_file = "//electron/build/esbuild/configs/worker.js"
|
||||
out_file = "$target_gen_dir/js2c/worker_init.js"
|
||||
}
|
||||
|
||||
webpack_build("electron_sandboxed_renderer_bundle") {
|
||||
esbuild_build("electron_sandboxed_renderer_bundle") {
|
||||
deps = [ ":build_electron_definitions" ]
|
||||
|
||||
inputs = auto_filenames.sandbox_bundle_deps
|
||||
|
||||
config_file = "//electron/build/webpack/webpack.config.sandboxed_renderer.js"
|
||||
config_file = "//electron/build/esbuild/configs/sandboxed_renderer.js"
|
||||
out_file = "$target_gen_dir/js2c/sandbox_bundle.js"
|
||||
}
|
||||
|
||||
webpack_build("electron_isolated_renderer_bundle") {
|
||||
esbuild_build("electron_isolated_renderer_bundle") {
|
||||
deps = [ ":build_electron_definitions" ]
|
||||
|
||||
inputs = auto_filenames.isolated_bundle_deps
|
||||
|
||||
config_file = "//electron/build/webpack/webpack.config.isolated_renderer.js"
|
||||
config_file = "//electron/build/esbuild/configs/isolated_renderer.js"
|
||||
out_file = "$target_gen_dir/js2c/isolated_bundle.js"
|
||||
}
|
||||
|
||||
webpack_build("electron_node_bundle") {
|
||||
esbuild_build("electron_node_bundle") {
|
||||
deps = [ ":build_electron_definitions" ]
|
||||
|
||||
inputs = auto_filenames.node_bundle_deps
|
||||
|
||||
config_file = "//electron/build/webpack/webpack.config.node.js"
|
||||
config_file = "//electron/build/esbuild/configs/node.js"
|
||||
out_file = "$target_gen_dir/js2c/node_init.js"
|
||||
}
|
||||
|
||||
webpack_build("electron_utility_bundle") {
|
||||
esbuild_build("electron_utility_bundle") {
|
||||
deps = [ ":build_electron_definitions" ]
|
||||
|
||||
inputs = auto_filenames.utility_bundle_deps
|
||||
|
||||
config_file = "//electron/build/webpack/webpack.config.utility.js"
|
||||
config_file = "//electron/build/esbuild/configs/utility.js"
|
||||
out_file = "$target_gen_dir/js2c/utility_init.js"
|
||||
}
|
||||
|
||||
webpack_build("electron_preload_realm_bundle") {
|
||||
esbuild_build("electron_preload_realm_bundle") {
|
||||
deps = [ ":build_electron_definitions" ]
|
||||
|
||||
inputs = auto_filenames.preload_realm_bundle_deps
|
||||
|
||||
config_file = "//electron/build/webpack/webpack.config.preload_realm.js"
|
||||
config_file = "//electron/build/esbuild/configs/preload_realm.js"
|
||||
out_file = "$target_gen_dir/js2c/preload_realm_bundle.js"
|
||||
}
|
||||
|
||||
@@ -238,6 +244,7 @@ action("electron_js2c") {
|
||||
deps = [
|
||||
":electron_browser_bundle",
|
||||
":electron_isolated_renderer_bundle",
|
||||
":electron_lib_typecheck",
|
||||
":electron_node_bundle",
|
||||
":electron_preload_realm_bundle",
|
||||
":electron_renderer_bundle",
|
||||
|
||||
326
build/esbuild/bundle.js
Normal file
326
build/esbuild/bundle.js
Normal file
@@ -0,0 +1,326 @@
|
||||
#!/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);
|
||||
});
|
||||
4
build/esbuild/configs/browser.js
Normal file
4
build/esbuild/configs/browser.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
target: 'browser',
|
||||
alwaysHasNode: true
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = require('./webpack.config.base')({
|
||||
module.exports = {
|
||||
target: 'isolated_renderer',
|
||||
alwaysHasNode: false,
|
||||
wrapInitWithTryCatch: true
|
||||
});
|
||||
};
|
||||
4
build/esbuild/configs/node.js
Normal file
4
build/esbuild/configs/node.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
target: 'node',
|
||||
alwaysHasNode: true
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = require('./webpack.config.base')({
|
||||
module.exports = {
|
||||
target: 'preload_realm',
|
||||
alwaysHasNode: false,
|
||||
wrapInitWithProfilingTimeout: true,
|
||||
wrapInitWithTryCatch: true
|
||||
});
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = require('./webpack.config.base')({
|
||||
module.exports = {
|
||||
target: 'renderer',
|
||||
alwaysHasNode: true,
|
||||
targetDeletesNodeGlobals: true,
|
||||
wrapInitWithProfilingTimeout: true,
|
||||
wrapInitWithTryCatch: true
|
||||
});
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = require('./webpack.config.base')({
|
||||
module.exports = {
|
||||
target: 'sandboxed_renderer',
|
||||
alwaysHasNode: false,
|
||||
wrapInitWithProfilingTimeout: true,
|
||||
wrapInitWithTryCatch: true
|
||||
});
|
||||
};
|
||||
4
build/esbuild/configs/utility.js
Normal file
4
build/esbuild/configs/utility.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
target: 'utility',
|
||||
alwaysHasNode: true
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = require('./webpack.config.base')({
|
||||
module.exports = {
|
||||
target: 'worker',
|
||||
loadElectronFromAlternateTarget: 'renderer',
|
||||
alwaysHasNode: true,
|
||||
targetDeletesNodeGlobals: true,
|
||||
wrapInitWithTryCatch: true
|
||||
});
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
import("../npm.gni")
|
||||
|
||||
template("webpack_build") {
|
||||
assert(defined(invoker.config_file), "Need webpack config file to run")
|
||||
template("esbuild_build") {
|
||||
assert(defined(invoker.config_file), "Need esbuild config file to run")
|
||||
assert(defined(invoker.out_file), "Need output file to run")
|
||||
assert(defined(invoker.inputs), "Need webpack inputs to run")
|
||||
assert(defined(invoker.inputs), "Need esbuild inputs to run")
|
||||
|
||||
npm_action(target_name) {
|
||||
forward_variables_from(invoker,
|
||||
@@ -11,11 +11,14 @@ template("webpack_build") {
|
||||
"deps",
|
||||
"public_deps",
|
||||
])
|
||||
script = "webpack"
|
||||
script = "bundle"
|
||||
|
||||
inputs = [
|
||||
invoker.config_file,
|
||||
"//electron/build/webpack/webpack.config.base.js",
|
||||
"//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/tsconfig.json",
|
||||
"//electron/yarn.lock",
|
||||
"//electron/typings/internal-ambient.d.ts",
|
||||
@@ -34,10 +37,10 @@ template("webpack_build") {
|
||||
get_path_info(invoker.out_file, "file"),
|
||||
"--output-path",
|
||||
rebase_path(get_path_info(invoker.out_file, "dir")),
|
||||
"--env",
|
||||
"buildflags=" + rebase_path("$target_gen_dir/buildflags/buildflags.h"),
|
||||
"--env",
|
||||
"mode=" + mode,
|
||||
"--buildflags",
|
||||
rebase_path("$target_gen_dir/buildflags/buildflags.h"),
|
||||
"--mode",
|
||||
mode,
|
||||
]
|
||||
deps += [ "//electron/buildflags" ]
|
||||
|
||||
18
build/esbuild/shims/browser-globals-shim.js
Normal file
18
build/esbuild/shims/browser-globals-shim.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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 };
|
||||
14
build/esbuild/shims/node-globals-shim.js
Normal file
14
build/esbuild/shims/node-globals-shim.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// 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 };
|
||||
7
build/esbuild/shims/promise-shim.js
Normal file
7
build/esbuild/shims/promise-shim.js
Normal file
@@ -0,0 +1,7 @@
|
||||
// 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 };
|
||||
@@ -1,5 +1,42 @@
|
||||
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")
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
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
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
module.exports = require('./webpack.config.base')({
|
||||
target: 'browser',
|
||||
alwaysHasNode: true
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
module.exports = require('./webpack.config.base')({
|
||||
target: 'node',
|
||||
alwaysHasNode: true
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
module.exports = require('./webpack.config.base')({
|
||||
target: 'utility',
|
||||
alwaysHasNode: true
|
||||
});
|
||||
@@ -174,62 +174,10 @@ auto_filenames = {
|
||||
"docs/api/structures/window-session-end-event.md",
|
||||
]
|
||||
|
||||
sandbox_bundle_deps = [
|
||||
"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",
|
||||
"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/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",
|
||||
"package.json",
|
||||
"tsconfig.electron.json",
|
||||
"tsconfig.json",
|
||||
"typings/internal-ambient.d.ts",
|
||||
"typings/internal-electron.d.ts",
|
||||
]
|
||||
|
||||
isolated_bundle_deps = [
|
||||
"lib/common/web-view-methods.ts",
|
||||
"lib/isolated_renderer/init.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",
|
||||
"package.json",
|
||||
"tsconfig.electron.json",
|
||||
"tsconfig.json",
|
||||
"typings/internal-ambient.d.ts",
|
||||
"typings/internal-electron.d.ts",
|
||||
]
|
||||
|
||||
browser_bundle_deps = [
|
||||
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",
|
||||
@@ -300,8 +248,189 @@ 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/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/renderer/api/context-bridge.ts",
|
||||
"lib/renderer/api/crash-reporter.ts",
|
||||
"lib/renderer/api/ipc-renderer.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/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",
|
||||
"package.json",
|
||||
"tsconfig.electron.json",
|
||||
"tsconfig.json",
|
||||
"typings/internal-ambient.d.ts",
|
||||
"typings/internal-electron.d.ts",
|
||||
]
|
||||
|
||||
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",
|
||||
"lib/renderer/web-view/web-view-constants.ts",
|
||||
"lib/renderer/web-view/web-view-element.ts",
|
||||
"lib/renderer/web-view/web-view-impl.ts",
|
||||
"package.json",
|
||||
"tsconfig.electron.json",
|
||||
"tsconfig.json",
|
||||
"typings/internal-ambient.d.ts",
|
||||
"typings/internal-electron.d.ts",
|
||||
]
|
||||
|
||||
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",
|
||||
"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/common/webpack-globals-provider.ts",
|
||||
"package.json",
|
||||
"tsconfig.electron.json",
|
||||
"tsconfig.json",
|
||||
@@ -310,6 +439,8 @@ 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",
|
||||
@@ -317,8 +448,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",
|
||||
@@ -352,6 +483,8 @@ 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",
|
||||
@@ -359,7 +492,7 @@ auto_filenames = {
|
||||
"lib/common/deprecate.ts",
|
||||
"lib/common/init.ts",
|
||||
"lib/common/ipc-messages.ts",
|
||||
"lib/common/webpack-provider.ts",
|
||||
"lib/common/timers-shim.ts",
|
||||
"lib/renderer/api/clipboard.ts",
|
||||
"lib/renderer/api/context-bridge.ts",
|
||||
"lib/renderer/api/crash-reporter.ts",
|
||||
@@ -381,6 +514,7 @@ auto_filenames = {
|
||||
]
|
||||
|
||||
node_bundle_deps = [
|
||||
"build/esbuild/shims/promise-shim.js",
|
||||
"lib/node/asar-fs-wrapper.ts",
|
||||
"lib/node/init.ts",
|
||||
"package.json",
|
||||
@@ -391,6 +525,7 @@ 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",
|
||||
@@ -398,7 +533,7 @@ auto_filenames = {
|
||||
"lib/common/define-properties.ts",
|
||||
"lib/common/deprecate.ts",
|
||||
"lib/common/init.ts",
|
||||
"lib/common/webpack-globals-provider.ts",
|
||||
"lib/common/timers-shim.ts",
|
||||
"lib/utility/api/exports/electron.ts",
|
||||
"lib/utility/api/module-list.ts",
|
||||
"lib/utility/api/net.ts",
|
||||
@@ -412,10 +547,11 @@ 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",
|
||||
|
||||
@@ -5,23 +5,27 @@ import type { ClientRequestConstructorOptions } from 'electron/main';
|
||||
|
||||
const { isOnline } = process._linkedBinding('electron_common_net');
|
||||
|
||||
export function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
|
||||
function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
|
||||
if (!app.isReady()) {
|
||||
throw new Error('net module can only be used after app is ready');
|
||||
}
|
||||
return new ClientRequest(options, callback);
|
||||
}
|
||||
|
||||
export function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
|
||||
function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
|
||||
return session.defaultSession.fetch(input, init);
|
||||
}
|
||||
|
||||
export function resolveHost (host: string, options?: Electron.ResolveHostOptions): Promise<Electron.ResolvedHost> {
|
||||
function resolveHost (host: string, options?: Electron.ResolveHostOptions): Promise<Electron.ResolvedHost> {
|
||||
return session.defaultSession.resolveHost(host, options);
|
||||
}
|
||||
|
||||
exports.isOnline = isOnline;
|
||||
|
||||
Object.defineProperty(exports, 'online', {
|
||||
get: () => isOnline()
|
||||
});
|
||||
module.exports = {
|
||||
request,
|
||||
fetch,
|
||||
resolveHost,
|
||||
isOnline,
|
||||
get online () {
|
||||
return isOnline();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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') as typeof import('@node/lib/internal/modules/run_main');
|
||||
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main');
|
||||
const main = (require('url') as typeof url).pathToFileURL(path.join(packagePath, mainStartupScript));
|
||||
runEntryPointWithESMLoader(async (cascadedLoader: any) => {
|
||||
try {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import timers = require('timers');
|
||||
import * as timers from '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 = timers.setImmediate = wrapWithActivateUvLoop(timers.setImmediate);
|
||||
global.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.
|
||||
timers.setTimeout = wrapWithActivateUvLoop(timers.setTimeout);
|
||||
timers.setInterval = wrapWithActivateUvLoop(timers.setInterval);
|
||||
const wrappedSetTimeout = wrapWithActivateUvLoop(timers.setTimeout);
|
||||
const wrappedSetInterval = 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 @@ timers.setInterval = wrapWithActivateUvLoop(timers.setInterval);
|
||||
// are deleted in these processes, see renderer/init.js for reference.
|
||||
if (process.type === 'browser' ||
|
||||
process.type === 'utility') {
|
||||
global.setTimeout = timers.setTimeout;
|
||||
global.setInterval = timers.setInterval;
|
||||
global.setTimeout = wrappedSetTimeout;
|
||||
global.setInterval = wrappedSetInterval;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Drop-in replacement for timers-browserify@1.4.2.
|
||||
// Provides the Node.js 'timers' API surface for renderer/web webpack bundles
|
||||
// Provides the Node.js 'timers' API surface for renderer/web bundles
|
||||
// without relying on window.postMessage (which the newer timers-browserify 2.x
|
||||
// polyfill uses and can interfere with Electron IPC).
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// 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;
|
||||
@@ -1,18 +0,0 @@
|
||||
// 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
|
||||
};
|
||||
@@ -52,20 +52,20 @@ const {
|
||||
getValidatedPath,
|
||||
getOptions,
|
||||
getDirent
|
||||
} = __non_webpack_require__('internal/fs/utils') as typeof import('@node/lib/internal/fs/utils');
|
||||
} = __non_webpack_require__('internal/fs/utils');
|
||||
|
||||
const {
|
||||
assignFunctionName
|
||||
} = __non_webpack_require__('internal/util') as typeof import('@node/lib/internal/util');
|
||||
} = __non_webpack_require__('internal/util');
|
||||
|
||||
const {
|
||||
validateBoolean,
|
||||
validateFunction
|
||||
} = __non_webpack_require__('internal/validators') as typeof import('@node/lib/internal/validators');
|
||||
} = __non_webpack_require__('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') as typeof import('@node/lib/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');
|
||||
|
||||
// Separate asar package's path from full path.
|
||||
const splitPath = (archivePathOrBuffer: string | Buffer | URL) => {
|
||||
|
||||
@@ -29,8 +29,9 @@ Module._load = function (request: string) {
|
||||
// code with JavaScript.
|
||||
//
|
||||
// Note 3: We provide the equivalent extra variables internally through the
|
||||
// webpack ProvidePlugin in webpack.config.base.js. If you add any extra
|
||||
// variables to this wrapper please ensure to update that plugin as well.
|
||||
// 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.
|
||||
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
|
||||
@@ -65,9 +66,9 @@ require('@electron/internal/renderer/common-init');
|
||||
|
||||
if (nodeIntegration) {
|
||||
// Export node bindings to global.
|
||||
const { makeRequireFunction } = __non_webpack_require__('internal/modules/helpers') as typeof import('@node/lib/internal/modules/helpers');
|
||||
const { makeRequireFunction } = __non_webpack_require__('internal/modules/helpers');
|
||||
global.module = new Module('electron/js2c/renderer_init');
|
||||
global.require = makeRequireFunction(global.module) as NodeRequire;
|
||||
global.require = makeRequireFunction(global.module);
|
||||
|
||||
// Set the __filename to the path of html file if it is file: protocol.
|
||||
if (window.location.protocol === 'file:') {
|
||||
@@ -152,7 +153,7 @@ if (cjsPreloads.length) {
|
||||
}
|
||||
}
|
||||
if (esmPreloads.length) {
|
||||
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main') as typeof import('@node/lib/internal/modules/run_main');
|
||||
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main');
|
||||
|
||||
runEntryPointWithESMLoader(async (cascadedLoader: any) => {
|
||||
// Load the preload scripts.
|
||||
|
||||
@@ -5,18 +5,20 @@ import type { ClientRequestConstructorOptions, IncomingMessage } from 'electron/
|
||||
|
||||
const { isOnline, resolveHost } = process._linkedBinding('electron_common_net');
|
||||
|
||||
export function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
|
||||
function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
|
||||
return new ClientRequest(options, callback);
|
||||
}
|
||||
|
||||
export function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
|
||||
function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
|
||||
return fetchWithSession(input, init, undefined, request);
|
||||
}
|
||||
|
||||
exports.resolveHost = resolveHost;
|
||||
|
||||
exports.isOnline = isOnline;
|
||||
|
||||
Object.defineProperty(exports, 'online', {
|
||||
get: () => isOnline()
|
||||
});
|
||||
module.exports = {
|
||||
request,
|
||||
fetch,
|
||||
resolveHost,
|
||||
isOnline,
|
||||
get online () {
|
||||
return isOnline();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ parentPort.on('removeListener', (name: string) => {
|
||||
});
|
||||
|
||||
// Finally load entry script.
|
||||
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main') as typeof import('@node/lib/internal/modules/run_main');
|
||||
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main');
|
||||
const mainEntry = pathToFileURL(entryScript);
|
||||
|
||||
runEntryPointWithESMLoader(async (cascadedLoader: any) => {
|
||||
|
||||
@@ -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') as typeof import('@node/lib/internal/modules/helpers');
|
||||
const { makeRequireFunction } = __non_webpack_require__('internal/modules/helpers');
|
||||
global.module = new Module('electron/js2c/worker_init');
|
||||
global.require = makeRequireFunction(global.module) as NodeRequire;
|
||||
global.require = makeRequireFunction(global.module);
|
||||
|
||||
// See WebWorkerObserver::WorkerScriptReadyForEvaluation.
|
||||
if ((globalThis as any).blinkfetch) {
|
||||
|
||||
12
package.json
12
package.json
@@ -23,10 +23,12 @@
|
||||
"@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",
|
||||
@@ -43,20 +45,15 @@
|
||||
"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,
|
||||
@@ -93,8 +90,9 @@
|
||||
"repl": "node ./script/start.js --interactive",
|
||||
"start": "node ./script/start.js",
|
||||
"test": "node ./script/spec-runner.js",
|
||||
"tsc": "tsc",
|
||||
"webpack": "webpack"
|
||||
"tsc": "tsgo",
|
||||
"tsc-check": "node script/typecheck.js",
|
||||
"bundle": "node build/esbuild/bundle.js"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "Electron Community",
|
||||
|
||||
@@ -68,7 +68,7 @@ index f91857eb0b6ad385721b8224100de26dfdd7dd8d..45e8766fcb8d46d8edc3bf8d21d3f826
|
||||
: PdfRenderSettings::Mode::POSTSCRIPT_LEVEL3;
|
||||
}
|
||||
diff --git a/chrome/browser/printing/print_view_manager_base.cc b/chrome/browser/printing/print_view_manager_base.cc
|
||||
index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f0c49c1e8 100644
|
||||
index aa79c324af2cec50019bca3bccff5d420fb30ffd..0b85598f87673537eccdd0b310e8462e96990d04 100644
|
||||
--- a/chrome/browser/printing/print_view_manager_base.cc
|
||||
+++ b/chrome/browser/printing/print_view_manager_base.cc
|
||||
@@ -80,6 +80,20 @@ namespace printing {
|
||||
@@ -260,12 +260,18 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
if (prefs && prefs->HasPrefPath(prefs::kPrintRasterizePdfDpi)) {
|
||||
int value = prefs->GetInteger(prefs::kPrintRasterizePdfDpi);
|
||||
if (value > 0)
|
||||
@@ -740,8 +765,22 @@ void PrintViewManagerBase::UpdatePrintSettings(
|
||||
@@ -740,8 +765,28 @@ void PrintViewManagerBase::UpdatePrintSettings(
|
||||
}
|
||||
}
|
||||
|
||||
-#if BUILDFLAG(IS_WIN)
|
||||
- // TODO(crbug.com/40260379): Remove this if the printable areas can be made
|
||||
+#if BUILDFLAG(ENABLE_OOP_PRINTING)
|
||||
+ if (ShouldPrintJobOop() && !query_with_ui_client_id().has_value()) {
|
||||
+ RegisterSystemPrintClient();
|
||||
+ }
|
||||
+#endif
|
||||
+
|
||||
+ std::unique_ptr<PrinterQuery> query =
|
||||
+ queue_->CreatePrinterQuery(GetCurrentTargetFrame()->GetGlobalId());
|
||||
+ auto* query_ptr = query.get();
|
||||
@@ -285,7 +291,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
// fully available from `PrintBackend::GetPrinterSemanticCapsAndDefaults()`
|
||||
// for in-browser queries.
|
||||
if (printer_type == mojom::PrinterType::kLocal) {
|
||||
@@ -762,8 +801,6 @@ void PrintViewManagerBase::UpdatePrintSettings(
|
||||
@@ -762,8 +807,6 @@ void PrintViewManagerBase::UpdatePrintSettings(
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -294,7 +300,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
}
|
||||
|
||||
void PrintViewManagerBase::SetAccessibilityTree(
|
||||
@@ -779,7 +816,7 @@ void PrintViewManagerBase::SetAccessibilityTree(
|
||||
@@ -779,7 +822,7 @@ void PrintViewManagerBase::SetAccessibilityTree(
|
||||
void PrintViewManagerBase::IsPrintingEnabled(
|
||||
IsPrintingEnabledCallback callback) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
@@ -303,7 +309,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
}
|
||||
|
||||
void PrintViewManagerBase::ScriptedPrint(mojom::ScriptedPrintParamsPtr params,
|
||||
@@ -805,7 +842,7 @@ void PrintViewManagerBase::ScriptedPrint(mojom::ScriptedPrintParamsPtr params,
|
||||
@@ -805,7 +848,7 @@ void PrintViewManagerBase::ScriptedPrint(mojom::ScriptedPrintParamsPtr params,
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -312,7 +318,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
std::optional<enterprise_connectors::ContentAnalysisDelegate::Data>
|
||||
scanning_data = enterprise_data_protection::GetPrintAnalysisData(
|
||||
web_contents(), enterprise_data_protection::PrintScanningContext::
|
||||
@@ -835,11 +872,9 @@ void PrintViewManagerBase::PrintingFailed(int32_t cookie,
|
||||
@@ -835,11 +878,9 @@ void PrintViewManagerBase::PrintingFailed(int32_t cookie,
|
||||
// destroyed. In such cases the error notification to the user will
|
||||
// have already been displayed, and a second message should not be
|
||||
// shown.
|
||||
@@ -326,7 +332,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
ReleasePrinterQuery();
|
||||
}
|
||||
|
||||
@@ -851,15 +886,33 @@ void PrintViewManagerBase::RemoveTestObserver(TestObserver& observer) {
|
||||
@@ -851,15 +892,33 @@ void PrintViewManagerBase::RemoveTestObserver(TestObserver& observer) {
|
||||
test_observers_.RemoveObserver(&observer);
|
||||
}
|
||||
|
||||
@@ -360,7 +366,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
}
|
||||
|
||||
void PrintViewManagerBase::RenderFrameDeleted(
|
||||
@@ -901,13 +954,14 @@ void PrintViewManagerBase::SystemDialogCancelled() {
|
||||
@@ -901,13 +960,14 @@ void PrintViewManagerBase::SystemDialogCancelled() {
|
||||
// System dialog was cancelled. Clean up the print job and notify the
|
||||
// BackgroundPrintingManager.
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
@@ -376,7 +382,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
}
|
||||
|
||||
void PrintViewManagerBase::OnDocDone(int job_id, PrintedDocument* document) {
|
||||
@@ -921,18 +975,26 @@ void PrintViewManagerBase::OnJobDone() {
|
||||
@@ -921,18 +981,26 @@ void PrintViewManagerBase::OnJobDone() {
|
||||
// Printing is done, we don't need it anymore.
|
||||
// print_job_->is_job_pending() may still be true, depending on the order
|
||||
// of object registration.
|
||||
@@ -405,7 +411,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
TerminatePrintJob(true);
|
||||
}
|
||||
|
||||
@@ -942,7 +1004,7 @@ bool PrintViewManagerBase::RenderAllMissingPagesNow() {
|
||||
@@ -942,7 +1010,7 @@ bool PrintViewManagerBase::RenderAllMissingPagesNow() {
|
||||
|
||||
// Is the document already complete?
|
||||
if (print_job_->document() && print_job_->document()->IsComplete()) {
|
||||
@@ -414,7 +420,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -995,7 +1057,10 @@ bool PrintViewManagerBase::SetupNewPrintJob(
|
||||
@@ -995,7 +1063,10 @@ bool PrintViewManagerBase::SetupNewPrintJob(
|
||||
|
||||
// Disconnect the current `print_job_`.
|
||||
auto weak_this = weak_ptr_factory_.GetWeakPtr();
|
||||
@@ -426,7 +432,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
if (!weak_this)
|
||||
return false;
|
||||
|
||||
@@ -1015,7 +1080,7 @@ bool PrintViewManagerBase::SetupNewPrintJob(
|
||||
@@ -1015,7 +1086,7 @@ bool PrintViewManagerBase::SetupNewPrintJob(
|
||||
#endif
|
||||
print_job_->AddObserver(*this);
|
||||
|
||||
@@ -435,7 +441,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1073,7 +1138,7 @@ void PrintViewManagerBase::ReleasePrintJob() {
|
||||
@@ -1073,7 +1144,7 @@ void PrintViewManagerBase::ReleasePrintJob() {
|
||||
// Ensure that any residual registration of printing client is released.
|
||||
// This might be necessary in some abnormal cases, such as the associated
|
||||
// render process having terminated.
|
||||
@@ -444,7 +450,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
if (!analyzing_content_) {
|
||||
UnregisterSystemPrintClient();
|
||||
}
|
||||
@@ -1083,6 +1148,11 @@ void PrintViewManagerBase::ReleasePrintJob() {
|
||||
@@ -1083,6 +1154,11 @@ void PrintViewManagerBase::ReleasePrintJob() {
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -456,7 +462,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
if (!print_job_)
|
||||
return;
|
||||
|
||||
@@ -1090,7 +1160,7 @@ void PrintViewManagerBase::ReleasePrintJob() {
|
||||
@@ -1090,7 +1166,7 @@ void PrintViewManagerBase::ReleasePrintJob() {
|
||||
// printing_rfh_ should only ever point to a RenderFrameHost with a live
|
||||
// RenderFrame.
|
||||
DCHECK(rfh->IsRenderFrameLive());
|
||||
@@ -465,7 +471,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
}
|
||||
|
||||
print_job_->RemoveObserver(*this);
|
||||
@@ -1132,7 +1202,7 @@ bool PrintViewManagerBase::RunInnerMessageLoop() {
|
||||
@@ -1132,7 +1208,7 @@ bool PrintViewManagerBase::RunInnerMessageLoop() {
|
||||
}
|
||||
|
||||
bool PrintViewManagerBase::OpportunisticallyCreatePrintJob(int cookie) {
|
||||
@@ -474,7 +480,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
return true;
|
||||
|
||||
if (!cookie) {
|
||||
@@ -1155,7 +1225,7 @@ bool PrintViewManagerBase::OpportunisticallyCreatePrintJob(int cookie) {
|
||||
@@ -1155,7 +1231,7 @@ bool PrintViewManagerBase::OpportunisticallyCreatePrintJob(int cookie) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -483,7 +489,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
// Don't start printing if enterprise checks are being performed to check if
|
||||
// printing is allowed, or if content analysis is going to take place right
|
||||
// before starting `print_job_`.
|
||||
@@ -1286,6 +1356,8 @@ void PrintViewManagerBase::CompleteScriptedPrint(
|
||||
@@ -1286,6 +1362,8 @@ void PrintViewManagerBase::CompleteScriptedPrint(
|
||||
auto callback_wrapper = base::BindOnce(
|
||||
&PrintViewManagerBase::ScriptedPrintReply, weak_ptr_factory_.GetWeakPtr(),
|
||||
std::move(callback), render_process_host->GetDeprecatedID());
|
||||
@@ -492,7 +498,7 @@ index aa79c324af2cec50019bca3bccff5d420fb30ffd..455095a2cd63eabe4f267747070b443f
|
||||
std::unique_ptr<PrinterQuery> printer_query =
|
||||
queue()->PopPrinterQuery(params->cookie);
|
||||
if (!printer_query)
|
||||
@@ -1296,10 +1368,10 @@ void PrintViewManagerBase::CompleteScriptedPrint(
|
||||
@@ -1296,10 +1374,10 @@ void PrintViewManagerBase::CompleteScriptedPrint(
|
||||
params->expected_pages_count, params->has_selection, params->margin_type,
|
||||
params->is_scripted, !render_process_host->IsPdf(),
|
||||
base::BindOnce(&OnDidScriptedPrint, queue_, std::move(printer_query),
|
||||
@@ -614,7 +620,7 @@ index 2a477e820d9f0126a05f86cd44f02c2189275bad..a2e9442ff9f5acf8e301f457b1806251
|
||||
|
||||
#if BUILDFLAG(IS_CHROMEOS)
|
||||
diff --git a/chrome/browser/printing/printer_query_oop.cc b/chrome/browser/printing/printer_query_oop.cc
|
||||
index dc2a15ab4d784b0b6c85b84a30c3c08a17ed8e3d..8facb5981cc421cad6bce71dfa8985b0a3270405 100644
|
||||
index dc2a15ab4d784b0b6c85b84a30c3c08a17ed8e3d..e197026e8a7f132c1bf90a0f5f1eabb4f5f064ee 100644
|
||||
--- a/chrome/browser/printing/printer_query_oop.cc
|
||||
+++ b/chrome/browser/printing/printer_query_oop.cc
|
||||
@@ -126,7 +126,7 @@ void PrinterQueryOop::OnDidAskUserForSettings(
|
||||
@@ -626,6 +632,28 @@ index dc2a15ab4d784b0b6c85b84a30c3c08a17ed8e3d..8facb5981cc421cad6bce71dfa8985b0
|
||||
// Want the same PrintBackend service as the query so that we use the same
|
||||
// device context.
|
||||
print_document_client_id_ =
|
||||
@@ -189,6 +189,21 @@ void PrinterQueryOop::GetSettingsWithUI(uint32_t document_page_count,
|
||||
// browser process.
|
||||
// - Other platforms don't have a system print UI or do not use OOP
|
||||
// printing, so this does not matter.
|
||||
+
|
||||
+ // Apply cached settings to the local printing context so that the in-browser
|
||||
+ // system print dialog is prefilled with user-specified options (e.g. copies,
|
||||
+ // collate, duplex). OOP UpdatePrintSettings only applies settings to the
|
||||
+ // remote service context, not the local one used by the native dialog.
|
||||
+ if (settings().dpi()) {
|
||||
+ printing_context()->SetPrintSettings(settings());
|
||||
+ printing_context()->UpdatePrinterSettings(PrintingContext::PrinterSettings{
|
||||
+#if BUILDFLAG(IS_MAC)
|
||||
+ .external_preview = false,
|
||||
+#endif
|
||||
+ .show_system_dialog = false,
|
||||
+ });
|
||||
+ }
|
||||
+
|
||||
PrinterQuery::GetSettingsWithUI(
|
||||
document_page_count, has_selection, is_scripted,
|
||||
base::BindOnce(&PrinterQueryOop::OnDidAskUserForSettings,
|
||||
diff --git a/components/printing/browser/print_manager.cc b/components/printing/browser/print_manager.cc
|
||||
index 21c81377d32ae8d4185598a7eba88ed1d2063ef0..0767f4e9369e926b1cea99178c1a1975941f1765 100644
|
||||
--- a/components/printing/browser/print_manager.cc
|
||||
@@ -675,7 +703,7 @@ index ac2f719be566020d9f41364560c12e6d6d0fe3d8..16d758a6936f66148a196761cfb875f6
|
||||
PrintingFailed(int32 cookie, PrintFailureReason reason);
|
||||
|
||||
diff --git a/components/printing/renderer/print_render_frame_helper.cc b/components/printing/renderer/print_render_frame_helper.cc
|
||||
index 60b5e83a8bc1ed07970be4cdfdc19962698bd754..1320f3b10b07b2cee90f39f406604176c7575796 100644
|
||||
index 60b5e83a8bc1ed07970be4cdfdc19962698bd754..dd83b6cfb6e3f916e60f50402014cd931a4d8850 100644
|
||||
--- a/components/printing/renderer/print_render_frame_helper.cc
|
||||
+++ b/components/printing/renderer/print_render_frame_helper.cc
|
||||
@@ -54,6 +54,7 @@
|
||||
@@ -799,7 +827,7 @@ index 60b5e83a8bc1ed07970be4cdfdc19962698bd754..1320f3b10b07b2cee90f39f406604176
|
||||
// Check if `this` is still valid.
|
||||
if (!self)
|
||||
return;
|
||||
@@ -2394,29 +2415,43 @@ void PrintRenderFrameHelper::IPCProcessed() {
|
||||
@@ -2394,29 +2415,47 @@ void PrintRenderFrameHelper::IPCProcessed() {
|
||||
}
|
||||
|
||||
bool PrintRenderFrameHelper::InitPrintSettings(blink::WebLocalFrame* frame,
|
||||
@@ -835,8 +863,12 @@ index 60b5e83a8bc1ed07970be4cdfdc19962698bd754..1320f3b10b07b2cee90f39f406604176
|
||||
- : mojom::PrintScalingOption::kSourceSize;
|
||||
- RecordDebugEvent(settings.params->printed_doc_type ==
|
||||
+ bool silent = new_settings.FindBool("silent").value_or(false);
|
||||
+ if (silent) {
|
||||
+ settings->params->print_scaling_option = mojom::PrintScalingOption::kFitToPrintableArea;
|
||||
+ int margins_type = new_settings.FindInt(kSettingMarginsType)
|
||||
+ .value_or(static_cast<int>(mojom::MarginType::kDefaultMargins));
|
||||
+ if (silent &&
|
||||
+ margins_type == static_cast<int>(mojom::MarginType::kDefaultMargins)) {
|
||||
+ settings->params->print_scaling_option =
|
||||
+ mojom::PrintScalingOption::kFitToPrintableArea;
|
||||
+ } else {
|
||||
+ settings->params->print_scaling_option =
|
||||
+ center_on_paper ? mojom::PrintScalingOption::kCenterShrinkToFitPaper
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
set -euo pipefail
|
||||
|
||||
export XDG_SESSION_TYPE=wayland
|
||||
# On a Wayland desktop, the tests will use your active display and compositor.
|
||||
# To run headlessly in weston like in CI, set WAYLAND_DISPLAY=wayland-99.
|
||||
export WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-99}"
|
||||
|
||||
if [[ -z "${XDG_RUNTIME_DIR:-}" ]]; then
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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, '..');
|
||||
@@ -16,52 +15,24 @@ 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 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 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 webpackTargetsWithDeps = await Promise.all(webpackTargets.map(async webpackTarget => {
|
||||
const tmpDir = await fs.promises.mkdtemp(path.resolve(os.tmpdir(), 'electron-filenames-'));
|
||||
const targetsWithDeps = await Promise.all(bundleTargets.map(async bundleTarget => {
|
||||
const child = cp.spawn('node', [
|
||||
'./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'
|
||||
'./build/esbuild/bundle.js',
|
||||
'--config', `./build/esbuild/configs/${bundleTarget.config}`,
|
||||
'--print-graph'
|
||||
], {
|
||||
cwd: path.resolve(__dirname, '..')
|
||||
cwd: rootPath
|
||||
});
|
||||
let output = '';
|
||||
child.stdout.on('data', chunk => {
|
||||
@@ -71,32 +42,33 @@ 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 webpack dependencies for entry: ${webpackTarget.name}`));
|
||||
return reject(new Error(`Failed to list bundle dependencies for entry: ${bundleTarget.name}`));
|
||||
}
|
||||
|
||||
resolve();
|
||||
}));
|
||||
|
||||
const webpackTargetWithDeps = {
|
||||
...webpackTarget,
|
||||
return {
|
||||
...bundleTarget,
|
||||
dependencies: (JSON.parse(output) as string[])
|
||||
// Remove whitespace
|
||||
.map(line => line.trim())
|
||||
// Get the relative path
|
||||
.map(line => path.relative(rootPath, line).replace(/\\/g, '/'))
|
||||
// Only care about files in //electron
|
||||
.map(line => path.relative(rootPath, path.resolve(rootPath, line)).replace(/\\/g, '/'))
|
||||
.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
|
||||
@@ -105,7 +77,11 @@ auto_filenames = {
|
||||
${allDocs.map(doc => ` "${doc}",`).join('\n')}
|
||||
]
|
||||
|
||||
${webpackTargetsWithDeps.map(target => ` ${target.name} = [
|
||||
typecheck_sources = [
|
||||
${typecheckSources.map(src => ` "${src}",`).join('\n')}
|
||||
]
|
||||
|
||||
${targetsWithDeps.map(target => ` ${target.name} = [
|
||||
${target.dependencies.map(dep => ` "${dep}",`).join('\n')}
|
||||
]`).join('\n\n')}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Octokit } from '@octokit/rest';
|
||||
|
||||
import * as assert from 'node:assert';
|
||||
import { strict as assert } from 'node:assert';
|
||||
|
||||
import { createGitHubTokenStrategy } from './github-token';
|
||||
import { ELECTRON_ORG, ELECTRON_REPO } from './types';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as minimist from 'minimist';
|
||||
import * as streamChain from 'stream-chain';
|
||||
import * as streamJson from 'stream-json';
|
||||
import minimist = require('minimist');
|
||||
import streamChain = require('stream-chain');
|
||||
import streamJson = require('stream-json');
|
||||
import { ignore as streamJsonIgnore } from 'stream-json/filters/Ignore';
|
||||
import { streamArray as streamJsonStreamArray } from 'stream-json/streamers/StreamArray';
|
||||
|
||||
|
||||
58
script/typecheck.js
Normal file
58
script/typecheck.js
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/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, '');
|
||||
@@ -1,4 +1,5 @@
|
||||
spec/parse-features-string-spec.ts
|
||||
spec/types-spec.ts
|
||||
spec/version-bump-spec.ts
|
||||
spec/api-app-spec.ts
|
||||
spec/api-app-spec.ts
|
||||
spec/api-browser-window-spec.ts
|
||||
@@ -264,11 +264,6 @@ void BrowserWindow::BlurWebView() {
|
||||
web_contents()->GetRenderViewHost()->GetWidget()->Blur();
|
||||
}
|
||||
|
||||
bool BrowserWindow::IsWebViewFocused() {
|
||||
auto* host_view = web_contents()->GetRenderViewHost()->GetWidget()->GetView();
|
||||
return host_view && host_view->HasFocus();
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> BrowserWindow::GetWebContents(v8::Isolate* isolate) {
|
||||
if (web_contents_.IsEmpty())
|
||||
return v8::Null(isolate);
|
||||
@@ -332,7 +327,6 @@ void BrowserWindow::BuildPrototype(v8::Isolate* isolate,
|
||||
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
|
||||
.SetMethod("focusOnWebView", &BrowserWindow::FocusOnWebView)
|
||||
.SetMethod("blurWebView", &BrowserWindow::BlurWebView)
|
||||
.SetMethod("isWebViewFocused", &BrowserWindow::IsWebViewFocused)
|
||||
.SetProperty("webContents", &BrowserWindow::GetWebContents);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,6 @@ class BrowserWindow : public BaseWindow,
|
||||
// BrowserWindow APIs.
|
||||
void FocusOnWebView();
|
||||
void BlurWebView();
|
||||
bool IsWebViewFocused();
|
||||
v8::Local<v8::Value> GetWebContents(v8::Isolate* isolate);
|
||||
|
||||
private:
|
||||
|
||||
@@ -220,12 +220,6 @@ download::DownloadItem::DownloadState DownloadItem::GetState() const {
|
||||
return download_item_->GetState();
|
||||
}
|
||||
|
||||
bool DownloadItem::IsDone() const {
|
||||
if (!CheckAlive())
|
||||
return false;
|
||||
return download_item_->IsDone();
|
||||
}
|
||||
|
||||
void DownloadItem::SetSavePath(const base::FilePath& path) {
|
||||
save_path_ = path;
|
||||
}
|
||||
@@ -289,7 +283,6 @@ gin::ObjectTemplateBuilder DownloadItem::GetObjectTemplateBuilder(
|
||||
.SetMethod("getURL", &DownloadItem::GetURL)
|
||||
.SetMethod("getURLChain", &DownloadItem::GetURLChain)
|
||||
.SetMethod("getState", &DownloadItem::GetState)
|
||||
.SetMethod("isDone", &DownloadItem::IsDone)
|
||||
.SetMethod("setSavePath", &DownloadItem::SetSavePath)
|
||||
.SetMethod("getSavePath", &DownloadItem::GetSavePath)
|
||||
.SetProperty("savePath", &DownloadItem::GetSavePath,
|
||||
|
||||
@@ -78,7 +78,6 @@ class DownloadItem final : public gin_helper::DeprecatedWrappable<DownloadItem>,
|
||||
const GURL& GetURL() const;
|
||||
v8::Local<v8::Value> GetURLChain() const;
|
||||
download::DownloadItem::DownloadState GetState() const;
|
||||
bool IsDone() const;
|
||||
void SetSaveDialogOptions(const file_dialog::DialogSettings& options);
|
||||
std::string GetLastModifiedTime() const;
|
||||
std::string GetETag() const;
|
||||
|
||||
@@ -264,22 +264,6 @@ int Menu::GetItemCount() const {
|
||||
return model_->GetItemCount();
|
||||
}
|
||||
|
||||
int Menu::GetCommandIdAt(int index) const {
|
||||
return model_->GetCommandIdAt(index);
|
||||
}
|
||||
|
||||
std::u16string Menu::GetLabelAt(int index) const {
|
||||
return model_->GetLabelAt(index);
|
||||
}
|
||||
|
||||
std::u16string Menu::GetSublabelAt(int index) const {
|
||||
return model_->GetSecondaryLabelAt(index);
|
||||
}
|
||||
|
||||
std::u16string Menu::GetToolTipAt(int index) const {
|
||||
return model_->GetToolTipAt(index);
|
||||
}
|
||||
|
||||
std::u16string Menu::GetAcceleratorTextAtForTesting(int index) const {
|
||||
ui::Accelerator accelerator;
|
||||
model_->GetAcceleratorAtWithParams(index, true, &accelerator);
|
||||
@@ -298,10 +282,6 @@ bool Menu::IsVisibleAt(int index) const {
|
||||
return model_->IsVisibleAt(index);
|
||||
}
|
||||
|
||||
bool Menu::WorksWhenHiddenAt(int index) const {
|
||||
return model_->WorksWhenHiddenAt(index);
|
||||
}
|
||||
|
||||
void Menu::OnMenuWillClose() {
|
||||
keep_alive_.Clear();
|
||||
Emit("menu-will-close");
|
||||
@@ -325,15 +305,9 @@ void Menu::FillObjectTemplate(v8::Isolate* isolate,
|
||||
.SetMethod("setRole", &Menu::SetRole)
|
||||
.SetMethod("setCustomType", &Menu::SetCustomType)
|
||||
.SetMethod("clear", &Menu::Clear)
|
||||
.SetMethod("getIndexOfCommandId", &Menu::GetIndexOfCommandId)
|
||||
.SetMethod("getItemCount", &Menu::GetItemCount)
|
||||
.SetMethod("getCommandIdAt", &Menu::GetCommandIdAt)
|
||||
.SetMethod("getLabelAt", &Menu::GetLabelAt)
|
||||
.SetMethod("getSublabelAt", &Menu::GetSublabelAt)
|
||||
.SetMethod("getToolTipAt", &Menu::GetToolTipAt)
|
||||
.SetMethod("isItemCheckedAt", &Menu::IsItemCheckedAt)
|
||||
.SetMethod("isEnabledAt", &Menu::IsEnabledAt)
|
||||
.SetMethod("worksWhenHiddenAt", &Menu::WorksWhenHiddenAt)
|
||||
.SetMethod("isVisibleAt", &Menu::IsVisibleAt)
|
||||
.SetMethod("popupAt", &Menu::PopupAt)
|
||||
.SetMethod("closePopupAt", &Menu::ClosePopupAt)
|
||||
|
||||
@@ -131,14 +131,9 @@ class Menu : public gin::Wrappable<Menu>,
|
||||
void Clear();
|
||||
int GetIndexOfCommandId(int command_id) const;
|
||||
int GetItemCount() const;
|
||||
int GetCommandIdAt(int index) const;
|
||||
std::u16string GetLabelAt(int index) const;
|
||||
std::u16string GetSublabelAt(int index) const;
|
||||
std::u16string GetToolTipAt(int index) const;
|
||||
bool IsItemCheckedAt(int index) const;
|
||||
bool IsEnabledAt(int index) const;
|
||||
bool IsVisibleAt(int index) const;
|
||||
bool WorksWhenHiddenAt(int index) const;
|
||||
|
||||
gin_helper::SelfKeepAlive<Menu> keep_alive_{this};
|
||||
};
|
||||
|
||||
@@ -163,8 +163,6 @@ class ServiceWorkerMain final
|
||||
|
||||
raw_ptr<content::ServiceWorkerContext> service_worker_context_;
|
||||
mojo::AssociatedRemote<mojom::ElectronRenderer> remote_;
|
||||
|
||||
std::unique_ptr<gin_helper::Promise<void>> start_worker_promise_;
|
||||
};
|
||||
|
||||
} // namespace electron::api
|
||||
|
||||
@@ -2450,16 +2450,9 @@ int32_t WebContents::GetProcessID() const {
|
||||
}
|
||||
|
||||
base::ProcessId WebContents::GetOSProcessID() const {
|
||||
base::ProcessHandle process_handle = web_contents()
|
||||
->GetPrimaryMainFrame()
|
||||
->GetProcess()
|
||||
->GetProcess()
|
||||
.Handle();
|
||||
return base::GetProcId(process_handle);
|
||||
}
|
||||
|
||||
bool WebContents::Equal(const WebContents* web_contents) const {
|
||||
return ID() == web_contents->ID();
|
||||
const auto& process =
|
||||
web_contents()->GetPrimaryMainFrame()->GetProcess()->GetProcess();
|
||||
return process.IsValid() ? process.Pid() : base::kNullProcessId;
|
||||
}
|
||||
|
||||
GURL WebContents::GetURL() const {
|
||||
@@ -4588,7 +4581,6 @@ void WebContents::FillObjectTemplate(v8::Isolate* isolate,
|
||||
&WebContents::SetBackgroundThrottling)
|
||||
.SetMethod("getProcessId", &WebContents::GetProcessID)
|
||||
.SetMethod("getOSProcessId", &WebContents::GetOSProcessID)
|
||||
.SetMethod("equal", &WebContents::Equal)
|
||||
.SetMethod("_loadURL", &WebContents::LoadURL)
|
||||
.SetMethod("reload", &WebContents::Reload)
|
||||
.SetMethod("reloadIgnoringCache", &WebContents::ReloadIgnoringCache)
|
||||
|
||||
@@ -195,7 +195,6 @@ class WebContents final : public ExclusiveAccessContext,
|
||||
int32_t GetProcessID() const;
|
||||
base::ProcessId GetOSProcessID() const;
|
||||
[[nodiscard]] Type type() const { return type_; }
|
||||
bool Equal(const WebContents* web_contents) const;
|
||||
void LoadURL(const GURL& url, const gin_helper::Dictionary& options);
|
||||
void Reload();
|
||||
void ReloadIgnoringCache();
|
||||
|
||||
@@ -417,10 +417,13 @@ std::string WebFrameMain::FrameToken() const {
|
||||
|
||||
base::ProcessId WebFrameMain::OSProcessID() const {
|
||||
if (!CheckRenderFrame())
|
||||
return -1;
|
||||
base::ProcessHandle process_handle =
|
||||
render_frame_host()->GetProcess()->GetProcess().Handle();
|
||||
return base::GetProcId(process_handle);
|
||||
return base::kNullProcessId;
|
||||
|
||||
const auto& process = render_frame_host()->GetProcess()->GetProcess();
|
||||
if (!process.IsValid())
|
||||
return base::kNullProcessId;
|
||||
|
||||
return process.Pid();
|
||||
}
|
||||
|
||||
int32_t WebFrameMain::ProcessID() const {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "base/time/time.h"
|
||||
#include "content/public/browser/browser_context.h"
|
||||
#include "extensions/browser/extension_file_task_runner.h"
|
||||
#include "extensions/browser/extension_pref_names.h"
|
||||
#include "extensions/browser/extension_prefs.h"
|
||||
#include "extensions/browser/extension_registry.h"
|
||||
#include "extensions/browser/pref_names.h"
|
||||
@@ -27,6 +28,7 @@
|
||||
#include "extensions/common/error_utils.h"
|
||||
#include "extensions/common/file_util.h"
|
||||
#include "extensions/common/manifest_constants.h"
|
||||
#include "extensions/common/manifest_handlers/background_info.h"
|
||||
|
||||
namespace extensions {
|
||||
|
||||
@@ -143,6 +145,19 @@ void ElectronExtensionLoader::FinishExtensionLoad(
|
||||
std::pair<scoped_refptr<const Extension>, std::string> result) {
|
||||
scoped_refptr<const Extension> extension = result.first;
|
||||
if (extension) {
|
||||
ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(browser_context_);
|
||||
|
||||
if (BackgroundInfo::IsServiceWorkerBased(extension.get())) {
|
||||
// Tell Chromium that it needs to start the extension's service worker.
|
||||
// Chromium usually does this only when an extension is first installed
|
||||
// because Chrome will restart the service worker when the browser
|
||||
// relaunches. In Electron, we make a fresh install on every app start,
|
||||
// so we need to run the fresh install logic again.
|
||||
extension_prefs->UpdateExtensionPref(
|
||||
extension.get()->id(), extensions::kPrefHasStartedServiceWorker,
|
||||
base::Value(false));
|
||||
}
|
||||
|
||||
extension_registrar_->AddExtension(extension);
|
||||
|
||||
// Write extension install time to ExtensionPrefs.
|
||||
@@ -152,7 +167,6 @@ void ElectronExtensionLoader::FinishExtensionLoad(
|
||||
// Implementation for writing the pref was based on
|
||||
// PreferenceAPIBase::SetExtensionControlledPref.
|
||||
{
|
||||
ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(browser_context_);
|
||||
ExtensionPrefs::ScopedDictionaryUpdate update(
|
||||
extension_prefs, extension.get()->id(),
|
||||
extensions::pref_names::kPrefPreferences);
|
||||
|
||||
@@ -697,7 +697,11 @@ void FileSystemAccessPermissionContext::ConfirmSensitiveEntryAccess(
|
||||
content::GlobalRenderFrameHostId frame_id,
|
||||
base::OnceCallback<void(SensitiveEntryResult)> callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
callback_map_.try_emplace(path_info.path, std::move(callback));
|
||||
|
||||
auto [it, inserted] = callback_map_.try_emplace(path_info.path);
|
||||
it->second.push_back(std::move(callback));
|
||||
if (!inserted)
|
||||
return;
|
||||
|
||||
auto after_blocklist_check_callback = base::BindOnce(
|
||||
&FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist,
|
||||
@@ -769,8 +773,11 @@ void FileSystemAccessPermissionContext::PerformAfterWriteChecks(
|
||||
void FileSystemAccessPermissionContext::RunRestrictedPathCallback(
|
||||
const base::FilePath& file_path,
|
||||
SensitiveEntryResult result) {
|
||||
if (auto val = callback_map_.extract(file_path))
|
||||
std::move(val.mapped()).Run(result);
|
||||
if (auto val = callback_map_.extract(file_path)) {
|
||||
for (auto& callback : val.mapped()) {
|
||||
std::move(callback).Run(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::OnRestrictedPathResult(
|
||||
|
||||
@@ -196,7 +196,8 @@ class FileSystemAccessPermissionContext
|
||||
|
||||
std::map<url::Origin, base::DictValue> id_pathinfo_map_;
|
||||
|
||||
std::map<base::FilePath, base::OnceCallback<void(SensitiveEntryResult)>>
|
||||
std::map<base::FilePath,
|
||||
std::vector<base::OnceCallback<void(SensitiveEntryResult)>>>
|
||||
callback_map_;
|
||||
|
||||
std::unique_ptr<ChromeFileSystemAccessPermissionContext::BlockPathRules>
|
||||
|
||||
@@ -4,12 +4,16 @@
|
||||
|
||||
#include "shell/browser/notifications/linux/libnotify_notification.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "base/containers/flat_set.h"
|
||||
#include "base/files/file_enumerator.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/nix/xdg_util.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "base/process/process_handle.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
@@ -50,6 +54,9 @@ bool NotifierSupportsActions() {
|
||||
return HasCapability("actions");
|
||||
}
|
||||
|
||||
using GetActivationTokenFunc = const char* (*)(NotifyNotification*);
|
||||
GetActivationTokenFunc g_get_activation_token = nullptr;
|
||||
|
||||
void log_and_clear_error(GError* error, const char* context) {
|
||||
LOG(ERROR) << context << ": domain=" << error->domain
|
||||
<< " code=" << error->code << " message=\"" << error->message
|
||||
@@ -61,18 +68,40 @@ void log_and_clear_error(GError* error, const char* context) {
|
||||
|
||||
// static
|
||||
bool LibnotifyNotification::Initialize() {
|
||||
if (!GetLibNotifyLoader().Load("libnotify.so.4") && // most common one
|
||||
!GetLibNotifyLoader().Load("libnotify.so.5") &&
|
||||
!GetLibNotifyLoader().Load("libnotify.so.1") &&
|
||||
!GetLibNotifyLoader().Load("libnotify.so")) {
|
||||
constexpr std::array kLibnotifySonames = {
|
||||
"libnotify.so.4",
|
||||
"libnotify.so.5",
|
||||
"libnotify.so.1",
|
||||
"libnotify.so",
|
||||
};
|
||||
|
||||
const char* loaded_soname = nullptr;
|
||||
for (const char* soname : kLibnotifySonames) {
|
||||
if (GetLibNotifyLoader().Load(soname)) {
|
||||
loaded_soname = soname;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!loaded_soname) {
|
||||
LOG(WARNING) << "Unable to find libnotify; notifications disabled";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GetLibNotifyLoader().notify_is_initted() &&
|
||||
!GetLibNotifyLoader().notify_init(GetApplicationName().c_str())) {
|
||||
LOG(WARNING) << "Unable to initialize libnotify; notifications disabled";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Safe to cache the symbol after dlclose(handle) because libnotify remains
|
||||
// loaded via GetLibNotifyLoader() for the process lifetime.
|
||||
if (void* handle = dlopen(loaded_soname, RTLD_LAZY)) {
|
||||
g_get_activation_token = reinterpret_cast<GetActivationTokenFunc>(
|
||||
dlsym(handle, "notify_notification_get_activation_token"));
|
||||
dlclose(handle);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -192,6 +221,14 @@ void LibnotifyNotification::OnNotificationView(NotifyNotification* notification,
|
||||
gpointer user_data) {
|
||||
LibnotifyNotification* that = static_cast<LibnotifyNotification*>(user_data);
|
||||
DCHECK(that);
|
||||
|
||||
if (g_get_activation_token) {
|
||||
const char* token = g_get_activation_token(notification);
|
||||
if (token && *token) {
|
||||
base::nix::SetActivationToken(std::string(token));
|
||||
}
|
||||
}
|
||||
|
||||
that->NotificationClicked();
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "base/base64.h"
|
||||
#include "base/containers/fixed_flat_set.h"
|
||||
#include "base/containers/span.h"
|
||||
#include "base/dcheck_is_on.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
@@ -160,6 +161,13 @@ void OnOpenItemComplete(const base::FilePath& path, const std::string& result) {
|
||||
constexpr base::TimeDelta kInitialBackoffDelay = base::Milliseconds(250);
|
||||
constexpr base::TimeDelta kMaxBackoffDelay = base::Seconds(10);
|
||||
|
||||
constexpr auto kValidDockStates = base::MakeFixedFlatSet<std::string_view>(
|
||||
{"bottom", "left", "right", "undocked"});
|
||||
|
||||
bool IsValidDockState(const std::string& state) {
|
||||
return kValidDockStates.contains(state);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class InspectableWebContents::NetworkResourceLoader
|
||||
@@ -394,7 +402,7 @@ void InspectableWebContents::SetDockState(const std::string& state) {
|
||||
can_dock_ = false;
|
||||
} else {
|
||||
can_dock_ = true;
|
||||
dock_state_ = state;
|
||||
dock_state_ = IsValidDockState(state) ? state : "right";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,7 +567,13 @@ void InspectableWebContents::LoadCompleted() {
|
||||
pref_service_->GetDict(kDevToolsPreferences);
|
||||
const std::string* current_dock_state =
|
||||
prefs.FindString("currentDockState");
|
||||
base::RemoveChars(*current_dock_state, "\"", &dock_state_);
|
||||
if (current_dock_state) {
|
||||
std::string sanitized;
|
||||
base::RemoveChars(*current_dock_state, "\"", &sanitized);
|
||||
dock_state_ = IsValidDockState(sanitized) ? sanitized : "right";
|
||||
} else {
|
||||
dock_state_ = "right";
|
||||
}
|
||||
}
|
||||
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
|
||||
auto* api_web_contents = api::WebContents::From(GetWebContents());
|
||||
|
||||
@@ -163,7 +163,7 @@ int ClientFrameViewLinux::ResizingBorderHitTest(const gfx::Point& point) {
|
||||
gfx::Rect ClientFrameViewLinux::GetBoundsForClientView() const {
|
||||
gfx::Rect client_bounds = bounds();
|
||||
if (!frame_->IsFullscreen()) {
|
||||
client_bounds.Inset(RestoredFrameBorderInsets());
|
||||
client_bounds.Inset(linux_frame_layout_->FrameBorderInsets(false));
|
||||
client_bounds.Inset(
|
||||
gfx::Insets::TLBR(GetTitlebarBounds().height(), 0, 0, 0));
|
||||
}
|
||||
@@ -236,6 +236,21 @@ void ClientFrameViewLinux::Layout(PassKey) {
|
||||
}
|
||||
|
||||
void ClientFrameViewLinux::OnPaint(gfx::Canvas* canvas) {
|
||||
if (frame_->IsFullscreen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame_->IsMaximized()) {
|
||||
// Some GTK themes (Breeze) still render shadow/border assets when
|
||||
// maximized, and we don't need a border when maximized anyway. Chromium
|
||||
// switches on this too: OpaqueBrowserFrameView::PaintMaximizedFrameBorder.
|
||||
PaintMaximizedFrameBorder(canvas);
|
||||
} else {
|
||||
PaintRestoredFrameBorder(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientFrameViewLinux::PaintRestoredFrameBorder(gfx::Canvas* canvas) {
|
||||
if (auto* frame_provider = linux_frame_layout_->GetFrameProvider()) {
|
||||
frame_provider->PaintWindowFrame(
|
||||
canvas, GetLocalBounds(), GetTitlebarBounds().bottom(),
|
||||
@@ -243,6 +258,18 @@ void ClientFrameViewLinux::OnPaint(gfx::Canvas* canvas) {
|
||||
}
|
||||
}
|
||||
|
||||
void ClientFrameViewLinux::PaintMaximizedFrameBorder(gfx::Canvas* canvas) {
|
||||
ui::NativeTheme::FrameTopAreaExtraParams frame_top_area;
|
||||
frame_top_area.use_custom_frame = true;
|
||||
frame_top_area.is_active = ShouldPaintAsActive();
|
||||
frame_top_area.default_background_color = SK_ColorTRANSPARENT;
|
||||
ui::NativeTheme::ExtraParams params(frame_top_area);
|
||||
GetNativeTheme()->Paint(
|
||||
canvas->sk_canvas(), GetColorProvider(), ui::NativeTheme::kFrameTopArea,
|
||||
ui::NativeTheme::kNormal,
|
||||
gfx::Rect(0, 0, width(), GetTitlebarBounds().bottom()), params);
|
||||
}
|
||||
|
||||
void ClientFrameViewLinux::PaintAsActiveChanged() {
|
||||
UpdateThemeValues();
|
||||
}
|
||||
@@ -251,23 +278,15 @@ void ClientFrameViewLinux::UpdateThemeValues() {
|
||||
gtk::GtkCssContext window_context =
|
||||
gtk::AppendCssNodeToStyleContext({}, "window.background.csd");
|
||||
gtk::GtkCssContext headerbar_context = gtk::AppendCssNodeToStyleContext(
|
||||
{}, "headerbar.default-decoration.titlebar");
|
||||
window_context, "headerbar.default-decoration.titlebar");
|
||||
gtk::GtkCssContext title_context =
|
||||
gtk::AppendCssNodeToStyleContext(headerbar_context, "label.title");
|
||||
gtk::GtkCssContext button_context = gtk::AppendCssNodeToStyleContext(
|
||||
headerbar_context, "button.image-button");
|
||||
|
||||
gtk_style_context_set_parent(headerbar_context, window_context);
|
||||
gtk_style_context_set_parent(title_context, headerbar_context);
|
||||
gtk_style_context_set_parent(button_context, headerbar_context);
|
||||
|
||||
// ShouldPaintAsActive asks the widget, so assume active if the widget is not
|
||||
// set yet.
|
||||
if (GetWidget() != nullptr && !ShouldPaintAsActive()) {
|
||||
gtk_style_context_set_state(window_context, GTK_STATE_FLAG_BACKDROP);
|
||||
gtk_style_context_set_state(headerbar_context, GTK_STATE_FLAG_BACKDROP);
|
||||
gtk_style_context_set_state(title_context, GTK_STATE_FLAG_BACKDROP);
|
||||
gtk_style_context_set_state(button_context, GTK_STATE_FLAG_BACKDROP);
|
||||
}
|
||||
|
||||
theme_values_.window_border_radius =
|
||||
@@ -281,10 +300,6 @@ void ClientFrameViewLinux::UpdateThemeValues() {
|
||||
theme_values_.title_color = gtk::GtkStyleContextGetColor(title_context);
|
||||
theme_values_.title_padding = gtk::GtkStyleContextGetPadding(title_context);
|
||||
|
||||
gtk::GtkStyleContextGet(button_context, "min-height",
|
||||
&theme_values_.button_min_size, nullptr);
|
||||
theme_values_.button_padding = gtk::GtkStyleContextGetPadding(button_context);
|
||||
|
||||
title_->SetEnabledColor(theme_values_.title_color);
|
||||
|
||||
InvalidateLayout();
|
||||
@@ -299,8 +314,9 @@ ClientFrameViewLinux::GetButtonTypeToSkip() const {
|
||||
}
|
||||
|
||||
void ClientFrameViewLinux::UpdateButtonImages() {
|
||||
nav_button_provider_->RedrawImages(theme_values_.button_min_size,
|
||||
frame_->IsMaximized(),
|
||||
int top_area_height = theme_values_.titlebar_min_height +
|
||||
theme_values_.titlebar_padding.height();
|
||||
nav_button_provider_->RedrawImages(top_area_height, frame_->IsMaximized(),
|
||||
ShouldPaintAsActive());
|
||||
|
||||
ui::NavButtonProvider::FrameButtonDisplayType skip_type =
|
||||
@@ -368,7 +384,14 @@ void ClientFrameViewLinux::LayoutButtonsOnSide(
|
||||
|
||||
button->button->SetVisible(true);
|
||||
|
||||
int button_width = theme_values_.button_min_size;
|
||||
// CSS min-size/height/width is not enough to determine the actual size of
|
||||
// the buttons, so we sample the rendered image. See Chromium's
|
||||
// BrowserFrameViewLinuxNative::MaybeUpdateCachedFrameButtonImages.
|
||||
int button_width =
|
||||
nav_button_provider_
|
||||
->GetImage(button->type,
|
||||
ui::NavButtonProvider::ButtonState::kNormal)
|
||||
.width();
|
||||
int next_button_offset =
|
||||
button_width + nav_button_provider_->GetInterNavButtonSpacing();
|
||||
|
||||
@@ -404,7 +427,7 @@ gfx::Rect ClientFrameViewLinux::GetTitlebarBounds() const {
|
||||
std::max(font_height, theme_values_.titlebar_min_height) +
|
||||
GetTitlebarContentInsets().height();
|
||||
|
||||
gfx::Insets decoration_insets = RestoredFrameBorderInsets();
|
||||
gfx::Insets decoration_insets = linux_frame_layout_->FrameBorderInsets(false);
|
||||
|
||||
// We add the inset height here, so the .Inset() that follows won't reduce it
|
||||
// to be too small.
|
||||
|
||||
@@ -91,12 +91,11 @@ class ClientFrameViewLinux : public FramelessView,
|
||||
|
||||
SkColor title_color;
|
||||
gfx::Insets title_padding;
|
||||
|
||||
int button_min_size;
|
||||
gfx::Insets button_padding;
|
||||
};
|
||||
|
||||
void PaintAsActiveChanged();
|
||||
void PaintRestoredFrameBorder(gfx::Canvas* canvas);
|
||||
void PaintMaximizedFrameBorder(gfx::Canvas* canvas);
|
||||
|
||||
void UpdateThemeValues();
|
||||
|
||||
|
||||
@@ -83,6 +83,12 @@ gfx::Insets LinuxFrameLayout::RestoredFrameBorderInsets() const {
|
||||
return gfx::Insets();
|
||||
}
|
||||
|
||||
gfx::Insets LinuxFrameLayout::FrameBorderInsets(bool restored) const {
|
||||
return !restored && (window_->IsMaximized() || window_->IsFullscreen())
|
||||
? gfx::Insets()
|
||||
: RestoredFrameBorderInsets();
|
||||
}
|
||||
|
||||
gfx::Insets LinuxFrameLayout::GetInputInsets() const {
|
||||
return gfx::Insets(kResizeInsideBoundsSize);
|
||||
}
|
||||
@@ -106,7 +112,7 @@ void LinuxFrameLayout::set_tiled(bool tiled) {
|
||||
|
||||
gfx::Rect LinuxFrameLayout::GetWindowBounds() const {
|
||||
gfx::Rect bounds = window_->widget()->GetWindowBoundsInScreen();
|
||||
bounds.Inset(RestoredFrameBorderInsets());
|
||||
bounds.Inset(FrameBorderInsets(false));
|
||||
return bounds;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,9 @@ class LinuxFrameLayout {
|
||||
CSDStyle csd_style);
|
||||
|
||||
// Insets from the transparent widget border to the opaque part of the window.
|
||||
// Returns empty insets when maximized or fullscreen unless |restored| is
|
||||
// true. Matches Chromium's OpaqueBrowserFrameViewLayout::FrameBorderInsets.
|
||||
gfx::Insets FrameBorderInsets(bool restored) const;
|
||||
virtual gfx::Insets RestoredFrameBorderInsets() const;
|
||||
// Insets for parts of the surface that should be counted for user input.
|
||||
virtual gfx::Insets GetInputInsets() const;
|
||||
|
||||
@@ -210,7 +210,7 @@ void OpaqueFrameView::OnPaint(gfx::Canvas* canvas) {
|
||||
return;
|
||||
|
||||
const bool active = ShouldPaintAsActive();
|
||||
const gfx::Insets border = RestoredFrameBorderInsets();
|
||||
const gfx::Insets border = FrameBorderInsets(false);
|
||||
const bool showing_shadow = linux_frame_layout_->IsShowingShadow();
|
||||
gfx::RectF bounds_dip(GetLocalBounds());
|
||||
if (showing_shadow) {
|
||||
@@ -342,9 +342,7 @@ views::Button* OpaqueFrameView::CreateButton(
|
||||
}
|
||||
|
||||
gfx::Insets OpaqueFrameView::FrameBorderInsets(bool restored) const {
|
||||
return !restored && IsFrameCondensed()
|
||||
? gfx::Insets()
|
||||
: linux_frame_layout_->RestoredFrameBorderInsets();
|
||||
return linux_frame_layout_->FrameBorderInsets(restored);
|
||||
}
|
||||
|
||||
int OpaqueFrameView::FrameTopBorderThickness(bool restored) const {
|
||||
|
||||
@@ -101,13 +101,13 @@ base::DictValue BuildTargetDescriptor(
|
||||
int process_id,
|
||||
int routing_id,
|
||||
ui::AXMode accessibility_mode,
|
||||
base::ProcessHandle handle = base::kNullProcessHandle) {
|
||||
base::ProcessId pid = base::kNullProcessId) {
|
||||
base::DictValue target_data;
|
||||
target_data.Set(kProcessIdField, process_id);
|
||||
target_data.Set(kRoutingIdField, routing_id);
|
||||
target_data.Set(kUrlField, url.spec());
|
||||
target_data.Set(kNameField, base::EscapeForHTML(name));
|
||||
target_data.Set(kPidField, static_cast<int>(base::GetProcId(handle)));
|
||||
target_data.Set(kPidField, static_cast<int>(pid));
|
||||
target_data.Set(kFaviconUrlField, favicon_url.spec());
|
||||
target_data.Set(kAccessibilityModeField,
|
||||
static_cast<int>(accessibility_mode.flags()));
|
||||
@@ -138,9 +138,12 @@ base::DictValue BuildTargetDescriptor(content::RenderViewHost* rvh) {
|
||||
accessibility_mode = web_contents->GetAccessibilityMode();
|
||||
}
|
||||
|
||||
const auto& process = rvh->GetProcess()->GetProcess();
|
||||
const auto pid = process.IsValid() ? process.Pid() : base::kNullProcessId;
|
||||
|
||||
return BuildTargetDescriptor(url, title, favicon_url,
|
||||
rvh->GetProcess()->GetDeprecatedID(),
|
||||
rvh->GetRoutingID(), accessibility_mode);
|
||||
rvh->GetRoutingID(), accessibility_mode, pid);
|
||||
}
|
||||
|
||||
base::DictValue BuildTargetDescriptor(electron::NativeWindow* window) {
|
||||
|
||||
@@ -66,10 +66,6 @@ void SetHiddenValue(v8::Isolate* isolate,
|
||||
object->SetPrivate(context, privateKey, value);
|
||||
}
|
||||
|
||||
int32_t GetObjectHash(v8::Local<v8::Object> object) {
|
||||
return object->GetIdentityHash();
|
||||
}
|
||||
|
||||
void TakeHeapSnapshot(v8::Isolate* isolate) {
|
||||
isolate->GetHeapProfiler()->TakeHeapSnapshot();
|
||||
}
|
||||
@@ -103,7 +99,6 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||
gin_helper::Dictionary dict{isolate, exports};
|
||||
dict.SetMethod("getHiddenValue", &GetHiddenValue);
|
||||
dict.SetMethod("setHiddenValue", &SetHiddenValue);
|
||||
dict.SetMethod("getObjectHash", &GetObjectHash);
|
||||
dict.SetMethod("takeHeapSnapshot", &TakeHeapSnapshot);
|
||||
dict.SetMethod("requestGarbageCollectionForTesting",
|
||||
&RequestGarbageCollectionForTesting);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "shell/common/gin_helper/wrappable.h"
|
||||
|
||||
#include "base/task/sequenced_task_runner.h"
|
||||
#include "gin/object_template_builder.h"
|
||||
#include "gin/public/isolate_holder.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
@@ -90,7 +91,22 @@ void WrappableBase::SecondWeakCallback(
|
||||
if (gin::IsolateHolder::DestroyedMicrotasksRunner()) {
|
||||
return;
|
||||
}
|
||||
delete static_cast<WrappableBase*>(data.GetInternalField(0));
|
||||
// Defer destruction to a posted task. V8's second-pass weak callbacks run
|
||||
// inside a DisallowJavascriptExecutionScope (they may touch the V8 API but
|
||||
// must not invoke JS). Several Electron Wrappables (e.g. WebContents) emit
|
||||
// JS events from their destructors, so deleting synchronously here can
|
||||
// crash with "Invoke in DisallowJavascriptExecutionScope" — see
|
||||
// https://github.com/electron/electron/issues/47420. Posting via the
|
||||
// current sequence's task runner ensures the destructor runs once V8 has
|
||||
// left the GC scope. If no task runner is available (e.g. early/late in
|
||||
// process lifetime), fall back to synchronous deletion.
|
||||
auto* wrappable = static_cast<WrappableBase*>(data.GetInternalField(0));
|
||||
if (base::SequencedTaskRunner::HasCurrentDefault()) {
|
||||
base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
|
||||
wrappable);
|
||||
} else {
|
||||
delete wrappable;
|
||||
}
|
||||
}
|
||||
|
||||
DeprecatedWrappableBase::DeprecatedWrappableBase() = default;
|
||||
@@ -126,9 +142,19 @@ void DeprecatedWrappableBase::SecondWeakCallback(
|
||||
const v8::WeakCallbackInfo<DeprecatedWrappableBase>& data) {
|
||||
if (gin::IsolateHolder::DestroyedMicrotasksRunner())
|
||||
return;
|
||||
// See WrappableBase::SecondWeakCallback for why deletion is posted: V8's
|
||||
// second-pass weak callbacks run inside a DisallowJavascriptExecutionScope,
|
||||
// and several Wrappables emit JS events from their destructors.
|
||||
// https://github.com/electron/electron/issues/47420
|
||||
DeprecatedWrappableBase* wrappable = data.GetParameter();
|
||||
if (wrappable)
|
||||
if (!wrappable)
|
||||
return;
|
||||
if (base::SequencedTaskRunner::HasCurrentDefault()) {
|
||||
base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
|
||||
wrappable);
|
||||
} else {
|
||||
delete wrappable;
|
||||
}
|
||||
}
|
||||
|
||||
v8::MaybeLocal<v8::Object> DeprecatedWrappableBase::GetWrapperImpl(
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define ELECTRON_SHELL_COMMON_GIN_HELPER_WRAPPABLE_BASE_H_
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/task/sequenced_task_runner_helpers.h"
|
||||
#include "v8/include/v8-forward.h"
|
||||
|
||||
namespace gin {
|
||||
@@ -75,6 +76,11 @@ class DeprecatedWrappableBase {
|
||||
DeprecatedWrappableBase();
|
||||
virtual ~DeprecatedWrappableBase();
|
||||
|
||||
// SecondWeakCallback posts destruction via DeleteSoon so that destructors
|
||||
// (which may emit JS events) run outside V8's GC scope. DeleteSoon needs
|
||||
// access to the protected destructor.
|
||||
friend class base::DeleteHelper<DeprecatedWrappableBase>;
|
||||
|
||||
// Overrides of this method should be declared final and not overridden again.
|
||||
virtual gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate);
|
||||
|
||||
@@ -53,7 +53,8 @@ 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 webpack
|
||||
// electron scripts are wrapped in a try {} catch {} by the esbuild bundler
|
||||
// (see build/esbuild/bundle.js applyWrappers).
|
||||
if (try_catch.HasCaught()) {
|
||||
std::string msg = "no error message";
|
||||
if (!try_catch.Message().IsEmpty()) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import * as nodeUrl from 'node:url';
|
||||
import { emittedUntil, emittedNTimes } from './lib/events-helpers';
|
||||
import { randomString } from './lib/net-helpers';
|
||||
import { HexColors, hasCapturableScreen, ScreenCapture } from './lib/screen-helpers';
|
||||
import { ifit, ifdescribe, defer, listen, waitUntil } from './lib/spec-helpers';
|
||||
import { ifit, ifdescribe, defer, listen, waitUntil, isWayland } from './lib/spec-helpers';
|
||||
import { closeWindow, closeAllWindows } from './lib/window-helpers';
|
||||
|
||||
const fixtures = path.resolve(__dirname, 'fixtures');
|
||||
@@ -1204,7 +1204,45 @@ describe('BrowserWindow module', () => {
|
||||
});
|
||||
}
|
||||
|
||||
describe('focus and visibility', () => {
|
||||
describe('visibility', () => {
|
||||
let w: BrowserWindow;
|
||||
beforeEach(() => {
|
||||
w = new BrowserWindow({ show: false });
|
||||
});
|
||||
afterEach(async () => {
|
||||
await closeWindow(w);
|
||||
w = null as unknown as BrowserWindow;
|
||||
});
|
||||
|
||||
describe('BrowserWindow.show()', () => {
|
||||
it('should make the window visible', async () => {
|
||||
const show = once(w, 'show');
|
||||
w.show();
|
||||
await show;
|
||||
expect(w.isVisible()).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BrowserWindow.hide()', () => {
|
||||
it('should make the window not visible', () => {
|
||||
w.show();
|
||||
w.hide();
|
||||
expect(w.isVisible()).to.equal(false);
|
||||
});
|
||||
it('emits when window is hidden', async () => {
|
||||
const shown = once(w, 'show');
|
||||
w.show();
|
||||
await shown;
|
||||
const hidden = once(w, 'hide');
|
||||
w.hide();
|
||||
await hidden;
|
||||
expect(w.isVisible()).to.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Wayland does not allow focus and z-order to be controlled without user input
|
||||
ifdescribe(!isWayland)('focus, blur, and z-order', () => {
|
||||
let w: BrowserWindow;
|
||||
beforeEach(() => {
|
||||
w = new BrowserWindow({ show: false });
|
||||
@@ -1221,18 +1259,12 @@ describe('BrowserWindow module', () => {
|
||||
await p;
|
||||
expect(w.isFocused()).to.equal(true);
|
||||
});
|
||||
it('should make the window visible', async () => {
|
||||
it('emits focus event and makes the window visible', async () => {
|
||||
const p = once(w, 'focus');
|
||||
w.show();
|
||||
await p;
|
||||
expect(w.isVisible()).to.equal(true);
|
||||
});
|
||||
it('emits when window is shown', async () => {
|
||||
const show = once(w, 'show');
|
||||
w.show();
|
||||
await show;
|
||||
expect(w.isVisible()).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BrowserWindow.hide()', () => {
|
||||
@@ -1240,20 +1272,6 @@ describe('BrowserWindow module', () => {
|
||||
w.hide();
|
||||
expect(w.isFocused()).to.equal(false);
|
||||
});
|
||||
it('should make the window not visible', () => {
|
||||
w.show();
|
||||
w.hide();
|
||||
expect(w.isVisible()).to.equal(false);
|
||||
});
|
||||
it('emits when window is hidden', async () => {
|
||||
const shown = once(w, 'show');
|
||||
w.show();
|
||||
await shown;
|
||||
const hidden = once(w, 'hide');
|
||||
w.hide();
|
||||
await hidden;
|
||||
expect(w.isVisible()).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BrowserWindow.minimize()', () => {
|
||||
@@ -1626,6 +1644,20 @@ describe('BrowserWindow module', () => {
|
||||
await closeWindow(w2, { assertNotWindows: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe('window.webContents.focus()', () => {
|
||||
afterEach(closeAllWindows);
|
||||
it('focuses window', async () => {
|
||||
const w1 = new BrowserWindow({ x: 100, y: 300, width: 300, height: 200 });
|
||||
w1.loadURL('about:blank');
|
||||
const w2 = new BrowserWindow({ x: 300, y: 300, width: 300, height: 200 });
|
||||
w2.loadURL('about:blank');
|
||||
const w1Focused = once(w1, 'focus');
|
||||
w1.webContents.focus();
|
||||
await w1Focused;
|
||||
expect(w1.webContents.isFocused()).to.be.true('focuses window');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sizing', () => {
|
||||
@@ -1814,7 +1846,8 @@ describe('BrowserWindow module', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('BrowserWindow.setContentBounds(bounds)', () => {
|
||||
// Windows cannot be programmatically moved on Wayland
|
||||
ifdescribe(!isWayland)('BrowserWindow.setContentBounds(bounds)', () => {
|
||||
it('sets the content size and position', async () => {
|
||||
const bounds = { x: 10, y: 10, width: 250, height: 250 };
|
||||
const resize = once(w, 'resize');
|
||||
@@ -3285,6 +3318,19 @@ describe('BrowserWindow module', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// On Wayland, hidden windows may not have mapped surfaces or finalized geometry
|
||||
// until shown. Tests that depend on real geometry or frame events may need
|
||||
// to show the window first.
|
||||
const showWindowForWayland = async (w: BrowserWindow) => {
|
||||
if (!isWayland || w.isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shown = once(w, 'show');
|
||||
w.show();
|
||||
await shown;
|
||||
};
|
||||
|
||||
describe('"titleBarStyle" option', () => {
|
||||
const testWindowsOverlay = async (style: any) => {
|
||||
const w = new BrowserWindow({
|
||||
@@ -3304,8 +3350,10 @@ describe('BrowserWindow module', () => {
|
||||
} else {
|
||||
const overlayReady = once(ipcMain, 'geometrychange');
|
||||
await w.loadFile(overlayHTML);
|
||||
await showWindowForWayland(w);
|
||||
await overlayReady;
|
||||
}
|
||||
|
||||
const overlayEnabled = await w.webContents.executeJavaScript('navigator.windowControlsOverlay.visible');
|
||||
expect(overlayEnabled).to.be.true('overlayEnabled');
|
||||
const overlayRect = await w.webContents.executeJavaScript('getJSOverlayProperties()');
|
||||
@@ -3418,6 +3466,7 @@ describe('BrowserWindow module', () => {
|
||||
} else {
|
||||
const overlayReady = once(ipcMain, 'geometrychange');
|
||||
await w.loadFile(overlayHTML);
|
||||
await showWindowForWayland(w);
|
||||
await overlayReady;
|
||||
}
|
||||
|
||||
@@ -3491,6 +3540,7 @@ describe('BrowserWindow module', () => {
|
||||
const overlayHTML = path.join(__dirname, 'fixtures', 'pages', 'overlay.html');
|
||||
const overlayReady = once(ipcMain, 'geometrychange');
|
||||
await w.loadFile(overlayHTML);
|
||||
await showWindowForWayland(w);
|
||||
if (firstRun) {
|
||||
await overlayReady;
|
||||
}
|
||||
@@ -4771,7 +4821,9 @@ describe('BrowserWindow module', () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
let called = false;
|
||||
w.loadFile(path.join(fixtures, 'api', 'frame-subscriber.html'));
|
||||
w.webContents.on('dom-ready', () => {
|
||||
w.webContents.on('dom-ready', async () => {
|
||||
await showWindowForWayland(w);
|
||||
|
||||
w.webContents.beginFrameSubscription(function () {
|
||||
// This callback might be called twice.
|
||||
if (called) return;
|
||||
@@ -4791,7 +4843,9 @@ describe('BrowserWindow module', () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
let called = false;
|
||||
w.loadFile(path.join(fixtures, 'api', 'frame-subscriber.html'));
|
||||
w.webContents.on('dom-ready', () => {
|
||||
w.webContents.on('dom-ready', async () => {
|
||||
await showWindowForWayland(w);
|
||||
|
||||
w.webContents.beginFrameSubscription(function (data) {
|
||||
// This callback might be called twice.
|
||||
if (called) return;
|
||||
@@ -4815,7 +4869,9 @@ describe('BrowserWindow module', () => {
|
||||
let called = false;
|
||||
let gotInitialFullSizeFrame = false;
|
||||
const [contentWidth, contentHeight] = w.getContentSize();
|
||||
w.webContents.on('did-finish-load', () => {
|
||||
w.webContents.on('did-finish-load', async () => {
|
||||
await showWindowForWayland(w);
|
||||
|
||||
w.webContents.beginFrameSubscription(true, (image, rect) => {
|
||||
if (image.isEmpty()) {
|
||||
// Chromium sometimes sends a 0x0 frame at the beginning of the
|
||||
@@ -5396,55 +5452,57 @@ describe('BrowserWindow module', () => {
|
||||
await createTwo();
|
||||
});
|
||||
|
||||
ifit(process.platform !== 'darwin')('can disable and enable a window', () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
w.setEnabled(false);
|
||||
expect(w.isEnabled()).to.be.false('w.isEnabled()');
|
||||
w.setEnabled(true);
|
||||
expect(w.isEnabled()).to.be.true('!w.isEnabled()');
|
||||
});
|
||||
ifdescribe(process.platform !== 'darwin' && !isWayland)('disabling parent windows', () => {
|
||||
it('can disable and enable a window', () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
w.setEnabled(false);
|
||||
expect(w.isEnabled()).to.be.false('w.isEnabled()');
|
||||
w.setEnabled(true);
|
||||
expect(w.isEnabled()).to.be.true('!w.isEnabled()');
|
||||
});
|
||||
|
||||
ifit(process.platform !== 'darwin')('disables parent window', () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
const c = new BrowserWindow({ show: false, parent: w, modal: true });
|
||||
expect(w.isEnabled()).to.be.true('w.isEnabled');
|
||||
c.show();
|
||||
expect(w.isEnabled()).to.be.false('w.isEnabled');
|
||||
});
|
||||
it('disables parent window', () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
const c = new BrowserWindow({ show: false, parent: w, modal: true });
|
||||
expect(w.isEnabled()).to.be.true('w.isEnabled');
|
||||
c.show();
|
||||
expect(w.isEnabled()).to.be.false('w.isEnabled');
|
||||
});
|
||||
|
||||
ifit(process.platform !== 'darwin')('re-enables an enabled parent window when closed', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
const c = new BrowserWindow({ show: false, parent: w, modal: true });
|
||||
const closed = once(c, 'closed');
|
||||
c.show();
|
||||
c.close();
|
||||
await closed;
|
||||
expect(w.isEnabled()).to.be.true('w.isEnabled');
|
||||
});
|
||||
it('re-enables an enabled parent window when closed', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
const c = new BrowserWindow({ show: false, parent: w, modal: true });
|
||||
const closed = once(c, 'closed');
|
||||
c.show();
|
||||
c.close();
|
||||
await closed;
|
||||
expect(w.isEnabled()).to.be.true('w.isEnabled');
|
||||
});
|
||||
|
||||
ifit(process.platform !== 'darwin')('does not re-enable a disabled parent window when closed', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
const c = new BrowserWindow({ show: false, parent: w, modal: true });
|
||||
const closed = once(c, 'closed');
|
||||
w.setEnabled(false);
|
||||
c.show();
|
||||
c.close();
|
||||
await closed;
|
||||
expect(w.isEnabled()).to.be.false('w.isEnabled');
|
||||
});
|
||||
it('does not re-enable a disabled parent window when closed', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
const c = new BrowserWindow({ show: false, parent: w, modal: true });
|
||||
const closed = once(c, 'closed');
|
||||
w.setEnabled(false);
|
||||
c.show();
|
||||
c.close();
|
||||
await closed;
|
||||
expect(w.isEnabled()).to.be.false('w.isEnabled');
|
||||
});
|
||||
|
||||
ifit(process.platform !== 'darwin')('disables parent window recursively', () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
const c = new BrowserWindow({ show: false, parent: w, modal: true });
|
||||
const c2 = new BrowserWindow({ show: false, parent: w, modal: true });
|
||||
c.show();
|
||||
expect(w.isEnabled()).to.be.false('w.isEnabled');
|
||||
c2.show();
|
||||
expect(w.isEnabled()).to.be.false('w.isEnabled');
|
||||
c.destroy();
|
||||
expect(w.isEnabled()).to.be.false('w.isEnabled');
|
||||
c2.destroy();
|
||||
expect(w.isEnabled()).to.be.true('w.isEnabled');
|
||||
it('disables parent window recursively', () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
const c = new BrowserWindow({ show: false, parent: w, modal: true });
|
||||
const c2 = new BrowserWindow({ show: false, parent: w, modal: true });
|
||||
c.show();
|
||||
expect(w.isEnabled()).to.be.false('w.isEnabled');
|
||||
c2.show();
|
||||
expect(w.isEnabled()).to.be.false('w.isEnabled');
|
||||
c.destroy();
|
||||
expect(w.isEnabled()).to.be.false('w.isEnabled');
|
||||
c2.destroy();
|
||||
expect(w.isEnabled()).to.be.true('w.isEnabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5684,7 +5742,7 @@ describe('BrowserWindow module', () => {
|
||||
});
|
||||
});
|
||||
|
||||
ifdescribe(process.platform !== 'win32')('visibleOnAllWorkspaces state', () => {
|
||||
ifdescribe(process.platform !== 'win32' && !isWayland)('visibleOnAllWorkspaces state', () => {
|
||||
describe('with properties', () => {
|
||||
it('can be changed', () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
@@ -6835,20 +6893,6 @@ describe('BrowserWindow module', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('window.webContents.focus()', () => {
|
||||
afterEach(closeAllWindows);
|
||||
it('focuses window', async () => {
|
||||
const w1 = new BrowserWindow({ x: 100, y: 300, width: 300, height: 200 });
|
||||
w1.loadURL('about:blank');
|
||||
const w2 = new BrowserWindow({ x: 300, y: 300, width: 300, height: 200 });
|
||||
w2.loadURL('about:blank');
|
||||
const w1Focused = once(w1, 'focus');
|
||||
w1.webContents.focus();
|
||||
await w1Focused;
|
||||
expect(w1.webContents.isFocused()).to.be.true('focuses window');
|
||||
});
|
||||
});
|
||||
|
||||
describe('offscreen rendering', () => {
|
||||
let w: BrowserWindow;
|
||||
beforeEach(function () {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { app } from 'electron/main';
|
||||
import { expect } from 'chai';
|
||||
import * as dbus from 'dbus-native';
|
||||
|
||||
import { once } from 'node:events';
|
||||
import * as path from 'node:path';
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
@@ -25,7 +26,7 @@ const skip = process.platform !== 'linux' ||
|
||||
!process.env.DBUS_SESSION_BUS_ADDRESS;
|
||||
|
||||
ifdescribe(!skip)('Notification module (dbus)', () => {
|
||||
let mock: any, Notification, getCalls: any, reset: any;
|
||||
let mock: any, Notification: any, getCalls: any, emitSignal: any, reset: any;
|
||||
const realAppName = app.name;
|
||||
const realAppVersion = app.getVersion();
|
||||
const appName = 'api-notification-dbus-spec';
|
||||
@@ -45,7 +46,17 @@ ifdescribe(!skip)('Notification module (dbus)', () => {
|
||||
const getInterface = promisify(service.getInterface.bind(service));
|
||||
mock = await getInterface(path, iface);
|
||||
getCalls = promisify(mock.GetCalls.bind(mock));
|
||||
emitSignal = promisify(mock.EmitSignal.bind(mock));
|
||||
reset = promisify(mock.Reset.bind(mock));
|
||||
|
||||
// Override GetCapabilities to include "actions" so that libnotify
|
||||
// registers the "default" action callback on notifications.
|
||||
const addMethod = promisify(mock.AddMethod.bind(mock));
|
||||
await addMethod(
|
||||
serviceName, 'GetCapabilities', '', 'as',
|
||||
'ret = ["body", "body-markup", "icon-static", "image/svg+xml", ' +
|
||||
'"private-synchronous", "append", "private-icon-only", "truncation", "actions"]'
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
@@ -122,7 +133,7 @@ ifdescribe(!skip)('Notification module (dbus)', () => {
|
||||
app_icon: '',
|
||||
title: 'title',
|
||||
body: 'body',
|
||||
actions: [],
|
||||
actions: ['default', 'View'],
|
||||
hints: {
|
||||
append: 'true',
|
||||
image_data: [3, 3, 12, true, 8, 4, Buffer.from([255, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 76, 255, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 38, 255, 255, 0, 0, 0, 255, 0, 0, 0, 0])],
|
||||
@@ -133,4 +144,30 @@ ifdescribe(!skip)('Notification module (dbus)', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ActivationToken on notification click', () => {
|
||||
it('should emit click when ActionInvoked is sent by the daemon', async () => {
|
||||
const n = new Notification({ title: 'activation-token-test', body: 'test' });
|
||||
const clicked = once(n, 'click');
|
||||
|
||||
n.show();
|
||||
|
||||
// getCalls returns all D-Bus method calls. The mock assigns sequential
|
||||
// notification IDs starting at 1 for each Notify call.
|
||||
const calls = await getCalls();
|
||||
const notifyCalls = calls.filter((c: any) => c[1] === 'Notify');
|
||||
const notificationId = notifyCalls.length;
|
||||
|
||||
// Simulate the notification daemon emitting ActivationToken (FDN 1.2)
|
||||
// followed by ActionInvoked for a "default" click.
|
||||
emitSignal(
|
||||
'org.freedesktop.Notifications', 'ActivationToken',
|
||||
'us', [['u', notificationId], ['s', 'test-activation-token']]);
|
||||
emitSignal(
|
||||
'org.freedesktop.Notifications', 'ActionInvoked',
|
||||
'us', [['u', notificationId], ['s', 'default']]);
|
||||
|
||||
await clicked;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,6 +53,26 @@ describe('session module', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const collectCookieChanges = async (cookies: Electron.Cookies, action: () => Promise<void>, count: number) => {
|
||||
const changes: Array<{ cause: string, cookie: Electron.Cookie, removed: boolean }> = [];
|
||||
let listener: ((event: Electron.Event, cookie: Electron.Cookie, cause: string, removed: boolean) => void) | undefined;
|
||||
|
||||
const changesPromise = new Promise<typeof changes>(resolve => {
|
||||
listener = (_event, cookie, cause, removed) => {
|
||||
changes.push({ cause, cookie, removed });
|
||||
if (changes.length === count) resolve(changes);
|
||||
};
|
||||
cookies.on('changed', listener);
|
||||
});
|
||||
|
||||
try {
|
||||
await action();
|
||||
return await changesPromise;
|
||||
} finally {
|
||||
if (listener) cookies.removeListener('changed', listener);
|
||||
}
|
||||
};
|
||||
|
||||
it('should get cookies', async () => {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Set-Cookie', [`${name}=${value}`]);
|
||||
@@ -223,6 +243,60 @@ describe('session module', () => {
|
||||
expect(removeEventRemoved).to.equal(true);
|
||||
});
|
||||
|
||||
it('emits overwrite and inserted events when a cookie is overwritten with a new value', async () => {
|
||||
const { cookies } = session.fromPartition('cookies-overwrite-changed');
|
||||
const name = 'foo';
|
||||
const oldVal = 'bar';
|
||||
const newVal = 'baz';
|
||||
const expected = [
|
||||
{ cause: 'overwrite', name, removed: true, value: oldVal },
|
||||
{ cause: 'inserted', name, removed: false, value: newVal }
|
||||
];
|
||||
|
||||
await cookies.set({ url, name, value: oldVal });
|
||||
const changes = await collectCookieChanges(cookies, async () => {
|
||||
await cookies.set({ url, name, value: newVal });
|
||||
}, 2);
|
||||
|
||||
const actual = changes.map(({ cookie: { name, value }, cause, removed }) => ({ cause, name, removed, value }));
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('emits inserted-no-value-change-overwrite when a cookie is overwritten with the same value', async () => {
|
||||
const { cookies } = session.fromPartition('cookies-same-value-overwrite-changed');
|
||||
const name = 'foo';
|
||||
const value = 'bar';
|
||||
const nowSec = Date.now() / 1000;
|
||||
const expected = [
|
||||
{ cause: 'overwrite', name, removed: true, value },
|
||||
{ cause: 'inserted-no-value-change-overwrite', name, removed: false, value }
|
||||
];
|
||||
|
||||
await cookies.set({ url, name, value, expirationDate: nowSec + 120 });
|
||||
const changes = await collectCookieChanges(cookies, async () => {
|
||||
await cookies.set({ url, name, value, expirationDate: nowSec + 240 });
|
||||
}, 2);
|
||||
|
||||
const actual = changes.map(({ cookie: { name, value }, cause, removed }) => ({ cause, name, removed, value }));
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('emits expired-overwrite when a cookie is overwritten by an already-expired cookie', async () => {
|
||||
const { cookies } = session.fromPartition('cookies-expired-overwrite-changed');
|
||||
const name = 'foo';
|
||||
const value = 'bar';
|
||||
const nowSec = Date.now() / 1000;
|
||||
const expected = [{ cause: 'expired-overwrite', name, removed: true, value }];
|
||||
|
||||
await cookies.set({ url, name, value, expirationDate: nowSec + 120 });
|
||||
const changes = await collectCookieChanges(cookies, async () => {
|
||||
await cookies.set({ url, name, value, expirationDate: nowSec - 10 });
|
||||
}, 1);
|
||||
|
||||
const actual = changes.map(({ cookie: { name, value }, cause, removed }) => ({ cause, name, removed, value }));
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
|
||||
describe('ses.cookies.flushStore()', async () => {
|
||||
it('flushes the cookies to disk', async () => {
|
||||
const name = 'foo';
|
||||
|
||||
@@ -1065,6 +1065,64 @@ describe('chromium features', () => {
|
||||
expect(status).to.equal('granted');
|
||||
});
|
||||
|
||||
it('concurrent getFileHandle calls on the same file do not stall', (done) => {
|
||||
const writablePath = path.join(fixturesPath, 'file-system', 'test-perms.html');
|
||||
const testDir = path.join(fixturesPath, 'file-system');
|
||||
const testFile = path.join(testDir, 'test.txt');
|
||||
|
||||
const w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
sandbox: false
|
||||
}
|
||||
});
|
||||
|
||||
w.webContents.session.setPermissionRequestHandler((wc, permission, callback, details) => {
|
||||
if (permission === 'fileSystem') {
|
||||
const { href } = url.pathToFileURL(writablePath);
|
||||
expect(details).to.deep.equal({
|
||||
fileAccessType: 'readable',
|
||||
isDirectory: false,
|
||||
isMainFrame: true,
|
||||
filePath: testFile,
|
||||
requestingUrl: href
|
||||
});
|
||||
callback(true);
|
||||
} else {
|
||||
callback(false);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.once('did-create-directory-handle', async () => {
|
||||
const result = await w.webContents.executeJavaScript(`
|
||||
new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const handles = await Promise.all([
|
||||
handle.getFileHandle('test.txt'),
|
||||
handle.getFileHandle('test.txt')
|
||||
]);
|
||||
resolve(handles.length === 2);
|
||||
} catch (err) {
|
||||
reject(err.message);
|
||||
}
|
||||
})
|
||||
`, true);
|
||||
expect(result).to.be.true();
|
||||
done();
|
||||
});
|
||||
|
||||
w.loadFile(writablePath);
|
||||
|
||||
w.webContents.once('did-finish-load', () => {
|
||||
// @ts-expect-error Undocumented testing method.
|
||||
clipboard._writeFilesForTesting([testDir]);
|
||||
w.webContents.focus();
|
||||
w.webContents.paste();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows permission when trying to create a writable file handle', (done) => {
|
||||
const writablePath = path.join(fixturesPath, 'file-system', 'test-perms.html');
|
||||
const testFile = path.join(fixturesPath, 'file-system', 'test.txt');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { app, session, webFrameMain, BrowserWindow, ipcMain, WebContents, Extension, Session } from 'electron/main';
|
||||
import { app, session, webFrameMain, BrowserWindow, ipcMain, WebContents, Extension, Session, ServiceWorkerInfo, ServiceWorkersRunningStatusChangedEventParams } from 'electron/main';
|
||||
|
||||
import { expect } from 'chai';
|
||||
import * as WebSocket from 'ws';
|
||||
@@ -10,7 +10,7 @@ import * as http from 'node:http';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import { emittedNTimes, emittedUntil } from './lib/events-helpers';
|
||||
import { ifit, listen, waitUntil } from './lib/spec-helpers';
|
||||
import { ifit, listen, startRemoteControlApp, waitUntil } from './lib/spec-helpers';
|
||||
import { expectWarningMessages } from './lib/warning-helpers';
|
||||
import { closeAllWindows, closeWindow, cleanupWebContents } from './lib/window-helpers';
|
||||
|
||||
@@ -816,6 +816,99 @@ describe('chrome extensions', () => {
|
||||
expect(scope).equals(extension.url);
|
||||
});
|
||||
|
||||
it('launches background service worker', async () => {
|
||||
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
|
||||
const launchPromise = new Promise<void>(resolve => {
|
||||
customSession.serviceWorkers.on('running-status-changed', ({ runningStatus }) => {
|
||||
if (runningStatus === 'running') resolve();
|
||||
});
|
||||
});
|
||||
const extension = await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'mv3-service-worker'));
|
||||
await launchPromise;
|
||||
const serviceWorkers = customSession.serviceWorkers.getAllRunning();
|
||||
expect(Object.values(serviceWorkers).some(worker => worker.scope === extension.url)).equals(true);
|
||||
});
|
||||
|
||||
it('launches background service worker when the extension is loaded again without restarting the app', async () => {
|
||||
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
|
||||
const extensionPath = path.join(fixtures, 'extensions', 'mv3-service-worker');
|
||||
|
||||
const isWorkerRunning = (extension: Extension) => {
|
||||
const serviceWorkers = customSession.serviceWorkers.getAllRunning();
|
||||
return Object.values(serviceWorkers).some(worker => worker.scope === extension.url);
|
||||
};
|
||||
|
||||
const loadAndUnloadExtension = async () => {
|
||||
const launchPromise = new Promise<void>(resolve => {
|
||||
customSession.serviceWorkers.on('running-status-changed', ({ runningStatus }) => {
|
||||
if (runningStatus === 'running') resolve();
|
||||
});
|
||||
});
|
||||
const extension = await customSession.extensions.loadExtension(extensionPath);
|
||||
await launchPromise;
|
||||
expect(isWorkerRunning(extension)).equals(true);
|
||||
|
||||
const stopPromise = new Promise<void>(resolve => {
|
||||
customSession.serviceWorkers.on('running-status-changed', ({ runningStatus }) => {
|
||||
if (runningStatus === 'stopped') resolve();
|
||||
});
|
||||
});
|
||||
customSession.extensions.removeExtension(extension.id);
|
||||
await stopPromise;
|
||||
expect(isWorkerRunning(extension)).equals(false);
|
||||
};
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await loadAndUnloadExtension();
|
||||
}
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/electron/electron/issues/41613
|
||||
it('launches background service worker after restarting the app', async () => {
|
||||
const partition = `persist:${uuid.v4()}`;
|
||||
const extensionPath = path.join(fixtures, 'extensions', 'mv3-service-worker');
|
||||
|
||||
const runRemoteControlApp = async () => {
|
||||
const rc = await startRemoteControlApp();
|
||||
|
||||
const exitPromise = once(rc.process, 'exit');
|
||||
|
||||
const { workerScopes, extensionUrl } = await rc.remotely(async (partition: string, extensionPath: string) => {
|
||||
const { session } = require('electron/main');
|
||||
const { setTimeout } = require('node:timers/promises');
|
||||
|
||||
const customSession = session.fromPartition(partition);
|
||||
|
||||
const launchPromise = new Promise<void>(resolve => {
|
||||
customSession.serviceWorkers.on('running-status-changed', ({ runningStatus }: ServiceWorkersRunningStatusChangedEventParams) => {
|
||||
if (runningStatus === 'running') resolve();
|
||||
});
|
||||
});
|
||||
const extension = await customSession.extensions.loadExtension(extensionPath);
|
||||
await launchPromise;
|
||||
const serviceWorkers = customSession.serviceWorkers.getAllRunning();
|
||||
|
||||
const workerScopes = Object.values(serviceWorkers).map(worker => (worker as ServiceWorkerInfo).scope);
|
||||
const extensionUrl = extension.url;
|
||||
|
||||
// Give Chromium some time to update extensions::kPrefHasStartedServiceWorker on disk
|
||||
await setTimeout(500);
|
||||
|
||||
global.setTimeout(() => require('electron').app.quit());
|
||||
|
||||
return { workerScopes, extensionUrl };
|
||||
}, partition, extensionPath);
|
||||
|
||||
await exitPromise;
|
||||
|
||||
expect(workerScopes.includes(extensionUrl)).equals(true);
|
||||
};
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await runRemoteControlApp();
|
||||
}
|
||||
});
|
||||
|
||||
it('can run chrome extension APIs', async () => {
|
||||
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true } });
|
||||
|
||||
29
spec/fixtures/crash-cases/wrappable-gc-weak-callback-emit/index.js
vendored
Normal file
29
spec/fixtures/crash-cases/wrappable-gc-weak-callback-emit/index.js
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
const { app, WebContentsView } = require('electron');
|
||||
|
||||
const v8 = require('node:v8');
|
||||
|
||||
// Force V8 to schedule incremental-marking finalization steps as foreground
|
||||
// tasks on every allocation. Those tasks run inside V8's
|
||||
// DisallowJavascriptExecutionScope. Prior to the fix in
|
||||
// shell/common/gin_helper/wrappable.cc, gin_helper::SecondWeakCallback would
|
||||
// `delete` the Wrappable synchronously inside that scope, and Wrappables
|
||||
// whose destructors emit JS events (WebContents emits 'will-destroy' /
|
||||
// 'destroyed') would crash with "Invoke in DisallowJavascriptExecutionScope".
|
||||
//
|
||||
// Regression test for https://github.com/electron/electron/issues/47420.
|
||||
v8.setFlagsFromString('--stress-incremental-marking');
|
||||
|
||||
app.whenReady().then(() => {
|
||||
// Leak several WebContentsView instances so at least one wrapper's
|
||||
// collection lands in a foreground GC task during app.quit()'s uv_run
|
||||
// drain. Three is enough for the crash to reproduce 10/10 on a Linux
|
||||
// testing build before the fix.
|
||||
// eslint-disable-next-line no-new
|
||||
new WebContentsView();
|
||||
// eslint-disable-next-line no-new
|
||||
new WebContentsView();
|
||||
// eslint-disable-next-line no-new
|
||||
new WebContentsView();
|
||||
|
||||
app.quit();
|
||||
});
|
||||
18
spec/fixtures/module/preload-require-identity.js
vendored
Normal file
18
spec/fixtures/module/preload-require-identity.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/* 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
|
||||
});
|
||||
@@ -284,6 +284,41 @@ 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', () => {
|
||||
|
||||
@@ -2,8 +2,8 @@ const childProcess = require('node:child_process');
|
||||
const path = require('node:path');
|
||||
|
||||
const typeCheck = () => {
|
||||
const tscExec = path.resolve(require.resolve('typescript'), '../../bin/tsc');
|
||||
const tscChild = childProcess.spawn(process.execPath, [tscExec, '--project', './ts-smoke/tsconfig.json'], {
|
||||
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'], {
|
||||
cwd: path.resolve(__dirname, '../')
|
||||
});
|
||||
tscChild.stdout.on('data', d => console.log(d.toString()));
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"compilerOptions": {
|
||||
"rootDir": "default_app",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node"
|
||||
"moduleResolution": "bundler"
|
||||
},
|
||||
"include": [
|
||||
"default_app",
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "lib"
|
||||
// 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.
|
||||
},
|
||||
"include": [
|
||||
"lib",
|
||||
|
||||
@@ -10,14 +10,12 @@
|
||||
"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/*"],
|
||||
"@node/*": ["../third_party/electron_node/*"]
|
||||
"@electron/internal/*": ["./lib/*"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
|
||||
36
typings/internal-ambient.d.ts
vendored
36
typings/internal-ambient.d.ts
vendored
@@ -1,7 +1,37 @@
|
||||
/// <reference types="webpack/module" />
|
||||
|
||||
declare const BUILDFLAG: (flag: boolean) => boolean;
|
||||
|
||||
// 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 namespace NodeJS {
|
||||
interface ModuleInternal extends NodeJS.Module {
|
||||
new(id: string, parent?: NodeJS.Module | null): NodeJS.Module;
|
||||
@@ -282,7 +312,7 @@ declare namespace NodeJS {
|
||||
}
|
||||
}
|
||||
|
||||
declare module NodeJS {
|
||||
declare namespace NodeJS {
|
||||
interface Global {
|
||||
require: NodeRequire;
|
||||
module: NodeModule;
|
||||
|
||||
2
typings/internal-electron.d.ts
vendored
2
typings/internal-electron.d.ts
vendored
@@ -93,7 +93,6 @@ declare namespace Electron {
|
||||
getLastWebPreferences(): Electron.WebPreferences | null;
|
||||
_getProcessMemoryInfo(): Electron.ProcessMemoryInfo;
|
||||
_getPreloadScript(): Electron.PreloadScript | null;
|
||||
equal(other: WebContents): boolean;
|
||||
browserWindowOptions: BrowserWindowConstructorOptions;
|
||||
_windowOpenHandler: ((details: Electron.HandlerDetails) => any) | null;
|
||||
_callWindowOpenHandler(event: any, details: Electron.HandlerDetails): {browserWindowConstructorOptions: Electron.BrowserWindowConstructorOptions | null, outlivesOpener: boolean, createWindow?: Electron.CreateWindowFunction};
|
||||
@@ -102,7 +101,6 @@ declare namespace Electron {
|
||||
_sendInternal(channel: string, ...args: any[]): void;
|
||||
_printToPDF(options: any): Promise<Buffer>;
|
||||
_print(options: any, callback?: (success: boolean, failureReason: string) => void): void;
|
||||
_getPrintersAsync(): Promise<Electron.PrinterInfo[]>;
|
||||
_init(): void;
|
||||
_getNavigationEntryAtIndex(index: number): Electron.NavigationEntry | null;
|
||||
_getActiveIndex(): number;
|
||||
|
||||
Reference in New Issue
Block a user