Compare commits

..

6 Commits

Author SHA1 Message Date
John Kleinschmidt
f204435c7d ci: test linux 64k 2026-02-25 17:33:38 -05:00
Mitchell Cohen
6a2571ee3d ci: Wayland test job, helpers, and app spec (#49908)
* wayland test chromium patch

* ci: add wayland test job and helpers

* use weston directly instead of wlheadless-run

* roll build image to eac3529

* fixed exec command

* Update .github/workflows/pipeline-segment-electron-test.yml

Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>

* Update .github/workflows/pipeline-segment-electron-test.yml

Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>

* chore: fixup shard case statement

* reverted leftover patch line

---------

Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-02-25 14:51:13 -05:00
Shelley Vohr
94aa90bb64 fix: recover network requests after Network Service restart (#49887)
* fix: recover network requests after Network Service restart

* chore: reuse implementation

* chore: make linter happy

* chore: fix lint

---------

Co-authored-by: deepak1556 <hop2deep@gmail.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
2026-02-25 12:53:06 -05:00
zonescape
b9a09acff3 docs: mark "Show hidden files" file dialog setting as deprecated on Linux (#46926)
* fix: don't overwrite "Show hidden files" setting on Linux/GTK

* docs: deprecate showHiddenFiles property in dialogs on Linux

* docs: mark Electron 42 as the removal date for this feature

---------

Co-authored-by: Charles Kerr <charles@charleskerr.com>
2026-02-25 11:05:01 -05:00
David Sanders
fd9bf54243 build: fix Chromium roll linting merge base determination in CI (#49937) 2026-02-25 08:38:31 -05:00
John Kleinschmidt
d9170093aa build: exit upload with error code if github upload fails (#49936) 2026-02-25 14:16:13 +01:00
38 changed files with 562 additions and 134 deletions

View File

@@ -2,7 +2,7 @@ version: '3'
services:
buildtools:
image: ghcr.io/electron/devcontainer:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
image: ghcr.io/electron/devcontainer:eac3529546ea8f3aa356d31e345715eef342233b
volumes:
- ..:/workspaces/gclient/src/electron:cached

View File

@@ -41,7 +41,7 @@ jobs:
permissions:
contents: read
container:
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
image: ghcr.io/electron/build:eac3529546ea8f3aa356d31e345715eef342233b
options: --user root
volumes:
- /mnt/cross-instance-cache:/mnt/cross-instance-cache

View File

@@ -15,7 +15,7 @@ jobs:
permissions:
contents: read
container:
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
image: ghcr.io/electron/build:eac3529546ea8f3aa356d31e345715eef342233b
options: --user root
volumes:
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
@@ -39,7 +39,7 @@ jobs:
permissions:
contents: read
container:
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
image: ghcr.io/electron/build:eac3529546ea8f3aa356d31e345715eef342233b
options: --user root --device /dev/fuse --cap-add SYS_ADMIN
volumes:
- /mnt/win-cache:/mnt/win-cache
@@ -66,7 +66,7 @@ jobs:
# This job updates the same git cache as linux, so it needs to run after the linux one.
needs: build-git-cache-linux
container:
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
image: ghcr.io/electron/build:eac3529546ea8f3aa356d31e345715eef342233b
options: --user root
volumes:
- /mnt/cross-instance-cache:/mnt/cross-instance-cache

View File

@@ -6,7 +6,7 @@ on:
build-image-sha:
type: string
description: 'SHA for electron/build image'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
default: 'eac3529546ea8f3aa356d31e345715eef342233b'
required: true
skip-macos:
type: boolean
@@ -77,7 +77,7 @@ jobs:
id: set-output
run: |
if [ -z "${{ inputs.build-image-sha }}" ]; then
echo "build-image-sha=a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb" >> "$GITHUB_OUTPUT"
echo "build-image-sha=eac3529546ea8f3aa356d31e345715eef342233b" >> "$GITHUB_OUTPUT"
else
echo "build-image-sha=${{ inputs.build-image-sha }}" >> "$GITHUB_OUTPUT"
fi

View File

@@ -6,7 +6,7 @@ on:
build-image-sha:
type: string
description: 'SHA for electron/build image'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
default: 'eac3529546ea8f3aa356d31e345715eef342233b'
upload-to-storage:
description: 'Uploads to Azure storage'
required: false

View File

@@ -6,7 +6,7 @@ on:
build-image-sha:
type: string
description: 'SHA for electron/build image'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
default: 'eac3529546ea8f3aa356d31e345715eef342233b'
required: true
upload-to-storage:
description: 'Uploads to Azure storage'

View File

@@ -110,6 +110,33 @@ jobs:
test-runs-on: ${{ inputs.test-runs-on }}
test-container: ${{ inputs.test-container }}
secrets: inherit
test-wayland:
uses: ./.github/workflows/pipeline-segment-electron-test.yml
permissions:
contents: read
issues: read
pull-requests: read
needs: build
if: ${{ inputs.target-platform == 'linux' && inputs.target-arch == 'x64' && !inputs.is-asan }}
with:
target-arch: ${{ inputs.target-arch }}
target-platform: ${{ inputs.target-platform }}
test-runs-on: ${{ inputs.test-runs-on }}
test-container: ${{ inputs.test-container }}
display-server: wayland
secrets: inherit
test-linux-64k:
uses: ./.github/workflows/pipeline-segment-electron-test-64k.yml
permissions:
contents: read
issues: read
pull-requests: read
needs: build
if: ${{ inputs.target-platform == 'linux' && inputs.target-arch == 'arm64' && !inputs.is-asan }}
with:
test-runs-on: ${{ inputs.test-runs-on }}
test-container: ${{ inputs.test-container }}
secrets: inherit
nn-test:
uses: ./.github/workflows/pipeline-segment-node-nan-test.yml
permissions:

View File

@@ -0,0 +1,64 @@
name: Pipeline Segment - Electron Test on Linux ARM64 64k
on:
workflow_call:
inputs:
test-runs-on:
type: string
description: 'What host to run the tests on'
required: true
test-container:
type: string
description: 'JSON container information for aks runs-on'
required: false
default: '{"image":null}'
concurrency:
group: electron-test-linux-64k-${{ github.ref_protected == true && github.run_id || github.ref }}
cancel-in-progress: ${{ github.ref_protected != true }}
permissions: {}
env:
ELECTRON_OUT_DIR: Default
jobs:
test-linux-arm64-64k:
defaults:
run:
shell: bash
runs-on: ${{ inputs.test-runs-on }}
permissions:
contents: read
issues: read
pull-requests: read
steps:
- name: Download Generated Artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
with:
name: generated_artifacts_linux_arm64
path: ./generated_artifacts_linux_arm64
- name: Download Src Artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
with:
name: src_artifacts_linux_arm64
path: ./src_artifacts_linux_arm64
- name: Restore Generated Artifacts
run: ./src/electron/script/actions/restore-artifacts.sh
- name: Unzip Dist
run: |
cd src/out/Default
unzip -:o dist.zip
- name: Run Electron Tests in QEMU 64k Container
shell: bash
env:
MOCHA_REPORTER: mocha-multi-reporters
MOCHA_MULTI_REPORTERS: mocha-junit-reporter, tap
ELECTRON_DISABLE_SECURITY_WARNINGS: 1
DISPLAY: ':99.0'
run: |
container=$(echo '${{ inputs.test-container }}' | jq -r '.image')
echo "Running tests in container: $container"
src/electron/script/run-qemu-64k.sh --container="$container" --testfiles=`pwd`/src

View File

@@ -30,9 +30,14 @@ on:
required: false
type: boolean
default: false
display-server:
description: 'Display backend for Linux tests: x11 or wayland'
required: false
type: string
default: x11
concurrency:
group: electron-test-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ inputs.is-asan }}-${{ github.ref_protected == true && github.run_id || github.ref }}
group: electron-test-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ inputs.is-asan }}-${{ inputs.display-server }}-${{ github.ref_protected == true && github.run_id || github.ref }}
cancel-in-progress: ${{ github.ref_protected != true }}
permissions: {}
@@ -59,7 +64,7 @@ jobs:
fail-fast: false
matrix:
build-type: ${{ inputs.target-platform == 'macos' && fromJSON('["darwin","mas"]') || (inputs.target-platform == 'win' && fromJSON('["win"]') || fromJSON('["linux"]')) }}
shard: ${{ inputs.target-platform == 'linux' && fromJSON('[1, 2, 3]') || fromJSON('[1, 2]') }}
shard: ${{ case(inputs.display-server == 'wayland', fromJSON('[1]'), inputs.target-platform == 'linux', fromJSON('[1, 2, 3]'), fromJSON('[1, 2]')) }}
env:
BUILD_TYPE: ${{ matrix.build-type }}
TARGET_ARCH: ${{ inputs.target-arch }}
@@ -210,7 +215,22 @@ jobs:
cd src/electron
export ELECTRON_TEST_RESULTS_DIR=`pwd`/junit
# Get which tests are on this shard
tests_files=$(node script/split-tests ${{ matrix.shard }} ${{ inputs.target-platform == 'linux' && 3 || 2 }})
tests_files=$(node script/split-tests ${{ matrix.shard }} ${{ case(inputs.display-server == 'wayland', 1, inputs.target-platform == 'linux', 3, 2) }})
if [ "${{ inputs.display-server }}" = "wayland" ]; then
allowlist_file=script/wayland-test-allowlist.txt
filtered_tests=""
for test_file in $tests_files; do
if grep -Fxq "$test_file" "$allowlist_file"; then
filtered_tests="$filtered_tests $test_file"
fi
done
tests_files="${filtered_tests# }"
if [ -z "$tests_files" ]; then
echo "No tests matched Wayland filter, skipping."
exit 0
fi
fi
# Run tests
if [ "${{ inputs.target-platform }}" != "linux" ]; then
@@ -245,7 +265,11 @@ jobs:
if [ "${{ inputs.target-arch }}" = "arm" ]; then
runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn.js test --skipYarnInstall --runners=main --enableRerun=3 --trace-uncaught --enable-logging --files $tests_files
else
runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn.js test --runners=main --enableRerun=3 --trace-uncaught --enable-logging --files $tests_files
if [ "${{ inputs.display-server }}" = "wayland" ]; then
runuser -u builduser -- script/actions/run-tests-wayland.sh script/yarn.js test --runners=main --enableRerun=3 --trace-uncaught --enable-logging --files $tests_files
else
runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn.js test --runners=main --enableRerun=3 --trace-uncaught --enable-logging --files $tests_files
fi
fi
fi
@@ -268,7 +292,7 @@ jobs:
if: always() && !cancelled()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: test_artifacts_${{ env.ARTIFACT_KEY }}_${{ matrix.shard }}
name: ${{ inputs.target-platform == 'linux' && format('test_artifacts_{0}_{1}_{2}', env.ARTIFACT_KEY, inputs.display-server, matrix.shard) || format('test_artifacts_{0}_{1}', env.ARTIFACT_KEY, matrix.shard) }}
path: src/electron/spec/artifacts
if-no-files-found: ignore
- name: Wait for active SSH sessions

View File

@@ -6,7 +6,7 @@ on:
build-image-sha:
type: string
description: 'SHA for electron/build image'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
default: 'eac3529546ea8f3aa356d31e345715eef342233b'
required: true
upload-to-storage:
description: 'Uploads to Azure storage'

View File

@@ -21,10 +21,7 @@ The `dialog` module has the following methods:
* `window` [BaseWindow](base-window.md) (optional)
* `options` Object
* `title` string (optional)
* `defaultPath` string (optional) - Absolute directory path, absolute file
path, or file name to use by default. If not provided, the dialog will
default to the user's Downloads folder, or their home directory if Downloads
doesn't exist.
* `defaultPath` string (optional)
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
left empty the default label will be used.
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
@@ -33,7 +30,7 @@ The `dialog` module has the following methods:
* `openFile` - Allow files to be selected.
* `openDirectory` - Allow directories to be selected.
* `multiSelections` - Allow multiple paths to be selected.
* `showHiddenFiles` - Show hidden files in dialog.
* `showHiddenFiles` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
* `promptToCreate` _Windows_ - Prompt for creation if the file path entered
in the dialog does not exist. This does not actually create the file at
@@ -96,10 +93,7 @@ dialog.showOpenDialogSync(mainWindow, {
* `window` [BaseWindow](base-window.md) (optional)
* `options` Object
* `title` string (optional)
* `defaultPath` string (optional) - Absolute directory path, absolute file
path, or file name to use by default. If not provided, the dialog will
default to the user's Downloads folder, or their home directory if Downloads
doesn't exist.
* `defaultPath` string (optional)
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
left empty the default label will be used.
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
@@ -108,7 +102,7 @@ dialog.showOpenDialogSync(mainWindow, {
* `openFile` - Allow files to be selected.
* `openDirectory` - Allow directories to be selected.
* `multiSelections` - Allow multiple paths to be selected.
* `showHiddenFiles` - Show hidden files in dialog.
* `showHiddenFiles` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
* `promptToCreate` _Windows_ - Prompt for creation if the file path entered
in the dialog does not exist. This does not actually create the file at
@@ -181,9 +175,7 @@ dialog.showOpenDialog(mainWindow, {
* `options` Object
* `title` string (optional) - The dialog title. Cannot be displayed on some _Linux_ desktop environments.
* `defaultPath` string (optional) - Absolute directory path, absolute file
path, or file name to use by default. If not provided, the dialog will
default to the user's Downloads folder, or their home directory if Downloads
doesn't exist.
path, or file name to use by default.
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
left empty the default label will be used.
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
@@ -193,7 +185,7 @@ dialog.showOpenDialog(mainWindow, {
* `showsTagField` boolean (optional) _macOS_ - Show the tags input box,
defaults to `true`.
* `properties` string[]&#32;(optional)
* `showHiddenFiles` - Show hidden files in dialog.
* `showHiddenFiles` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
* `treatPackageAsDirectory` _macOS_ - Treat packages, such as `.app` folders,
as a directory instead of a file.
@@ -214,9 +206,7 @@ The `filters` specifies an array of file types that can be displayed, see
* `options` Object
* `title` string (optional) - The dialog title. Cannot be displayed on some _Linux_ desktop environments.
* `defaultPath` string (optional) - Absolute directory path, absolute file
path, or file name to use by default. If not provided, the dialog will
default to the user's Downloads folder, or their home directory if Downloads
doesn't exist.
path, or file name to use by default.
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
left empty the default label will be used.
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
@@ -225,7 +215,7 @@ The `filters` specifies an array of file types that can be displayed, see
displayed in front of the filename text field.
* `showsTagField` boolean (optional) _macOS_ - Show the tags input box, defaults to `true`.
* `properties` string[]&#32;(optional)
* `showHiddenFiles` - Show hidden files in dialog.
* `showHiddenFiles` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
* `treatPackageAsDirectory` _macOS_ - Treat packages, such as `.app` folders,
as a directory instead of a file.

View File

@@ -14,17 +14,6 @@ This document uses the following convention to categorize breaking changes:
## Planned Breaking API Changes (42.0)
### Behavior Changed: Dialog methods default to Downloads directory
The `defaultPath` option for the following methods now defaults to the user's Downloads folder (or their home directory if Downloads doesn't exist) when not explicitly provided:
* `dialog.showOpenDialog`
* `dialog.showOpenDialogSync`
* `dialog.showSaveDialog`
* `dialog.showSaveDialogSync`
Previously, these methods used the last-opened directory or an OS-determined default. To preserve the old behavior, explicitly pass `defaultPath` when calling these methods.
### Behavior Changed: Offscreen rendering will use `1.0` as default device scale factor.
Previously, OSR used the primary display's device scale factor for rendering, which made the output frame size vary across users.
@@ -91,6 +80,12 @@ your preload script and expose it using the [contextBridge](https://www.electron
Debug symbols for MacOS (dSYM) now use xz compression in order to handle larger file sizes. `dsym.zip` files are now
`dsym.tar.xz` files. End users using debug symbols may need to update their zip utilities.
### Deprecated: `showHiddenFiles` in Dialogs on Linux
This property will still be honored on macOS and Windows, but support on Linux
will be removed in Electron 42. GTK intends for this to be a user choice rather
than an app choice and has removed the API to do this programmatically.
## Planned Breaking API Changes (39.0)
### Deprecated: `--host-rules` command line switch

View File

@@ -144,3 +144,4 @@ fix_linux_tray_id.patch
expose_gtk_ui_platform_field.patch
patch_osr_control_screen_info.patch
refactor_allow_customizing_config_in_freedesktopsecretkeyprovider.patch
fix_wayland_test_crash_on_teardown.patch

View File

@@ -0,0 +1,20 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Mitchell Cohen <mitch.cohen@me.com>
Date: Sun, 22 Feb 2026 11:38:49 -0500
Subject: fix: Wayland test crash on teardown
Allows Wayland test job to teardown the connection without crashing when trying to update the pointer
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.cc b/ui/ozone/platform/wayland/host/wayland_connection.cc
index 4c44eaeebe091906a1676da106faa9072819b67e..224f6abfe06794d31fc4d876c8242dab79ba075d 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection.cc
+++ b/ui/ozone/platform/wayland/host/wayland_connection.cc
@@ -426,7 +426,7 @@ std::vector<TouchscreenDevice> WaylandConnection::CreateTouchscreenDevices()
}
void WaylandConnection::UpdateCursor() {
- if (auto* pointer = seat_->pointer()) {
+ if (auto* pointer = seat_ ? seat_->pointer() : nullptr) {
cursor_ = std::make_unique<WaylandCursor>(pointer, this);
cursor_->set_listener(listener_);
cursor_position_ = std::make_unique<WaylandCursorPosition>();

View File

@@ -0,0 +1,30 @@
#!/bin/bash
set -euo pipefail
export XDG_SESSION_TYPE=wayland
export WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-99}"
if [[ -z "${XDG_RUNTIME_DIR:-}" ]]; then
XDG_RUNTIME_DIR="$(mktemp -d)"
chmod 700 "$XDG_RUNTIME_DIR"
export XDG_RUNTIME_DIR
trap 'kill "$WESTON_PID" >/dev/null 2>&1 || true; rm -rf "$XDG_RUNTIME_DIR"' EXIT
else
trap 'kill "$WESTON_PID" >/dev/null 2>&1 || true' EXIT
fi
weston \
--backend=headless-backend.so \
--socket="$WAYLAND_DISPLAY" \
--idle-time=0 \
>/tmp/weston-headless.log 2>&1 &
WESTON_PID=$!
for _ in {1..100}; do
if [[ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]]; then
break
fi
sleep 0.1
done
node "$@" --ozone-platform=wayland

View File

@@ -124,7 +124,7 @@ async function main () {
// Get the merge base with the target branch
let mergeBase;
try {
mergeBase = execSync(`git merge-base ${currentBranch} origin/${targetBranch}`, {
mergeBase = execSync(`git merge-base HEAD origin/${targetBranch}`, {
cwd: ELECTRON_DIR,
encoding: 'utf8'
}).trim();

41
script/qemu-init.sh Normal file
View File

@@ -0,0 +1,41 @@
#!/bin/sh
echo "Mounting essential filesystems"
mount -t proc proc /proc
mount -t sysfs sys /sys
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
mkdir -p /dev/shm
mount -t tmpfs tmpfs /dev/shm
mount -t tmpfs tmpfs /tmp
chmod 1777 /tmp
mount -t tmpfs tmpfs /run
mkdir -p /run/dbus
mkdir -p /run/user/0
chmod 700 /run/user/0
mount -t tmpfs tmpfs /var/tmp
echo "Setting up machine-id for D-Bus"
cat /proc/sys/kernel/random/uuid | tr -d '-' > /etc/machine-id
echo "Setting system clock"
date -s "$(cat /host-time)"
echo "Setting up networking"
ip link set lo up
ip link set eth0 up
ip addr add 10.0.2.15/24 dev eth0
ip route add default via 10.0.2.2
echo "nameserver 10.0.2.3" > /etc/resolv.conf
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export XDG_RUNTIME_DIR=/run/user/0
echo "Starting entrypoint"
echo "System: $(uname -s) $(uname -r) $(uname -m), page size: $(getconf PAGESIZE) bytes"
EXIT_CODE=$?
echo $EXIT_CODE > /exit-code
sync
echo "Powering off"
echo o > /proc/sysrq-trigger

View File

@@ -368,6 +368,9 @@ def upload_io_to_github(release, filename, filepath, version):
for c in iter(lambda: upload_process.stdout.read(1), b""):
sys.stdout.buffer.write(c)
sys.stdout.flush()
upload_process.wait()
if upload_process.returncode != 0:
sys.exit(upload_process.returncode)
if "GITHUB_OUTPUT" in os.environ:
output_path = os.environ["GITHUB_OUTPUT"]

89
script/run-qemu-64k.sh Normal file
View File

@@ -0,0 +1,89 @@
#!/bin/sh
set -e
CONTAINER=""
TESTFILES=""
ARGS=""
while [ $# -gt 0 ]; do
case "$1" in
--container) CONTAINER="$2"; shift 2 ;;
--testfiles) TESTFILES="$2"; shift 2 ;;
--) shift; ARGS="$*"; break ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
if [ -z "$CONTAINER" ]; then
echo "Usage: $0 --container CONTAINER [-- ARGS...]"
exit 1
fi
echo "Installing QEMU system emulation and tools"
sudo apt-get update && sudo apt-get install -y qemu-system-arm binutils
echo "Exporting container filesystem"
CONTAINER_ID=$(docker create --platform linux/arm64 "$CONTAINER")
ROOTFS_DIR=$(mktemp -d)
docker export "$CONTAINER_ID" | sudo tar -xf - -C "$ROOTFS_DIR"
docker rm -f "$CONTAINER_ID"
echo "Removing container image to free disk space"
docker rmi "$CONTAINER" || true
docker system prune -f || true
echo "Copying test files into root filesystem"
if [ -n "$TESTFILES" ]; then
sudo cp -r $TESTFILES "$ROOTFS_DIR/root/"
fi
echo "Downloading Ubuntu 24.04 generic-64k kernel for ARM64"
KERNEL_URL="http://ports.ubuntu.com/ubuntu-ports/pool/main/l/linux/linux-image-unsigned-6.8.0-90-generic-64k_6.8.0-90.91_arm64.deb"
KERNEL_DIR=$(mktemp -d)
curl -fL "$KERNEL_URL" -o "$KERNEL_DIR/kernel.deb"
echo "Extracting kernel"
(cd "$KERNEL_DIR" && ar x kernel.deb && tar xf data.tar*)
VMLINUZ="$KERNEL_DIR/boot/vmlinuz-6.8.0-90-generic-64k"
if [ ! -f "$VMLINUZ" ]; then
echo "Error: Could not find kernel at $VMLINUZ"
exit 1
fi
echo "Storing test arguments and installing init script"
echo "$ARGS" > "$ROOTFS_DIR/test-args"
date -u '+%Y-%m-%d %H:%M:%S' > "$ROOTFS_DIR/host-time"
sudo mv "$ROOTFS_DIR/root/electron/scripts/qemu-init.sh" "$ROOTFS_DIR/init"
sudo chmod +x "$ROOTFS_DIR/init"
echo "Creating disk image with root filesystem"
DISK_IMG=$(mktemp)
dd if=/dev/zero of="$DISK_IMG" bs=1M count=2048 status=none
sudo mkfs.ext4 -q -d "$ROOTFS_DIR" "$DISK_IMG"
sudo rm -rf "$ROOTFS_DIR"
echo "Starting QEMU VM with 64K page size kernel"
timeout 1800 qemu-system-aarch64 \
-M virt \
-cpu max,pauth-impdef=on \
-accel tcg,thread=multi \
-m 4096 \
-smp 2 \
-kernel "$VMLINUZ" \
-append "console=ttyAMA0 root=/dev/vda rw init=/init net.ifnames=0" \
-drive file="$DISK_IMG",format=raw,if=virtio \
-netdev user,id=net0 \
-device virtio-net-pci,netdev=net0 \
-nographic \
-no-reboot \
|| true
echo "Extracting test results from disk image"
MOUNT_DIR=$(mktemp -d)
sudo mount -o loop "$DISK_IMG" "$MOUNT_DIR"
if [ -f "$MOUNT_DIR/root/results.xml" ]; then
cp "$MOUNT_DIR/root/results.xml" "$TEST_DIR/results.xml"
fi
EXIT_CODE=$(cat "$MOUNT_DIR/exit-code" 2>/dev/null || echo 1)
sudo umount "$MOUNT_DIR"
exit $EXIT_CODE

View File

@@ -0,0 +1,4 @@
spec/parse-features-string-spec.ts
spec/types-spec.ts
spec/version-bump-spec.ts
spec/api-app-spec.ts

View File

@@ -14,6 +14,7 @@
#include "base/process/launch.h"
#include "base/process/process.h"
#include "chrome/browser/browser_process.h"
#include "content/browser/network_service_instance_impl.h" // nogncheck
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/common/result_codes.h"
@@ -72,7 +73,8 @@ UtilityProcessWrapper::UtilityProcessWrapper(
base::FilePath current_working_directory,
bool use_plugin_helper,
bool create_network_observer,
bool disclaim_responsibility) {
bool disclaim_responsibility)
: create_network_observer_(create_network_observer) {
#if BUILDFLAG(IS_WIN)
base::win::ScopedHandle stdout_write(nullptr);
base::win::ScopedHandle stderr_write(nullptr);
@@ -212,32 +214,15 @@ UtilityProcessWrapper::UtilityProcessWrapper(
connector_->set_connection_error_handler(base::BindOnce(
&UtilityProcessWrapper::CloseConnectorPort, weak_factory_.GetWeakPtr()));
mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory;
network::mojom::URLLoaderFactoryParamsPtr loader_params =
network::mojom::URLLoaderFactoryParams::New();
loader_params->process_id = network::OriginatingProcess::browser();
loader_params->is_orb_enabled = false;
loader_params->is_trusted = true;
if (create_network_observer) {
url_loader_network_observer_.emplace();
loader_params->url_loader_network_observer =
url_loader_network_observer_->Bind();
}
network::mojom::NetworkContext* network_context =
g_browser_process->system_network_context_manager()->GetContext();
network_context->CreateURLLoaderFactory(
url_loader_factory.InitWithNewPipeAndPassReceiver(),
std::move(loader_params));
params->url_loader_factory = std::move(url_loader_factory);
mojo::PendingRemote<network::mojom::HostResolver> host_resolver;
network_context->CreateHostResolver(
{}, host_resolver.InitWithNewPipeAndPassReceiver());
params->host_resolver = std::move(host_resolver);
params->use_network_observer_from_url_loader_factory =
create_network_observer;
params->url_loader_factory_params = CreateURLLoaderFactoryParams();
node_service_remote_->Initialize(std::move(params),
receiver_.BindNewPipeAndPassRemote());
// Subscribe to Network Service process gone notifications.
network_service_gone_subscription_ =
content::RegisterNetworkServiceProcessGoneHandler(base::BindRepeating(
&UtilityProcessWrapper::CreateAndSendURLLoaderFactory,
weak_factory_.GetWeakPtr()));
}
UtilityProcessWrapper::~UtilityProcessWrapper() {
@@ -429,6 +414,44 @@ void UtilityProcessWrapper::OnV8FatalError(const std::string& location,
EmitWithoutEvent("error", "FatalError", location, report);
}
void UtilityProcessWrapper::CreateAndSendURLLoaderFactory(bool /* crashed */) {
if (!node_service_remote_.is_connected())
return;
node_service_remote_->UpdateURLLoaderFactory(CreateURLLoaderFactoryParams());
}
node::mojom::URLLoaderFactoryParamsPtr
UtilityProcessWrapper::CreateURLLoaderFactoryParams() {
node::mojom::URLLoaderFactoryParamsPtr params =
node::mojom::URLLoaderFactoryParams::New();
mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory;
network::mojom::URLLoaderFactoryParamsPtr loader_params =
network::mojom::URLLoaderFactoryParams::New();
loader_params->process_id = network::OriginatingProcess::browser();
loader_params->is_orb_enabled = false;
loader_params->is_trusted = true;
if (create_network_observer_) {
url_loader_network_observer_.emplace();
loader_params->url_loader_network_observer =
url_loader_network_observer_->Bind();
}
network::mojom::NetworkContext* network_context =
g_browser_process->system_network_context_manager()->GetContext();
network_context->CreateURLLoaderFactory(
url_loader_factory.InitWithNewPipeAndPassReceiver(),
std::move(loader_params));
params->url_loader_factory = std::move(url_loader_factory);
mojo::PendingRemote<network::mojom::HostResolver> host_resolver;
network_context->CreateHostResolver(
{}, host_resolver.InitWithNewPipeAndPassReceiver());
params->host_resolver = std::move(host_resolver);
params->use_network_observer_from_url_loader_factory =
create_network_observer_;
return params;
}
// static
raw_ptr<UtilityProcessWrapper> UtilityProcessWrapper::FromProcessId(
base::ProcessId pid) {

View File

@@ -9,6 +9,7 @@
#include <memory>
#include <string>
#include "base/callback_list.h"
#include "base/containers/id_map.h"
#include "base/environment.h"
#include "base/memory/weak_ptr.h"
@@ -99,6 +100,11 @@ class UtilityProcessWrapper final
void OnServiceProcessDisconnected(uint32_t exit_code,
const std::string& description);
// Creates and sends a new URLLoaderFactory to the utility process.
// Called after Network Service restart to update the factory.
void CreateAndSendURLLoaderFactory(bool crashed);
node::mojom::URLLoaderFactoryParamsPtr CreateURLLoaderFactoryParams();
base::ProcessId pid_ = base::kNullProcessId;
#if BUILDFLAG(IS_WIN)
// Non-owning handles, these will be closed when the
@@ -111,12 +117,14 @@ class UtilityProcessWrapper final
bool connector_closed_ = false;
bool terminated_ = false;
bool killed_ = false;
bool create_network_observer_ = false;
std::unique_ptr<mojo::Connector> connector_;
blink::MessagePortDescriptor host_port_;
mojo::Receiver<node::mojom::NodeServiceClient> receiver_{this};
mojo::Remote<node::mojom::NodeService> node_service_remote_;
std::optional<electron::URLLoaderNetworkObserver>
url_loader_network_observer_;
base::CallbackListSubscription network_service_gone_subscription_;
base::WeakPtrFactory<UtilityProcessWrapper> weak_factory_{this};
};

View File

@@ -30,6 +30,7 @@
#include "components/proxy_config/pref_proxy_config_tracker_impl.h"
#include "components/proxy_config/proxy_config_pref_names.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h" // nogncheck
#include "content/browser/network_service_instance_impl.h" // nogncheck
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/cors_origin_pattern_setter.h"
#include "content/public/browser/host_zoom_map.h"
@@ -407,10 +408,18 @@ ElectronBrowserContext::ElectronBrowserContext(
extension_system->FinishInitialization();
}
#endif
// Subscribe to Network Service process gone notifications to reset the
// cached URLLoaderFactory when the Network Service crashes or restarts.
network_service_gone_subscription_ =
content::RegisterNetworkServiceProcessGoneHandler(base::BindRepeating(
&ElectronBrowserContext::OnNetworkServiceProcessGone,
weak_factory_.GetWeakPtr()));
}
ElectronBrowserContext::~ElectronBrowserContext() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
NotifyWillBeDestroyed();
// Notify any keyed services of browser context destruction.
@@ -568,6 +577,12 @@ content::PreconnectManager* ElectronBrowserContext::GetPreconnectManager() {
return preconnect_manager_.get();
}
void ElectronBrowserContext::OnNetworkServiceProcessGone(bool /* crashed */) {
// Clear the cached URLLoaderFactory so the next request creates a new one
// from the new NetworkContext.
url_loader_factory_.reset();
}
scoped_refptr<network::SharedURLLoaderFactory>
ElectronBrowserContext::GetURLLoaderFactory() {
if (url_loader_factory_)

View File

@@ -13,7 +13,9 @@
#include <variant>
#include <vector>
#include "base/callback_list.h"
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/media_stream_request.h"
#include "mojo/public/cpp/bindings/remote.h"
@@ -184,6 +186,9 @@ class ElectronBrowserContext : public content::BrowserContext {
// Initialize pref registry.
void InitPrefs();
// Called when the Network Service process crashes or restarts.
void OnNetworkServiceProcessGone(bool crashed);
scoped_refptr<ValueMapPrefStore> in_memory_pref_store_;
std::unique_ptr<CookieChangeNotifier> cookie_change_notifier_;
std::unique_ptr<PrefService> prefs_;
@@ -207,6 +212,9 @@ class ElectronBrowserContext : public content::BrowserContext {
// Shared URLLoaderFactory.
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
// Subscription to Network Service process gone notifications.
base::CallbackListSubscription network_service_gone_subscription_;
network::mojom::SSLConfigPtr ssl_config_;
mojo::Remote<network::mojom::SSLConfigClient> ssl_config_client_;
@@ -214,6 +222,8 @@ class ElectronBrowserContext : public content::BrowserContext {
// In-memory cache that holds objects that have been granted permissions.
DevicePermissionMap granted_devices_;
base::WeakPtrFactory<ElectronBrowserContext> weak_factory_{this};
};
} // namespace electron

View File

@@ -13,7 +13,6 @@
#include "shell/browser/javascript_environment.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/file_dialog.h"
#include "shell/common/electron_paths.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_helper/dictionary.h"
@@ -82,18 +81,14 @@ class FileChooserDialog : public ui::SelectFileDialog::Listener {
ui::SelectFileDialog::FileTypeInfo file_info =
GetFilterInfo(settings.filters);
ApplySettings(settings);
base::FilePath default_path = settings.default_path.empty()
? electron::GetDefaultPath()
: settings.default_path;
dialog_->SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE,
base::UTF8ToUTF16(settings.title), default_path,
&file_info /* file_types */, 0 /* file_type_index */,
base::FilePath::StringType() /* default_extension */,
settings.parent_window
? settings.parent_window->GetNativeWindow()
: nullptr,
nullptr);
dialog_->SelectFile(
ui::SelectFileDialog::SELECT_SAVEAS_FILE,
base::UTF8ToUTF16(settings.title), settings.default_path,
&file_info /* file_types */, 0 /* file_type_index */,
base::FilePath::StringType() /* default_extension */,
settings.parent_window ? settings.parent_window->GetNativeWindow()
: nullptr,
nullptr);
}
void RunSaveDialog(gin_helper::Promise<gin_helper::Dictionary> promise,
@@ -113,13 +108,9 @@ class FileChooserDialog : public ui::SelectFileDialog::Listener {
ui::SelectFileDialog::FileTypeInfo file_info =
GetFilterInfo(settings.filters);
ApplySettings(settings);
base::FilePath default_path = settings.default_path.empty()
? electron::GetDefaultPath()
: settings.default_path;
dialog_->SelectFile(
GetDialogType(settings.properties), base::UTF8ToUTF16(settings.title),
default_path, &file_info, 0 /* file_type_index */,
settings.default_path, &file_info, 0 /* file_type_index */,
base::FilePath::StringType() /* default_extension */,
settings.parent_window ? settings.parent_window->GetNativeWindow()
: nullptr,

View File

@@ -21,7 +21,6 @@
#include "content/public/browser/browser_thread.h"
#include "electron/mas.h"
#include "shell/browser/native_window.h"
#include "shell/common/electron_paths.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/promise.h"
@@ -187,18 +186,14 @@ void SetupDialog(NSSavePanel* dialog, const DialogSettings& settings) {
[dialog setShowsTagField:settings.shows_tag_field];
base::FilePath default_path = settings.default_path.empty()
? electron::GetDefaultPath()
: settings.default_path;
NSString* default_dir = nil;
NSString* default_filename = nil;
if (!default_path.empty()) {
if (!settings.default_path.empty()) {
electron::ScopedAllowBlockingForElectron allow_blocking;
if (base::DirectoryExists(default_path)) {
default_dir = base::SysUTF8ToNSString(default_path.value());
if (base::DirectoryExists(settings.default_path)) {
default_dir = base::SysUTF8ToNSString(settings.default_path.value());
} else {
if (default_path.IsAbsolute()) {
if (settings.default_path.IsAbsolute()) {
default_dir =
base::SysUTF8ToNSString(settings.default_path.DirName().value());
}

View File

@@ -21,7 +21,6 @@
#include "base/win/registry.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/win/dialog_thread.h"
#include "shell/common/electron_paths.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/promise.h"
@@ -107,12 +106,8 @@ static HRESULT ShowFileDialog(IFileDialog* dialog,
static void ApplySettings(IFileDialog* dialog, const DialogSettings& settings) {
std::wstring file_part;
base::FilePath default_path = settings.default_path.empty()
? electron::GetDefaultPath()
: settings.default_path;
if (!IsDirectory(default_path))
file_part = default_path.BaseName().value();
if (!IsDirectory(settings.default_path))
file_part = settings.default_path.BaseName().value();
dialog->SetFileName(file_part.c_str());
@@ -154,8 +149,8 @@ static void ApplySettings(IFileDialog* dialog, const DialogSettings& settings) {
}
}
if (default_path.IsAbsolute()) {
SetDefaultFolder(dialog, default_path);
if (settings.default_path.IsAbsolute()) {
SetDefaultFolder(dialog, settings.default_path);
}
}

View File

@@ -5,8 +5,12 @@
#include "base/command_line.h"
#include "base/dcheck_is_on.h"
#include "base/logging.h"
#include "content/browser/network_service_instance_impl.h" // nogncheck
#include "content/public/browser/network_service_instance.h"
#include "content/public/common/content_switches.h"
#include "shell/common/callback_util.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/promise.h"
#include "shell/common/node_includes.h"
#include "v8/include/v8.h"
@@ -41,6 +45,18 @@ std::string GetLoggingDestination() {
return command_line->GetSwitchValueASCII(switches::kEnableLogging);
}
v8::Local<v8::Promise> SimulateNetworkServiceCrash(v8::Isolate* isolate) {
gin_helper::Promise<void> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
auto subscription = content::RegisterNetworkServiceProcessGoneHandler(
electron::AdaptCallbackForRepeating(
base::BindOnce([](gin_helper::Promise<void> promise,
bool crashed) { promise.Resolve(); },
std::move(promise))));
content::RestartNetworkService();
return handle;
}
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
@@ -49,6 +65,7 @@ void Initialize(v8::Local<v8::Object> exports,
gin_helper::Dictionary dict{isolate, exports};
dict.SetMethod("log", &Log);
dict.SetMethod("getLoggingDestination", &GetLoggingDestination);
dict.SetMethod("simulateNetworkServiceCrash", &SimulateNetworkServiceCrash);
}
} // namespace

View File

@@ -484,6 +484,7 @@ void SimpleURLLoaderWrapper::Clone(
void SimpleURLLoaderWrapper::Cancel() {
loader_.reset();
url_loader_factory_.reset();
pinned_wrapper_.Reset();
pinned_chunk_pipe_getter_.Reset();
// This ensures that no further callbacks will be called, so there's no need
@@ -750,6 +751,7 @@ void SimpleURLLoaderWrapper::OnComplete(bool success) {
// we would perform cleanup of the wrapper and we should bail out below.
if (self) {
loader_.reset();
url_loader_factory_.reset();
pinned_wrapper_.Reset();
pinned_chunk_pipe_getter_.Reset();
}

View File

@@ -114,18 +114,4 @@ void RegisterPathProvider() {
PATH_END);
}
base::FilePath GetDefaultPath() {
base::FilePath path;
ScopedAllowBlockingForElectron allow_blocking;
if (base::PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &path) &&
base::DirectoryExists(path))
return path;
if (base::PathService::Get(base::DIR_HOME, &path))
return path;
return base::FilePath();
}
} // namespace electron

View File

@@ -51,10 +51,6 @@ static_assert(PATH_START < PATH_END, "invalid PATH boundaries");
// Register the path provider with the base::PathService.
void RegisterPathProvider();
// Returns a default directory for file dialogs when no default path is
// provided.
base::FilePath GetDefaultPath();
} // namespace electron
#endif // ELECTRON_SHELL_COMMON_ELECTRON_PATHS_H_

View File

@@ -122,10 +122,9 @@ void NodeService::Initialize(
ParentPort::GetInstance()->Initialize(std::move(params->port));
URLLoaderBundle::GetInstance()->SetURLLoaderFactory(
std::move(params->url_loader_factory),
mojo::Remote(std::move(params->host_resolver)),
params->use_network_observer_from_url_loader_factory);
if (params->url_loader_factory_params) {
UpdateURLLoaderFactory(std::move(params->url_loader_factory_params));
}
js_env_ = std::make_unique<JavascriptEnvironment>(node_bindings_->uv_loop());
@@ -208,4 +207,12 @@ void NodeService::Initialize(
node_bindings_->StartPolling();
}
void NodeService::UpdateURLLoaderFactory(
node::mojom::URLLoaderFactoryParamsPtr params) {
URLLoaderBundle::GetInstance()->SetURLLoaderFactory(
std::move(params->url_loader_factory),
mojo::Remote(std::move(params->host_resolver)),
params->use_network_observer_from_url_loader_factory);
}
} // namespace electron

View File

@@ -65,6 +65,8 @@ class NodeService : public node::mojom::NodeService {
void Initialize(node::mojom::NodeServiceParamsPtr params,
mojo::PendingRemote<node::mojom::NodeServiceClient>
client_pending_remote) override;
void UpdateURLLoaderFactory(
node::mojom::URLLoaderFactoryParamsPtr params) override;
private:
// This needs to be initialized first so that it can be destroyed last

View File

@@ -10,14 +10,18 @@ import "services/network/public/mojom/host_resolver.mojom";
import "services/network/public/mojom/url_loader_factory.mojom";
import "third_party/blink/public/mojom/messaging/message_port_descriptor.mojom";
struct URLLoaderFactoryParams {
pending_remote<network.mojom.URLLoaderFactory> url_loader_factory;
pending_remote<network.mojom.HostResolver> host_resolver;
bool use_network_observer_from_url_loader_factory = false;
};
struct NodeServiceParams {
mojo_base.mojom.FilePath script;
array<string> args;
array<string> exec_args;
blink.mojom.MessagePortDescriptor port;
pending_remote<network.mojom.URLLoaderFactory> url_loader_factory;
pending_remote<network.mojom.HostResolver> host_resolver;
bool use_network_observer_from_url_loader_factory = false;
URLLoaderFactoryParams url_loader_factory_params;
};
interface NodeServiceClient {
@@ -28,4 +32,6 @@ interface NodeServiceClient {
interface NodeService {
Initialize(NodeServiceParams params,
pending_remote<NodeServiceClient> client_remote);
UpdateURLLoaderFactory(URLLoaderFactoryParams params);
};

View File

@@ -15,7 +15,7 @@ import { setTimeout } from 'node:timers/promises';
import { promisify } from 'node:util';
import { collectStreamBody, getResponse } from './lib/net-helpers';
import { ifdescribe, ifit, listen, waitUntil } from './lib/spec-helpers';
import { ifdescribe, ifit, isWayland, listen, waitUntil } from './lib/spec-helpers';
import { closeWindow, closeAllWindows } from './lib/window-helpers';
const fixturesPath = path.resolve(__dirname, 'fixtures');
@@ -587,7 +587,7 @@ describe('app module', () => {
});
// FIXME: re-enable this test on win32.
ifit(process.platform !== 'win32')('should emit render-process-gone event when renderer crashes', async () => {
ifit(process.platform !== 'win32' && !isWayland)('should emit render-process-gone event when renderer crashes', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
@@ -1437,7 +1437,7 @@ describe('app module', () => {
describe('getApplicationNameForProtocol()', () => {
// TODO: Linux CI doesn't have registered http & https handlers
ifit(!(process.env.CI && process.platform === 'linux'))('returns application names for common protocols', function () {
ifit(!(process.env.CI && process.platform === 'linux') && !isWayland)('returns application names for common protocols', function () {
// We can't expect particular app names here, but these protocols should
// at least have _something_ registered. Except on our Linux CI
// environment apparently.

View File

@@ -1688,4 +1688,61 @@ describe('net module', () => {
}
});
}
describe('Network Service crash recovery', () => {
const binding = process._linkedBinding('electron_common_testing');
it('should recover net.fetch after Network Service crash (main process)', async () => {
const serverUrl = await respondOnce.toSingleURL((request, response) => {
response.end('first');
});
const firstResponse = await net.fetch(serverUrl);
expect(firstResponse.ok).to.be.true();
expect(await firstResponse.text()).to.equal('first');
await binding.simulateNetworkServiceCrash();
// Wait for StoragePartitionImpl's NetworkContext disconnect handler to
// fire and reinitialize the context in the new Network Service.
await setTimeout(500);
const secondServerUrl = await respondOnce.toSingleURL((request, response) => {
response.end('second');
});
const secondResponse = await net.fetch(secondServerUrl);
expect(secondResponse.ok).to.be.true();
expect(await secondResponse.text()).to.equal('second');
});
it('should recover net.fetch after Network Service crash (utility process)', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'api', 'utility-process', 'network-restart-test.js'));
await once(child, 'spawn');
await once(child, 'message');
const firstServerUrl = await respondOnce.toSingleURL((request, response) => {
response.end('utility-first');
});
child.postMessage({ type: 'fetch', url: firstServerUrl });
const [firstResult] = await once(child, 'message');
expect(firstResult.ok).to.be.true();
expect(firstResult.body).to.equal('utility-first');
await binding.simulateNetworkServiceCrash();
// Needed for UpdateURLLoaderFactory IPC to propagate to the utility process
// and for any in-flight requests to settle
await setTimeout(500);
const secondServerUrl = await respondOnce.toSingleURL((request, response) => {
response.end('utility-second');
});
child.postMessage({ type: 'fetch', url: secondServerUrl });
const [secondResult] = await once(child, 'message');
expect(secondResult.ok).to.be.true();
expect(secondResult.body).to.equal('utility-second');
child.kill();
await once(child, 'exit');
});
});
});

View File

@@ -0,0 +1,24 @@
const { net } = require('electron');
process.parentPort.on('message', async (e) => {
const { type, url } = e.data;
if (type === 'fetch') {
try {
const response = await net.fetch(url);
const body = await response.text();
process.parentPort.postMessage({
ok: response.ok,
status: response.status,
body
});
} catch (error) {
process.parentPort.postMessage({
ok: false,
error: error.message
});
}
}
});
process.parentPort.postMessage({ type: 'ready' });

View File

@@ -25,6 +25,12 @@ const addOnly = <T>(fn: Function): T => {
export const ifit = (condition: boolean) => (condition ? it : addOnly<TestFunction>(it.skip));
export const ifdescribe = (condition: boolean) => (condition ? describe : addOnly<SuiteFunction>(describe.skip));
export const isWayland = process.platform === 'linux' && (
process.env.XDG_SESSION_TYPE === 'wayland' ||
!!process.env.WAYLAND_DISPLAY ||
process.argv.includes('--ozone-platform=wayland')
);
type CleanupFunction = (() => void) | (() => Promise<void>)
const cleanupFunctions: CleanupFunction[] = [];
export async function runCleanupFunctions () {