Compare commits

..

1 Commits

Author SHA1 Message Date
Niklas Wenzel
346d31f5e0 feat: make Chrome extensions work on custom protocols 2026-02-25 20:18:53 +01:00
50 changed files with 422 additions and 333 deletions

View File

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

View File

@@ -41,7 +41,7 @@ jobs:
permissions:
contents: read
container:
image: ghcr.io/electron/build:eac3529546ea8f3aa356d31e345715eef342233b
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
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:eac3529546ea8f3aa356d31e345715eef342233b
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
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:eac3529546ea8f3aa356d31e345715eef342233b
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
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:eac3529546ea8f3aa356d31e345715eef342233b
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
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: 'eac3529546ea8f3aa356d31e345715eef342233b'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
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=eac3529546ea8f3aa356d31e345715eef342233b" >> "$GITHUB_OUTPUT"
echo "build-image-sha=a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb" >> "$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: 'eac3529546ea8f3aa356d31e345715eef342233b'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
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: 'eac3529546ea8f3aa356d31e345715eef342233b'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
required: true
upload-to-storage:
description: 'Uploads to Azure storage'

View File

@@ -110,21 +110,6 @@ 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
nn-test:
uses: ./.github/workflows/pipeline-segment-node-nan-test.yml
permissions:

View File

@@ -30,14 +30,9 @@ 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 }}-${{ inputs.display-server }}-${{ github.ref_protected == true && github.run_id || github.ref }}
group: electron-test-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ inputs.is-asan }}-${{ github.ref_protected == true && github.run_id || github.ref }}
cancel-in-progress: ${{ github.ref_protected != true }}
permissions: {}
@@ -64,7 +59,7 @@ jobs:
fail-fast: false
matrix:
build-type: ${{ inputs.target-platform == 'macos' && fromJSON('["darwin","mas"]') || (inputs.target-platform == 'win' && fromJSON('["win"]') || fromJSON('["linux"]')) }}
shard: ${{ case(inputs.display-server == 'wayland', fromJSON('[1]'), inputs.target-platform == 'linux', fromJSON('[1, 2, 3]'), fromJSON('[1, 2]')) }}
shard: ${{ inputs.target-platform == 'linux' && fromJSON('[1, 2, 3]') || fromJSON('[1, 2]') }}
env:
BUILD_TYPE: ${{ matrix.build-type }}
TARGET_ARCH: ${{ inputs.target-arch }}
@@ -215,22 +210,7 @@ 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 }} ${{ 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
tests_files=$(node script/split-tests ${{ matrix.shard }} ${{ inputs.target-platform == 'linux' && 3 || 2 }})
# Run tests
if [ "${{ inputs.target-platform }}" != "linux" ]; then
@@ -265,11 +245,7 @@ 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
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
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
@@ -292,7 +268,7 @@ jobs:
if: always() && !cancelled()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
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) }}
name: test_artifacts_${{ 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: 'eac3529546ea8f3aa356d31e345715eef342233b'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
required: true
upload-to-storage:
description: 'Uploads to Azure storage'

View File

@@ -1556,6 +1556,19 @@ Enables full sandbox mode on the app. This means that all renderers will be laun
This method can only be called before app is ready.
### `app.enableExtensionsOnAllProtocols()`
Enables Chrome extensions on all protocols.
By default, Chrome extensions are enabled only for a select number of protocols
such as `http`, `https`, and `file`. Calling this function will enable Chrome extensions
on all protocols, including [custom protocols](protocol.md).
This can have security implications. For most apps, it is recommended to enable
this during development only and keep it disabled in production builds.
This method can only be called before app is ready.
### `app.isInApplicationsFolder()` _macOS_
Returns `boolean` - Whether the application is currently running from the

View File

@@ -30,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` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
* `showHiddenFiles` - Show hidden files in dialog.
* `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
@@ -102,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` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
* `showHiddenFiles` - Show hidden files in dialog.
* `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
@@ -185,7 +185,7 @@ dialog.showOpenDialog(mainWindow, {
* `showsTagField` boolean (optional) _macOS_ - Show the tags input box,
defaults to `true`.
* `properties` string[] (optional)
* `showHiddenFiles` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
* `showHiddenFiles` - Show hidden files in dialog.
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
* `treatPackageAsDirectory` _macOS_ - Treat packages, such as `.app` folders,
as a directory instead of a file.
@@ -215,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[] (optional)
* `showHiddenFiles` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
* `showHiddenFiles` - Show hidden files in dialog.
* `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

@@ -80,12 +80,6 @@ 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,4 +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
feat_allow_enabling_extensions_on_all_protocols.patch

View File

@@ -0,0 +1,84 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Niklas Wenzel <dev@nikwen.de>
Date: Wed, 25 Feb 2026 16:24:03 +0100
Subject: feat: allow enabling extensions on all protocols
This allows us to use Chrome extensions on custom protocols.
The patch can't really be upstreamed, unfortunately, because there are
other URLPattern functions that we don't patch that Chrome needs.
Patching those properly would require replacing the bitmap logic in
URLPattern with a more flexible solution. This would be a larger effort
and Chromium might reject it for performance reasons.
See: https://source.chromium.org/chromium/chromium/src/+/main:extensions/common/url_pattern.h;l=53-74;drc=50dbcddad2f8e36ddfcec21d4551f389df425c37
This patch makes it work in the context of Electron.
diff --git a/extensions/common/url_pattern.cc b/extensions/common/url_pattern.cc
index 4054af728030306c5473f9a47e580595596768a0..38c3f5976a122e6e4b7e8512ef977242fa395d8d 100644
--- a/extensions/common/url_pattern.cc
+++ b/extensions/common/url_pattern.cc
@@ -133,6 +133,13 @@ std::string_view CanonicalizeHostForMatching(std::string_view host_piece) {
} // namespace
+bool URLPattern::enable_extensions_on_all_protocols_ = false;
+
+// static
+void URLPattern::EnableExtensionsOnAllProtocols() {
+ enable_extensions_on_all_protocols_ = true;
+}
+
// static
bool URLPattern::IsValidSchemeForExtensions(std::string_view scheme) {
for (auto* valid_scheme : kValidSchemes) {
@@ -140,11 +147,14 @@ bool URLPattern::IsValidSchemeForExtensions(std::string_view scheme) {
return true;
}
}
- return false;
+ return enable_extensions_on_all_protocols_;
}
// static
int URLPattern::GetValidSchemeMaskForExtensions() {
+ if (enable_extensions_on_all_protocols_) {
+ return SCHEME_ALL;
+ }
int result = 0;
for (int valid_scheme_mask : kValidSchemeMasks) {
result |= valid_scheme_mask;
@@ -401,7 +411,7 @@ bool URLPattern::IsValidScheme(std::string_view scheme) const {
}
}
- return false;
+ return enable_extensions_on_all_protocols_;
}
void URLPattern::SetPath(std::string_view path) {
diff --git a/extensions/common/url_pattern.h b/extensions/common/url_pattern.h
index 4d09251b0160644d86682ad3db7c41b50f360e6f..78978b82e37e080add6680300d09503acdb663db 100644
--- a/extensions/common/url_pattern.h
+++ b/extensions/common/url_pattern.h
@@ -96,6 +96,8 @@ class URLPattern {
// Returns the mask for all schemes considered valid for extensions.
static int GetValidSchemeMaskForExtensions();
+ static void EnableExtensionsOnAllProtocols();
+
explicit URLPattern(int valid_schemes);
// Convenience to construct a URLPattern from a string. If the string is not
@@ -251,6 +253,9 @@ class URLPattern {
// Get an error string for a ParseResult.
static const char* GetParseResultString(URLPattern::ParseResult parse_result);
+ protected:
+ static bool enable_extensions_on_all_protocols_;
+
private:
// Returns true if any of the `schemes` items matches our scheme.
bool MatchesAnyScheme(const std::vector<std::string>& schemes) const;

View File

@@ -1,20 +0,0 @@
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

@@ -1 +1,2 @@
chore_expose_ui_to_allow_electron_to_set_dock_side.patch
feat_allow_enabling_extension_panels_on_all_protocols.patch

View File

@@ -0,0 +1,41 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Niklas Wenzel <dev@nikwen.de>
Date: Wed, 25 Feb 2026 16:23:07 +0100
Subject: feat: allow enabling extension panels on all protocols
This allows us to show Chrome extension panels on pages served over
custom protocols.
diff --git a/front_end/core/root/Runtime.ts b/front_end/core/root/Runtime.ts
index 19824217973f002a52478c7fa63a3faa217b0c63..6406fff61691fab0d2a8cc5344aaee743937c84e 100644
--- a/front_end/core/root/Runtime.ts
+++ b/front_end/core/root/Runtime.ts
@@ -639,6 +639,7 @@ export type HostConfig = Platform.TypeScriptUtilities.RecursivePartial<{
* or guest mode, rather than a "normal" profile.
*/
isOffTheRecord: boolean,
+ devToolsExtensionsOnAllProtocols: boolean,
devToolsEnableOriginBoundCookies: HostConfigEnableOriginBoundCookies,
devToolsAnimationStylesInStylesTab: HostConfigAnimationStylesInStylesTab,
thirdPartyCookieControls: HostConfigThirdPartyCookieControls,
diff --git a/front_end/panels/common/ExtensionServer.ts b/front_end/panels/common/ExtensionServer.ts
index 0a5ec620b135b128013d6ddbb5299f9a5813f122..1a6118b4fa1607a634720b5579a50d1b4478b60a 100644
--- a/front_end/panels/common/ExtensionServer.ts
+++ b/front_end/panels/common/ExtensionServer.ts
@@ -12,6 +12,7 @@ import * as Host from '../../core/host/host.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as Platform from '../../core/platform/platform.js';
import * as _ProtocolClient from '../../core/protocol_client/protocol_client.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
+import * as Root from '../../core/root/root.js';
import * as SDK from '../../core/sdk/sdk.js';
import type * as Protocol from '../../generated/protocol.js';
import * as Bindings from '../../models/bindings/bindings.js';
@@ -1607,7 +1608,7 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
return false;
}
- if (!kPermittedSchemes.includes(parsedURL.protocol)) {
+ if (!Root.Runtime.hostConfig.devToolsExtensionsOnAllProtocols && !kPermittedSchemes.includes(parsedURL.protocol)) {
return false;
}

View File

@@ -1,30 +0,0 @@
#!/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

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

View File

@@ -86,6 +86,10 @@
#include "v8/include/cppgc/allocation.h"
#include "v8/include/v8-traced-handle.h"
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#include "extensions/common/url_pattern.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "base/strings/utf_string_conversions.h"
#include "shell/browser/notifications/win/windows_toast_activator.h"
@@ -1546,6 +1550,28 @@ void App::EnableSandbox(gin_helper::ErrorThrower thrower) {
command_line->AppendSwitch(switches::kEnableSandbox);
}
void App::EnableExtensionsOnAllProtocols(gin_helper::ErrorThrower thrower) {
if (Browser::Get()->is_ready()) {
thrower.ThrowError(
"app.enableExtensionsOnAllProtocols() can only be called "
"before app is ready");
return;
}
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
enable_extensions_on_all_protocols_ = true;
URLPattern::EnableExtensionsOnAllProtocols();
#endif
}
bool App::AreExtensionsEnabledOnAllProtocols() const {
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
return enable_extensions_on_all_protocols_;
#else
return false;
#endif
}
v8::Local<v8::Promise> App::SetProxy(gin::Arguments* args) {
v8::Isolate* isolate = args->isolate();
gin_helper::Promise<void> promise(isolate);
@@ -1949,6 +1975,8 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
&App::IsHardwareAccelerationEnabled)
.SetMethod("disableDomainBlockingFor3DAPIs",
&App::DisableDomainBlockingFor3DAPIs)
.SetMethod("enableExtensionsOnAllProtocols",
&App::EnableExtensionsOnAllProtocols)
.SetMethod("getFileIcon", &App::GetFileIcon)
.SetMethod("getAppMetrics", &App::GetAppMetrics)
.SetMethod("getGPUFeatureStatus", &App::GetGPUFeatureStatus)

View File

@@ -89,6 +89,8 @@ class App final : public gin::Wrappable<App>,
static bool IsPackaged();
bool AreExtensionsEnabledOnAllProtocols() const;
App();
~App() override;
@@ -236,6 +238,7 @@ class App final : public gin::Wrappable<App>,
v8::Local<v8::Promise> GetGPUInfo(v8::Isolate* isolate,
const std::string& info_type);
void EnableSandbox(gin_helper::ErrorThrower thrower);
void EnableExtensionsOnAllProtocols(gin_helper::ErrorThrower thrower);
void SetUserAgentFallback(const std::string& user_agent);
std::string GetUserAgentFallback();
v8::Local<v8::Promise> SetProxy(gin::Arguments* args);
@@ -293,6 +296,10 @@ class App final : public gin::Wrappable<App>,
bool disable_domain_blocking_for_3DAPIs_ = false;
bool watch_singleton_socket_on_ready_ = false;
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
bool enable_extensions_on_all_protocols_ = false;
#endif
std::unique_ptr<content::ScopedAccessibilityMode> scoped_accessibility_mode_;
};

View File

@@ -14,7 +14,6 @@
#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"
@@ -73,8 +72,7 @@ UtilityProcessWrapper::UtilityProcessWrapper(
base::FilePath current_working_directory,
bool use_plugin_helper,
bool create_network_observer,
bool disclaim_responsibility)
: create_network_observer_(create_network_observer) {
bool disclaim_responsibility) {
#if BUILDFLAG(IS_WIN)
base::win::ScopedHandle stdout_write(nullptr);
base::win::ScopedHandle stderr_write(nullptr);
@@ -214,15 +212,32 @@ UtilityProcessWrapper::UtilityProcessWrapper(
connector_->set_connection_error_handler(base::BindOnce(
&UtilityProcessWrapper::CloseConnectorPort, weak_factory_.GetWeakPtr()));
params->url_loader_factory_params = CreateURLLoaderFactoryParams();
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;
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() {
@@ -414,44 +429,6 @@ 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,7 +9,6 @@
#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"
@@ -100,11 +99,6 @@ 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
@@ -117,14 +111,12 @@ 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

@@ -617,6 +617,12 @@ void ElectronBrowserClient::AppendExtraCommandLineSwitches(
command_line->AppendSwitch(switches::kServiceWorkerPreload);
}
}
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
if (api::App::Get()->AreExtensionsEnabledOnAllProtocols()) {
command_line->AppendSwitch(switches::kEnableExtensionsOnAllProtocols);
}
#endif
}
}

View File

@@ -30,7 +30,6 @@
#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"
@@ -408,18 +407,10 @@ 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.
@@ -577,12 +568,6 @@ 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,9 +13,7 @@
#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"
@@ -186,9 +184,6 @@ 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_;
@@ -212,9 +207,6 @@ 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_;
@@ -222,8 +214,6 @@ 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

@@ -371,7 +371,7 @@ void HandleToastActivation(const std::wstring& invoked_args,
int action_index = -1;
if (!action_index_str.empty()) {
base::StringToInt(base::WideToUTF8(action_index_str), &action_index);
action_index = std::stoi(action_index_str);
}
std::string reply_text;

View File

@@ -44,6 +44,7 @@
#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "shell/browser/api/electron_api_app.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/net/asar/asar_url_loader_factory.h"
@@ -865,6 +866,8 @@ void InspectableWebContents::GetSyncInformation(DispatchCallback callback) {
void InspectableWebContents::GetHostConfig(DispatchCallback callback) {
base::DictValue response_dict;
response_dict.Set("devToolsExtensionsOnAllProtocols",
api::App::Get()->AreExtensionsEnabledOnAllProtocols());
base::Value response = base::Value(std::move(response_dict));
std::move(callback).Run(&response);
}

View File

@@ -5,12 +5,8 @@
#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"
@@ -45,18 +41,6 @@ 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,
@@ -65,7 +49,6 @@ 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,7 +484,6 @@ 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
@@ -751,7 +750,6 @@ 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

@@ -8,6 +8,7 @@
#include <string_view>
#include "base/strings/cstring_view.h"
#include "electron/buildflags/buildflags.h"
namespace electron {
@@ -270,6 +271,12 @@ inline constexpr base::cstring_view kStreamingSchemes = "streaming-schemes";
// Register schemes as supporting V8 code cache.
inline constexpr base::cstring_view kCodeCacheSchemes = "code-cache-schemes";
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
// Enable Chrome extensions on all protocols.
inline constexpr base::cstring_view kEnableExtensionsOnAllProtocols =
"enable-extensions-on-all-protocols";
#endif
// The browser process app model ID
inline constexpr base::cstring_view kAppUserModelId = "app-user-model-id";

View File

@@ -162,6 +162,11 @@ RendererClientBase::RendererClientBase() {
ParseSchemesCLISwitch(command_line, switches::kSecureSchemes);
for (const std::string& scheme : secure_schemes_list)
url::AddSecureScheme(scheme.data());
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
// Parse --enable-extensions-on-all-protocols
if (command_line->HasSwitch(switches::kEnableExtensionsOnAllProtocols))
URLPattern::EnableExtensionsOnAllProtocols();
#endif
// We rely on the unique process host id which is notified to the
// renderer process via command line switch from the content layer,
// if this switch is removed from the content layer for some reason,

View File

@@ -122,9 +122,10 @@ void NodeService::Initialize(
ParentPort::GetInstance()->Initialize(std::move(params->port));
if (params->url_loader_factory_params) {
UpdateURLLoaderFactory(std::move(params->url_loader_factory_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);
js_env_ = std::make_unique<JavascriptEnvironment>(node_bindings_->uv_loop());
@@ -207,12 +208,4 @@ 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,8 +65,6 @@ 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,18 +10,14 @@ 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;
URLLoaderFactoryParams url_loader_factory_params;
pending_remote<network.mojom.URLLoaderFactory> url_loader_factory;
pending_remote<network.mojom.HostResolver> host_resolver;
bool use_network_observer_from_url_loader_factory = false;
};
interface NodeServiceClient {
@@ -32,6 +28,4 @@ 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, isWayland, listen, waitUntil } from './lib/spec-helpers';
import { ifdescribe, ifit, 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' && !isWayland)('should emit render-process-gone event when renderer crashes', async () => {
ifit(process.platform !== 'win32')('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') && !isWayland)('returns application names for common protocols', function () {
ifit(!(process.env.CI && process.platform === 'linux'))('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.
@@ -1765,6 +1765,15 @@ describe('app module', () => {
});
});
describe('enableExtensionsOnAllProtocols() API', () => {
// Proper tests are in extensions-spec.ts
it('throws when called after app is ready', () => {
expect(() => {
app.enableExtensionsOnAllProtocols();
}).to.throw(/before app is ready/);
});
});
describe('disableDomainBlockingFor3DAPIs() API', () => {
it('throws when called after app is ready', () => {
expect(() => {

View File

@@ -1688,61 +1688,4 @@ 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

@@ -3,6 +3,7 @@ import { app, session, webFrameMain, BrowserWindow, ipcMain, WebContents, Extens
import { expect } from 'chai';
import * as WebSocket from 'ws';
import { spawn } from 'node:child_process';
import { once } from 'node:events';
import * as fs from 'node:fs/promises';
import * as http from 'node:http';
@@ -1338,4 +1339,26 @@ describe('chrome extensions', () => {
});
});
});
describe('custom protocol', () => {
async function runFixture (name: string) {
const appProcess = spawn(process.execPath, [(path.join(fixtures, 'extensions', name, 'main.js'))]);
let output = '';
appProcess.stdout.on('data', (data) => { output += data; });
await once(appProcess.stdout, 'end');
return output.trim();
};
it('loads DevTools extensions on custom protocols with app.enableExtensionsOnAllProtocols() and runs content and background scripts', async () => {
const output = await runFixture('custom-protocol');
expect(output).to.equal('Title: MESSAGE RECEIVED');
});
it('loads DevTools panels on custom protocols with app.enableExtensionsOnAllProtocols()', async () => {
const output = await runFixture('custom-protocol-panel');
expect(output).to.equal('ELECTRON TEST PANEL created');
});
});
});

View File

@@ -1,24 +0,0 @@
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

@@ -0,0 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="devtools.js"></script>
</head>
</html>

View File

@@ -0,0 +1,4 @@
/* global chrome */
chrome.devtools.panels.create('ELECTRON TEST PANEL', '', 'panel.html');
console.log('ELECTRON TEST PANEL created');

View File

@@ -0,0 +1,6 @@
{
"name": "custom-protocol-panel",
"version": "1.0",
"devtools_page": "devtools.html",
"manifest_version": 3
}

View File

@@ -0,0 +1,4 @@
<!doctype html>
<body>
DevTools panel
</body>

View File

@@ -0,0 +1,48 @@
const { app, BrowserWindow, protocol, session } = require('electron/main');
const { once } = require('node:events');
const path = require('node:path');
const html = '<html><body><h1>EMPTY PAGE</h1></body></html>';
const scheme = 'custom';
protocol.registerSchemesAsPrivileged([
{
scheme,
privileges: {
standard: true,
secure: true,
allowServiceWorkers: true,
supportFetchAPI: true,
bypassCSP: false,
corsEnabled: true,
stream: true
}
}
]);
app.enableExtensionsOnAllProtocols();
app.whenReady().then(async () => {
const ses = session.defaultSession;
ses.protocol.handle(scheme, () => new Response(html, {
headers: { 'Content-Type': 'text/html' }
}));
await ses.extensions.loadExtension(path.join(__dirname, 'extension'));
const win = new BrowserWindow();
win.webContents.openDevTools();
await once(win.webContents, 'devtools-opened');
win.devToolsWebContents.on('console-message', ({ message }) => {
if (message === 'ELECTRON TEST PANEL created') {
console.log(message);
app.quit();
}
});
await win.loadURL(`${scheme}://app/`);
});

View File

@@ -0,0 +1,7 @@
/* global chrome */
chrome.runtime.onMessage.addListener((_message, sender, reply) => {
reply({
text: 'MESSAGE RECEIVED',
senderTabId: sender.tab && sender.tab.id
});
});

View File

@@ -0,0 +1,5 @@
/* global chrome */
chrome.runtime.sendMessage({ text: 'hello from content script' }, (response) => {
if (!response || !response.text) return;
document.title = response.text;
});

View File

@@ -0,0 +1,15 @@
{
"name": "custom-protocol",
"version": "1.0",
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_script.js"],
"run_at": "document_start"
}
],
"manifest_version": 3
}

View File

@@ -0,0 +1,42 @@
const { app, BrowserWindow, protocol, session } = require('electron/main');
const path = require('node:path');
const html = '<html><body><h1>EMPTY PAGE</h1></body></html>';
const scheme = 'example';
protocol.registerSchemesAsPrivileged([
{
scheme,
privileges: {
standard: true,
secure: true,
allowServiceWorkers: true,
supportFetchAPI: true,
bypassCSP: false,
corsEnabled: true,
stream: true
}
}
]);
app.enableExtensionsOnAllProtocols();
app.whenReady().then(async () => {
const ses = session.defaultSession;
ses.protocol.handle(scheme, () => new Response(html, {
headers: { 'Content-Type': 'text/html' }
}));
await ses.extensions.loadExtension(path.join(__dirname, 'extension'));
const win = new BrowserWindow();
win.on('page-title-updated', (_event, title) => {
console.log(`Title: ${title}`);
app.quit();
});
await win.loadURL(`${scheme}://app/`);
});

View File

@@ -25,12 +25,6 @@ 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 () {

View File

@@ -28,7 +28,7 @@ describe('node feature', () => {
expect(msg).to.equal('message');
});
it('Has its module search paths restricted', async () => {
it('Has its module searth paths restricted', async () => {
const child = childProcess.fork(path.join(fixtures, 'module', 'module-paths.js'));
const [msg] = await once(child, 'message');
expect(msg.length).to.equal(2);