Compare commits

..

1 Commits

Author SHA1 Message Date
George Xu
a7da613c70 refactor: drive useSystemPicker via upstream native picker
Upstream Chromium now owns the full SCContentSharingPicker lifecycle
via NativeScreenCapturePickerMac and the public
content::desktop_capture::OpenNativeScreenCapturePicker API, making
Electron's 392-line patch entirely redundant.

Route setDisplayMediaRequestHandler({ useSystemPicker: true }) through
the upstream API: forward the opt into ElectronBrowserContext, and when
the system picker is available on macOS 15+, call
OpenNativeScreenCapturePicker from ChooseDisplayMediaDevice and
complete the MediaResponseCallback with the upstream-issued picker id
instead of Electron's window:-4:N sentinel.

Also enable media::kUseSCContentSharingPicker on macOS 15+ via
feature_list_mac.mm so upstream constructs the picker instance.

Deletes:
- patches/chromium/feat_allow_usage_of_sccontentsharingpicker_on_supported_platforms.patch
- The fakeVideoWindowId / kMacOsNativePickerId / systemPickerVideoSource
  sentinel in lib/browser/api/session.ts
- The _setDisplayMediaRequestHandler internal binding

Notes: Rewrote useSystemPicker to use the upstream macOS native screen-capture picker directly. Deleted ~390 lines of Electron-specific Chromium patches.
2026-04-22 00:02:14 -07:00
36 changed files with 255 additions and 908 deletions

View File

@@ -101,21 +101,6 @@ runs:
git pack-refs
cd ..
# Pre-create the ThinLTO cache directory so lld-link does not need to
# call CreateDirectoryW through the bindflt filter driver, which can
# return ERROR_INVALID_PARAMETER under concurrent I/O on ARC runners.
# Discover the path from GN instead of hardcoding it so we stay in
# sync with `cache_dir` in build/config/compiler/BUILD.gn; skip the
# pre-create when ThinLTO is disabled (non-official builds).
$env:ELECTRON_DEPOT_TOOLS_DISABLE_LOG = "1"
$ltoFlag = e d gn desc out/Default //electron:electron_app ldflags 2>$null |
Select-String -Pattern '^/lldltocache:(.+)$' |
Select-Object -First 1
if ($ltoFlag) {
$cachePath = Join-Path 'out\Default' $ltoFlag.Matches[0].Groups[1].Value
New-Item -ItemType Directory -Force -Path $cachePath | Out-Null
}
$env:NINJA_SUMMARIZE_BUILD = 1
if ("${{ inputs.is-release }}" -eq "true") {
e build --target electron:release_build

View File

@@ -1,132 +0,0 @@
From a8afee1089ec2ae9ab5837b438d07338aefb3bc4 Mon Sep 17 00:00:00 2001
From: Samuel Attard <sam@electronjs.org>
Date: Wed, 22 Apr 2026 16:27:51 -0700
Subject: [PATCH] siso: retry transient ERROR_INVALID_PARAMETER when opening
ninja files on Windows
ManifestParser.Load fans out across all subninja files (~90k in a
Chromium build) at NumCPU parallelism. On Windows builders where out/
is served through a filesystem filter driver (e.g. bindflt/wcifs for
container bind mounts), CreateFileW can intermittently return
ERROR_INVALID_PARAMETER under this concurrent open burst. The previous
patch removes the redundant per-chunk re-open, but the single remaining
open per file can still hit the race; without a retry a single transient
failure aborts the entire manifest load.
Wrap the remaining os.Open call in readFile in a small Windows-only
retry for ERROR_INVALID_PARAMETER (5 attempts, 5-80ms backoff). Each
retry is logged via clog.Warningf and also written to stderr so it is
visible in CI step output where glog warnings are file-only by default.
Other platforms keep the direct os.Open path.
---
siso/toolsupport/ninjautil/file_parser.go | 3 +-
siso/toolsupport/ninjautil/openfile_other.go | 18 +++++++
.../toolsupport/ninjautil/openfile_windows.go | 50 +++++++++++++++++++
3 files changed, 69 insertions(+), 2 deletions(-)
create mode 100644 siso/toolsupport/ninjautil/openfile_other.go
create mode 100644 siso/toolsupport/ninjautil/openfile_windows.go
diff --git a/siso/toolsupport/ninjautil/file_parser.go b/siso/toolsupport/ninjautil/file_parser.go
index 6311666..324528d 100644
--- a/siso/toolsupport/ninjautil/file_parser.go
+++ b/siso/toolsupport/ninjautil/file_parser.go
@@ -7,7 +7,6 @@ package ninjautil
import (
"context"
"fmt"
- "os"
"runtime/trace"
"sync"
"time"
@@ -91,7 +90,7 @@ func (p *fileParser) parseFile(ctx context.Context, fname string) error {
// readFile reads a file of fname in parallel.
func (p *fileParser) readFile(ctx context.Context, fname string) ([]byte, error) {
defer trace.StartRegion(ctx, "ninja.read").End()
- f, err := os.Open(fname)
+ f, err := openFile(ctx, fname)
if err != nil {
return nil, err
}
diff --git a/siso/toolsupport/ninjautil/openfile_other.go b/siso/toolsupport/ninjautil/openfile_other.go
new file mode 100644
index 0000000..9fca690
--- /dev/null
+++ b/siso/toolsupport/ninjautil/openfile_other.go
@@ -0,0 +1,18 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//go:build !windows
+
+package ninjautil
+
+import (
+ "context"
+ "os"
+)
+
+// openFile opens fname for reading.
+// See openfile_windows.go for the Windows variant with transient-error retry.
+func openFile(ctx context.Context, fname string) (*os.File, error) {
+ return os.Open(fname)
+}
diff --git a/siso/toolsupport/ninjautil/openfile_windows.go b/siso/toolsupport/ninjautil/openfile_windows.go
new file mode 100644
index 0000000..f9d8e9d
--- /dev/null
+++ b/siso/toolsupport/ninjautil/openfile_windows.go
@@ -0,0 +1,50 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//go:build windows
+
+package ninjautil
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "time"
+
+ "golang.org/x/sys/windows"
+
+ "go.chromium.org/build/siso/o11y/clog"
+)
+
+// openFile opens fname for reading, retrying transient
+// ERROR_INVALID_PARAMETER failures.
+//
+// On Windows, CreateFileW can intermittently return
+// ERROR_INVALID_PARAMETER when the target lives behind a filesystem
+// filter driver (e.g. bindflt/wcifs for container bind mounts) under
+// highly concurrent opens. loadFile fans out across ~90k subninja
+// files at NumCPU parallelism, so a single transient failure would
+// otherwise abort the whole manifest load.
+func openFile(ctx context.Context, fname string) (*os.File, error) {
+ const maxAttempts = 5
+ delay := 5 * time.Millisecond
+ for i := 0; ; i++ {
+ f, err := os.Open(fname)
+ if err == nil {
+ return f, nil
+ }
+ if i+1 >= maxAttempts || !errors.Is(err, windows.ERROR_INVALID_PARAMETER) {
+ return nil, err
+ }
+ clog.Warningf(ctx, "open %s: %v; retrying (%d/%d) after %s", fname, err, i+1, maxAttempts, delay)
+ fmt.Fprintf(os.Stderr, "siso: open %s: %v; retrying (%d/%d) after %s\n", fname, err, i+1, maxAttempts, delay)
+ select {
+ case <-time.After(delay):
+ case <-ctx.Done():
+ return nil, context.Cause(ctx)
+ }
+ delay *= 2
+ }
+}
--
2.53.0

View File

@@ -398,7 +398,7 @@ jobs:
needs: [checkout-windows, build-siso-windows]
if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-windows }}
with:
build-runs-on: electron-arc-centralus-windows-amd64-32core
build-runs-on: electron-arc-centralus-windows-amd64-16core
clang-tidy-runs-on: electron-arc-centralus-linux-amd64-8core
test-runs-on: windows-latest
clang-tidy-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-windows.outputs.build-image-sha }}","options":"--user root --device /dev/fuse --cap-add SYS_ADMIN","volumes":["/mnt/win-cache:/mnt/win-cache"]}'
@@ -419,7 +419,7 @@ jobs:
needs: [checkout-windows, build-siso-windows]
if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-windows }}
with:
build-runs-on: electron-arc-centralus-windows-amd64-32core
build-runs-on: electron-arc-centralus-windows-amd64-16core
test-runs-on: windows-latest
target-platform: win
target-arch: x86
@@ -438,7 +438,7 @@ jobs:
needs: [checkout-windows, build-siso-windows]
if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-windows }}
with:
build-runs-on: electron-arc-centralus-windows-amd64-32core
build-runs-on: electron-arc-centralus-windows-amd64-16core
test-runs-on: windows-11-arm
target-platform: win
target-arch: arm64

View File

@@ -82,7 +82,7 @@ jobs:
needs: [checkout-windows, build-siso-windows]
with:
environment: production-release
build-runs-on: electron-arc-centralus-windows-amd64-32core
build-runs-on: electron-arc-centralus-windows-amd64-16core
target-platform: win
target-arch: x64
is-release: true
@@ -101,7 +101,7 @@ jobs:
needs: [checkout-windows, build-siso-windows]
with:
environment: production-release
build-runs-on: electron-arc-centralus-windows-amd64-32core
build-runs-on: electron-arc-centralus-windows-amd64-16core
target-platform: win
target-arch: arm64
is-release: true
@@ -120,7 +120,7 @@ jobs:
needs: [checkout-windows, build-siso-windows]
with:
environment: production-release
build-runs-on: electron-arc-centralus-windows-amd64-32core
build-runs-on: electron-arc-centralus-windows-amd64-16core
target-platform: win
target-arch: x86
is-release: true

View File

@@ -5,22 +5,6 @@ import * as deprecate from '@electron/internal/common/deprecate';
import { net } from 'electron/main';
const { fromPartition, fromPath, Session } = process._linkedBinding('electron_browser_session');
const { isDisplayMediaSystemPickerAvailable } = process._linkedBinding('electron_browser_desktop_capturer');
// Fake video window that activates the native system picker
// This is used to get around the need for a screen/window
// id in Chrome's desktopCapturer.
let fakeVideoWindowId = -1;
// See content/public/browser/desktop_media_id.h
const kMacOsNativePickerId = -4;
const systemPickerVideoSource = Object.create(null);
Object.defineProperty(systemPickerVideoSource, 'id', {
get() {
return `window:${kMacOsNativePickerId}:${fakeVideoWindowId--}`;
}
});
systemPickerVideoSource.name = '';
Object.freeze(systemPickerVideoSource);
Session.prototype._init = function () {
addIpcDispatchListeners(this);
@@ -45,18 +29,6 @@ Session.prototype.fetch = function (input: RequestInfo, init?: RequestInit) {
return fetchWithSession(input, init, this, net.request);
};
Session.prototype.setDisplayMediaRequestHandler = function (handler, opts) {
if (!handler) return this._setDisplayMediaRequestHandler(handler, opts);
this._setDisplayMediaRequestHandler(async (req, callback) => {
if (opts && opts.useSystemPicker && isDisplayMediaSystemPickerAvailable()) {
return callback({ video: systemPickerVideoSource });
}
return handler(req, callback);
}, opts);
};
const getPreloadsDeprecated = deprecate.warnOnce('session.getPreloads', 'session.getPreloadScripts');
Session.prototype.getPreloads = function () {
getPreloadsDeprecated();

View File

@@ -56,7 +56,7 @@ export function removeFunction<T extends Function>(fn: T, removedName: string):
const warn = warnOnce(`${fn.name} function`);
return function (this: any) {
warn();
return fn.apply(this, arguments);
fn.apply(this, arguments);
} as unknown as typeof fn;
}

View File

@@ -6,11 +6,11 @@
"install-electron": "install.js"
},
"dependencies": {
"@electron/get": "^5.0.0",
"@electron/get": "^4.0.3",
"@types/node": "^24.9.0",
"extract-zip": "^2.0.1"
},
"engines": {
"node": ">= 22.12.0"
"node": ">= 12.20.55"
}
}

View File

@@ -78,7 +78,7 @@
"gn-typescript-definitions": "npm run create-typescript-definitions && node script/cp.mjs electron.d.ts",
"pre-flight": "pre-flight",
"gn-check": "node ./script/gn-check.js",
"gn-format": "node ./script/lint.js --gn --fix",
"gn-format": "python3 script/run-gn-format.py",
"precommit": "lint-staged",
"preinstall": "node -e 'process.exit(0)'",
"pretest": "npm run create-typescript-definitions",
@@ -117,7 +117,7 @@
],
"*.{gn,gni}": [
"npm run gn-check",
"node ./script/lint.js --gn --fix --only --"
"npm run gn-format"
],
"*.py": [
"node script/lint.js --py --fix --only --"

View File

@@ -110,7 +110,6 @@ chore_remove_reference_to_chrome_browser_themes.patch
feat_enable_customizing_symbol_color_in_framecaptionbutton.patch
build_allow_electron_mojom_interfaces_to_depend_on_blink.patch
osr_shared_texture_remove_keyed_mutex_on_win_dxgi.patch
feat_allow_usage_of_sccontentsharingpicker_on_supported_platforms.patch
chore_partial_revert_of.patch
fix_adjust_headless_mode_handling_in_native_widget.patch
refactor_unfilter_unresponsive_events.patch

View File

@@ -1,392 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samuel Attard <marshallofsound@electronjs.org>
Date: Thu, 8 Aug 2024 08:39:10 -0700
Subject: feat: allow usage of SCContentSharingPicker on supported platforms
This is implemented as a magic "window id" that instead of pulling an SCStream manually
instead farms out to the screen picker.
diff --git a/content/browser/media/capture/desktop_capture_device_mac.cc b/content/browser/media/capture/desktop_capture_device_mac.cc
index 120590a93bbc5a47e73c5d5515b7ad07b2364eb6..50a8b0dfe5400d1ab9da2893088583e4f815a140 100644
--- a/content/browser/media/capture/desktop_capture_device_mac.cc
+++ b/content/browser/media/capture/desktop_capture_device_mac.cc
@@ -28,7 +28,7 @@ class DesktopCaptureDeviceMac : public IOSurfaceCaptureDeviceBase {
~DesktopCaptureDeviceMac() override = default;
// IOSurfaceCaptureDeviceBase:
- void OnStart() override {
+ void OnStart(std::optional<bool> use_native_picker) override {
requested_format_ = capture_params().requested_format;
requested_format_.pixel_format = media::PIXEL_FORMAT_NV12;
DCHECK_GT(requested_format_.frame_size.GetArea(), 0);
diff --git a/content/browser/media/capture/io_surface_capture_device_base_mac.cc b/content/browser/media/capture/io_surface_capture_device_base_mac.cc
index 7a76550922d97a835dae15be6a951c91a9b351b0..ce3e22e437fa208652326798e12952c40372197a 100644
--- a/content/browser/media/capture/io_surface_capture_device_base_mac.cc
+++ b/content/browser/media/capture/io_surface_capture_device_base_mac.cc
@@ -20,7 +20,7 @@ void IOSurfaceCaptureDeviceBase::AllocateAndStart(
client_ = std::move(client);
capture_params_ = params;
- OnStart();
+ OnStart(params.use_native_picker);
}
void IOSurfaceCaptureDeviceBase::StopAndDeAllocate() {
diff --git a/content/browser/media/capture/io_surface_capture_device_base_mac.h b/content/browser/media/capture/io_surface_capture_device_base_mac.h
index e2771b7b281274cdcb601a5bc78a948ad592087b..48d116823a28213e50775f378e6ce04ce3af5072 100644
--- a/content/browser/media/capture/io_surface_capture_device_base_mac.h
+++ b/content/browser/media/capture/io_surface_capture_device_base_mac.h
@@ -25,7 +25,7 @@ class CONTENT_EXPORT IOSurfaceCaptureDeviceBase
~IOSurfaceCaptureDeviceBase() override;
// OnStart is called by AllocateAndStart.
- virtual void OnStart() = 0;
+ virtual void OnStart(std::optional<bool> use_native_picker) = 0;
// OnStop is called by StopAndDeAllocate.
virtual void OnStop() = 0;
diff --git a/content/browser/media/capture/screen_capture_kit_device_mac.mm b/content/browser/media/capture/screen_capture_kit_device_mac.mm
index 64ffc2642c003c8fb7f133ee43ba3e20d48ea543..92846d1b45c04c324014474ec6c02e71fe939fa7 100644
--- a/content/browser/media/capture/screen_capture_kit_device_mac.mm
+++ b/content/browser/media/capture/screen_capture_kit_device_mac.mm
@@ -31,6 +31,61 @@
std::optional<float>,
bool)>;
using ErrorCallback = base::RepeatingCallback<void(NSError*)>;
+using CancelCallback = base::RepeatingClosure;
+
+API_AVAILABLE(macos(15.0))
+@interface ScreenCaptureKitPickerHelper
+ : NSObject <SCContentSharingPickerObserver>
+
+- (void)contentSharingPicker:(SCContentSharingPicker *)picker
+ didCancelForStream:(SCStream *)stream;
+
+- (void)contentSharingPicker:(SCContentSharingPicker *)picker
+ didUpdateWithFilter:(SCContentFilter *)filter
+ forStream:(SCStream *)stream;
+
+- (void)contentSharingPickerStartDidFailWithError:(NSError *)error;
+
+@end
+
+@implementation ScreenCaptureKitPickerHelper {
+ base::RepeatingCallback<void(SCContentFilter *)> _pickerCallback;
+ ErrorCallback _errorCallback;
+ CancelCallback _cancelCallback;
+}
+
+- (void)contentSharingPicker:(SCContentSharingPicker *)picker
+ didCancelForStream:(SCStream *)stream {
+ // TODO: This doesn't appear to be called on Apple's side;
+ // implement this logic
+ _cancelCallback.Run();
+}
+
+- (void)contentSharingPicker:(SCContentSharingPicker *)picker
+ didUpdateWithFilter:(SCContentFilter *)filter
+ forStream:(SCStream *)stream {
+ if (stream == nil) {
+ _pickerCallback.Run(filter);
+ [picker removeObserver:self];
+ }
+}
+
+- (void)contentSharingPickerStartDidFailWithError:(NSError *)error {
+ _errorCallback.Run(error);
+}
+
+- (instancetype)initWithStreamPickCallback:(base::RepeatingCallback<void(SCContentFilter *)>)pickerCallback
+ cancelCallback:(CancelCallback)cancelCallback
+ errorCallback:(ErrorCallback)errorCallback {
+ if (self = [super init]) {
+ _pickerCallback = pickerCallback;
+ _cancelCallback = cancelCallback;
+ _errorCallback = errorCallback;
+ }
+ return self;
+}
+
+@end
namespace {
API_AVAILABLE(macos(12.3))
@@ -161,18 +216,22 @@ @interface ScreenCaptureKitDeviceHelper
: NSObject <SCStreamDelegate, SCStreamOutput>
- (instancetype)initWithSampleCallback:(SampleCallback)sampleCallback
+ cancelCallback:(CancelCallback)cancelCallback
errorCallback:(ErrorCallback)errorCallback;
@end
@implementation ScreenCaptureKitDeviceHelper {
SampleCallback _sampleCallback;
+ CancelCallback _cancelCallback;
ErrorCallback _errorCallback;
}
- (instancetype)initWithSampleCallback:(SampleCallback)sampleCallback
+ cancelCallback:(CancelCallback)cancelCallback
errorCallback:(ErrorCallback)errorCallback {
if (self = [super init]) {
_sampleCallback = sampleCallback;
+ _cancelCallback = cancelCallback;
_errorCallback = errorCallback;
}
return self;
@@ -264,14 +323,12 @@ class API_AVAILABLE(macos(12.3)) ScreenCaptureKitDeviceMac
explicit ScreenCaptureKitDeviceMac(
const DesktopMediaID& source,
- bool is_native_picker,
- SCContentFilter* filter,
+ [[maybe_unused]] bool is_native_picker,
+ [[maybe_unused]] SCContentFilter* filter,
StreamCallback stream_created_callback,
std::unique_ptr<content::PipScreenCaptureCoordinatorProxy>
pip_screen_capture_coordinator_proxy)
: source_(source),
- is_native_picker_session_(is_native_picker),
- filter_(filter),
stream_created_callback_(std::move(stream_created_callback)),
device_task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
pip_screen_capture_coordinator_proxy_(
@@ -280,21 +337,43 @@ explicit ScreenCaptureKitDeviceMac(
device_task_runner_,
base::BindRepeating(&ScreenCaptureKitDeviceMac::OnStreamSample,
weak_factory_.GetWeakPtr()));
+ CancelCallback cancel_callback = base::BindPostTask(
+ device_task_runner_,
+ base::BindRepeating(&ScreenCaptureKitDeviceMac::OnStreamError,
+ weak_factory_.GetWeakPtr(), nullptr));
ErrorCallback error_callback = base::BindPostTask(
device_task_runner_,
base::BindRepeating(&ScreenCaptureKitDeviceMac::OnStreamError,
weak_factory_.GetWeakPtr()));
helper_ = [[ScreenCaptureKitDeviceHelper alloc]
initWithSampleCallback:sample_callback
+ cancelCallback:cancel_callback
errorCallback:error_callback];
if (pip_screen_capture_coordinator_proxy_) {
pip_screen_capture_coordinator_proxy_->AddObserver(this);
}
+ if (@available(macOS 15.0, *)) {
+ auto picker_callback = base::BindPostTask(
+ device_task_runner_,
+ base::BindRepeating(&ScreenCaptureKitDeviceMac::OnContentFilterReady, weak_factory_.GetWeakPtr())
+ );
+ picker_helper_ = [[ScreenCaptureKitPickerHelper alloc] initWithStreamPickCallback:picker_callback cancelCallback:cancel_callback errorCallback:error_callback];
+ [[SCContentSharingPicker sharedPicker] addObserver:picker_helper_];
+ }
}
ScreenCaptureKitDeviceMac(const ScreenCaptureKitDeviceMac&) = delete;
ScreenCaptureKitDeviceMac& operator=(const ScreenCaptureKitDeviceMac&) =
delete;
~ScreenCaptureKitDeviceMac() override {
+ if (@available(macOS 15.0, *)) {
+ auto* picker = [SCContentSharingPicker sharedPicker];
+ ScreenCaptureKitDeviceMac::active_streams_--;
+ picker.maximumStreamCount = @(ScreenCaptureKitDeviceMac::active_streams_);
+ if (ScreenCaptureKitDeviceMac::active_streams_ == 0 && picker.active) {
+ picker.active = false;
+ [[SCContentSharingPicker sharedPicker] removeObserver:picker_helper_];
+ }
+ }
DCHECK(device_task_runner_->RunsTasksInCurrentSequence());
if (pip_screen_capture_coordinator_proxy_) {
pip_screen_capture_coordinator_proxy_->RemoveObserver(this);
@@ -385,7 +464,7 @@ void CreateStream(SCContentFilter* filter) {
return;
}
- if (@available(macOS 14.0, *)) {
+ if (@available(macOS 15.0, *)) {
// Update the content size. This step is neccessary when used together
// with SCContentSharingPicker. If the Chrome picker is used, it will
// change to retina resolution if applicable.
@@ -394,6 +473,9 @@ void CreateStream(SCContentFilter* filter) {
filter.contentRect.size.height * filter.pointPixelScale);
}
+ OnContentFilterReady(filter);
+ }
+ void OnContentFilterReady(SCContentFilter* filter) {
gfx::RectF dest_rect_in_frame;
actual_capture_format_ = capture_params().requested_format;
actual_capture_format_.pixel_format = media::PIXEL_FORMAT_NV12;
@@ -407,6 +489,7 @@ void CreateStream(SCContentFilter* filter) {
stream_ = [[SCStream alloc] initWithFilter:filter
configuration:config
delegate:helper_];
+
{
NSError* error = nil;
bool add_stream_output_result =
@@ -566,7 +649,7 @@ void OnStreamError(NSError* _Nullable error) {
if (fullscreen_module_) {
fullscreen_module_->Reset();
}
- OnStart();
+ OnStart(std::nullopt);
} else {
std::string error_string =
base::StrCat({"Stream delegate called didStopWithError: ",
@@ -656,32 +739,41 @@ void OnStateChanged(
}
// IOSurfaceCaptureDeviceBase:
- void OnStart() override {
+ void OnStart(std::optional<bool> use_native_picker) override {
DCHECK(device_task_runner_->RunsTasksInCurrentSequence());
- if (filter_) {
- // SCContentSharingPicker is used where filter_ is set on creation.
- CreateStream(filter_);
- } else {
- if (is_native_picker_session_) {
- client()->OnError(
- media::VideoCaptureError::kScreenCaptureKitFailedStartCapture,
- FROM_HERE,
- "Native picker session failed to start due to missing authorized "
- "filter");
+
+ if (@available(macOS 15.0, *)) {
+ constexpr bool DefaultUseNativePicker = true;
+ if (use_native_picker.value_or(DefaultUseNativePicker) &&
+ source_.id == DesktopMediaID::kMacOsNativePickerId &&
+ source_.window_id < 0) {
+ auto* picker = [SCContentSharingPicker sharedPicker];
+ ScreenCaptureKitDeviceMac::active_streams_++;
+ picker.maximumStreamCount = @(ScreenCaptureKitDeviceMac::active_streams_);
+ if (!picker.active) {
+ picker.active = true;
+ }
+ NSMutableArray<NSNumber*>* exclude_ns_windows = [NSMutableArray array];
+ [[[[NSApplication sharedApplication] windows] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSWindow* win, NSDictionary *bindings) {
+ return [win sharingType] == NSWindowSharingNone;
+ }]] enumerateObjectsUsingBlock:^(NSWindow* win, NSUInteger idx, BOOL *stop) {
+ [exclude_ns_windows addObject:@([win windowNumber])];
+ }];
+ picker.defaultConfiguration.excludedWindowIDs = exclude_ns_windows;
+ [picker present];
return;
}
- // Fall back to user-selected window/screen discovery for
- // non-native-picker sessions.
- auto content_callback = base::BindPostTask(
- device_task_runner_,
- base::BindRepeating(
- &ScreenCaptureKitDeviceMac::OnShareableContentCreated,
- weak_factory_.GetWeakPtr()));
- auto handler = ^(SCShareableContent* content, NSError* error) {
- content_callback.Run(content);
- };
- [SCShareableContent getShareableContentWithCompletionHandler:handler];
}
+
+ auto content_callback = base::BindPostTask(
+ device_task_runner_,
+ base::BindRepeating(
+ &ScreenCaptureKitDeviceMac::OnShareableContentCreated,
+ weak_factory_.GetWeakPtr()));
+ auto handler = ^(SCShareableContent* content, NSError* error) {
+ content_callback.Run(content);
+ };
+ [SCShareableContent getShareableContentWithCompletionHandler:handler];
}
void OnStop() override {
DCHECK(device_task_runner_->RunsTasksInCurrentSequence());
@@ -740,8 +832,7 @@ void ResetStreamTo(SCWindow* window) override {
private:
const DesktopMediaID source_;
- const bool is_native_picker_session_;
- SCContentFilter* const filter_;
+ static int active_streams_;
StreamCallback stream_created_callback_;
const scoped_refptr<base::SingleThreadTaskRunner> device_task_runner_;
@@ -758,6 +849,10 @@ void ResetStreamTo(SCWindow* window) override {
// Helper class that acts as output and delegate for `stream_`.
ScreenCaptureKitDeviceHelper* __strong helper_;
+ // Helper class that acts as an observer for SCContentSharingPicker
+ API_AVAILABLE(macos(15.0))
+ ScreenCaptureKitPickerHelper* __strong picker_helper_;
+
// This is used to detect when a captured presentation enters fullscreen mode.
// If this happens, the module will call the ResetStreamTo function.
std::unique_ptr<ScreenCaptureKitFullscreenModule> fullscreen_module_;
@@ -772,6 +867,8 @@ void ResetStreamTo(SCWindow* window) override {
base::WeakPtrFactory<ScreenCaptureKitDeviceMac> weak_factory_{this};
};
+int ScreenCaptureKitDeviceMac::active_streams_ = 0;
+
} // namespace
// Although ScreenCaptureKit is available in 12.3 there were some bugs that
diff --git a/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc b/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
index 0f6531fd1c7784867b2b4e6d72d042bee4bff579..1c7b00d6b1929b807649d7a00f963195bf3f4ea1 100644
--- a/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
+++ b/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
@@ -321,8 +321,16 @@ void InProcessVideoCaptureDeviceLauncher::LaunchDeviceAsync(
break;
}
+#if defined(USE_AURA)
+ bool allow_window_id = false;
+#elif BUILDFLAG(IS_MAC)
+ bool allow_window_id =
+ desktop_id.id == DesktopMediaID::kMacOsNativePickerId;
+#endif
+
#if defined(USE_AURA) || BUILDFLAG(IS_MAC)
- if (desktop_id.window_id != DesktopMediaID::kNullId) {
+ if (!allow_window_id &&
+ desktop_id.window_id != DesktopMediaID::kNullId) {
// For the other capturers, when a bug reports the type of capture it's
// easy enough to determine which capturer was used, but it's a little
// fuzzier with window capture.
@@ -338,13 +346,15 @@ void InProcessVideoCaptureDeviceLauncher::LaunchDeviceAsync(
}
#endif // defined(USE_AURA) || BUILDFLAG(IS_MAC)
+ media::VideoCaptureParams updated_params = params;
+ updated_params.use_native_picker = stream_type != blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE;
// All cases other than tab capture or Aura desktop/window capture.
TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"UsingDesktopCapturer", TRACE_EVENT_SCOPE_THREAD);
start_capture_closure = base::BindOnce(
&InProcessVideoCaptureDeviceLauncher::
DoStartDesktopCaptureOnDeviceThread,
- base::Unretained(this), desktop_id, params,
+ base::Unretained(this), desktop_id, updated_params,
CreateDeviceClient(media::VideoCaptureBufferType::kSharedMemory,
kMaxNumberOfBuffers, std::move(receiver),
std::move(receiver_on_io_thread)),
diff --git a/content/public/browser/desktop_media_id.h b/content/public/browser/desktop_media_id.h
index b10c5376caa9a832826868c72dbc44ee54705283..e9b27b53d9b34fbb0a3410eb2fcc15306497cdf4 100644
--- a/content/public/browser/desktop_media_id.h
+++ b/content/public/browser/desktop_media_id.h
@@ -28,6 +28,8 @@ struct CONTENT_EXPORT DesktopMediaID {
static constexpr Id kNullId = 0;
// Represents a fake id to create a dummy capturer for autotests.
static constexpr Id kFakeId = -3;
+ // Represents an id to use native macOS picker for screenshare
+ static constexpr Id kMacOsNativePickerId = -4;
#if defined(USE_AURA) || BUILDFLAG(IS_MAC)
// Assigns integer identifier to the |window| and returns its DesktopMediaID.
diff --git a/media/capture/video_capture_types.h b/media/capture/video_capture_types.h
index f422e0ec81ee354512a10dafdd1bd8659a7e3d67..8095a5cb0060ed20a4b82e98b4921a8919077d7e 100644
--- a/media/capture/video_capture_types.h
+++ b/media/capture/video_capture_types.h
@@ -365,6 +365,8 @@ struct CAPTURE_EXPORT VideoCaptureParams {
// of the capture is dynamically changed, as for example when using
// share-this-tab-instead.
uint32_t capture_version_source = 0;
+
+ std::optional<bool> use_native_picker;
};
CAPTURE_EXPORT std::ostream& operator<<(

View File

@@ -10,10 +10,6 @@ however those files were cherry-picked from main branch and do not
really in 20/21. We have to wait until 22 is released to be able to
build with upstream GN files.
src/inspector/unofficial.gni - The node_protocol_generated_sources action
was missing gypi_values.node_pdl_files from its inputs, causing Ninja to skip
regeneration when PDL domain files changed. Should be upstreamed.
diff --git a/configure.py b/configure.py
index fa25de8c316b71d3ad5b55b5ce398b69a5d4a965..fc48438060e0dd84edc60d1aebf3d0946be98ea9 100755
--- a/configure.py
@@ -71,19 +67,6 @@ index 41f200189a34e150e4c8f25da2a72c2108259720..156fee33b3813fe4d94a1c9585f217a9
}
assert(!node_enable_inspector || node_use_openssl,
diff --git a/src/inspector/unofficial.gni b/src/inspector/unofficial.gni
index 4810d93eb971b253f7dadff7011a632f6dbe6a2b..48cf5102630737ffeba98719a8b508b52fe5e27a 100644
--- a/src/inspector/unofficial.gni
+++ b/src/inspector/unofficial.gni
@@ -29,7 +29,7 @@ template("inspector_gn_build") {
deps = [ ":node_protocol_json" ]
outputs = gypi_values.node_inspector_generated_sources
- inputs = gypi_values.node_protocol_files + [
+ inputs = gypi_values.node_protocol_files + gypi_values.node_pdl_files + [
"node_protocol_config.json",
"$node_gen_dir/src/node_protocol.json",
]
diff --git a/src/node_builtins.cc b/src/node_builtins.cc
index 6506dcea3f4f88a7781975fae1ee5f8b87d4dfb2..a077ad673fdf7eab61878940e5fef43921c2e453 100644
--- a/src/node_builtins.cc

25
script/run-gn-format.py Normal file
View File

@@ -0,0 +1,25 @@
import os
import subprocess
import sys
from lib.util import get_depot_tools_env
SOURCE_ROOT = os.path.dirname(os.path.dirname(__file__))
# Helper to run gn format on multiple files
# (gn only formats a single file at a time)
def main():
new_env = get_depot_tools_env()
new_env['DEPOT_TOOLS_WIN_TOOLCHAIN'] = '0'
new_env['CHROMIUM_BUILDTOOLS_PATH'] = os.path.realpath(
os.path.join(SOURCE_ROOT, '..', 'buildtools')
)
for gn_file in sys.argv[1:]:
subprocess.check_call(
['gn', 'format', gn_file],
env=new_env
)
if __name__ == '__main__':
sys.exit(main())

View File

@@ -12,28 +12,20 @@
#include "shell/common/gin_converters/time_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/event_emitter_caller.h"
#include "shell/common/gin_helper/handle.h"
#include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/gin_helper/wrappable_pointer_tags.h"
#include "shell/common/node_includes.h"
#include "v8/include/cppgc/allocation.h"
#include "v8/include/v8-cppgc.h"
namespace electron::api {
const gin::WrapperInfo AutoUpdater::kWrapperInfo =
electron::MakeWrapperInfo(electron::kElectronAutoUpdater);
gin::DeprecatedWrapperInfo AutoUpdater::kWrapperInfo = {
gin::kEmbedderNativeGin};
AutoUpdater::AutoUpdater(v8::Isolate* isolate) {
AutoUpdater::AutoUpdater() {
auto_updater::AutoUpdater::SetDelegate(this);
gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
data->AddDisposeObserver(this);
}
AutoUpdater::~AutoUpdater() = default;
void AutoUpdater::OnBeforeMicrotasksRunnerDispose(v8::Isolate* isolate) {
gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
data->RemoveDisposeObserver(this);
AutoUpdater::~AutoUpdater() {
auto_updater::AutoUpdater::SetDelegate(nullptr);
}
@@ -128,9 +120,8 @@ void AutoUpdater::QuitAndInstall() {
}
// static
AutoUpdater* AutoUpdater::Create(v8::Isolate* isolate) {
return cppgc::MakeGarbageCollected<AutoUpdater>(
isolate->GetCppHeap()->GetAllocationHandle(), isolate);
gin_helper::Handle<AutoUpdater> AutoUpdater::Create(v8::Isolate* isolate) {
return gin_helper::CreateHandle(isolate, new AutoUpdater());
}
gin::ObjectTemplateBuilder AutoUpdater::GetObjectTemplateBuilder(
@@ -147,12 +138,8 @@ gin::ObjectTemplateBuilder AutoUpdater::GetObjectTemplateBuilder(
.SetMethod("quitAndInstall", &AutoUpdater::QuitAndInstall);
}
const gin::WrapperInfo* AutoUpdater::wrapper_info() const {
return &kWrapperInfo;
}
const char* AutoUpdater::GetHumanReadableName() const {
return "Electron / AutoUpdater";
const char* AutoUpdater::GetTypeName() {
return "AutoUpdater";
}
} // namespace electron::api

View File

@@ -7,44 +7,39 @@
#include <string>
#include "gin/per_isolate_data.h"
#include "gin/wrappable.h"
#include "shell/browser/auto_updater.h"
#include "shell/browser/event_emitter_mixin.h"
#include "shell/browser/window_list_observer.h"
#include "shell/common/gin_helper/wrappable.h"
namespace gin_helper {
template <typename T>
class Handle;
} // namespace gin_helper
namespace electron::api {
class AutoUpdater final : public gin::Wrappable<AutoUpdater>,
class AutoUpdater final : public gin_helper::DeprecatedWrappable<AutoUpdater>,
public gin_helper::EventEmitterMixin<AutoUpdater>,
public auto_updater::Delegate,
public gin::PerIsolateData::DisposeObserver,
private WindowListObserver {
public:
static AutoUpdater* Create(v8::Isolate* isolate);
static gin_helper::Handle<AutoUpdater> Create(v8::Isolate* isolate);
// gin::Wrappable
static const gin::WrapperInfo kWrapperInfo;
const gin::WrapperInfo* wrapper_info() const override;
const char* GetHumanReadableName() const override;
// gin_helper::Wrappable
static gin::DeprecatedWrapperInfo kWrapperInfo;
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate) override;
const char* GetClassName() const { return "AutoUpdater"; }
// gin::PerIsolateData::DisposeObserver
void OnBeforeDispose(v8::Isolate* isolate) override {}
void OnBeforeMicrotasksRunnerDispose(v8::Isolate* isolate) override;
void OnDisposed() override {}
// Make public for cppgc::MakeGarbageCollected.
explicit AutoUpdater(v8::Isolate* isolate);
~AutoUpdater() override;
const char* GetTypeName() override;
// disable copy
AutoUpdater(const AutoUpdater&) = delete;
AutoUpdater& operator=(const AutoUpdater&) = delete;
private:
protected:
AutoUpdater();
~AutoUpdater() override;
// auto_updater::Delegate:
void OnError(const std::string& message) override;
void OnError(const std::string& message,
@@ -61,6 +56,7 @@ class AutoUpdater final : public gin::Wrappable<AutoUpdater>,
// WindowListObserver:
void OnWindowAllClosed() override;
private:
std::string GetFeedURL();
void QuitAndInstall();
};

View File

@@ -914,10 +914,17 @@ void Session::SetPermissionCheckHandler(v8::Local<v8::Value> val,
}
void Session::SetDisplayMediaRequestHandler(v8::Isolate* isolate,
v8::Local<v8::Value> val) {
v8::Local<v8::Value> val,
gin::Arguments* args) {
bool use_system_picker = false;
gin_helper::Dictionary opts;
if (args->GetNext(&opts)) {
opts.Get("useSystemPicker", &use_system_picker);
}
if (val->IsNull()) {
browser_context_->SetDisplayMediaRequestHandler(
DisplayMediaRequestHandler());
DisplayMediaRequestHandler(), use_system_picker);
return;
}
DisplayMediaRequestHandler handler;
@@ -926,7 +933,7 @@ void Session::SetDisplayMediaRequestHandler(v8::Isolate* isolate,
"Display media request handler must be null or a function");
return;
}
browser_context_->SetDisplayMediaRequestHandler(handler);
browser_context_->SetDisplayMediaRequestHandler(handler, use_system_picker);
}
void Session::SetDevicePermissionHandler(v8::Local<v8::Value> val,
@@ -1786,7 +1793,7 @@ void Session::FillObjectTemplate(v8::Isolate* isolate,
&Session::SetPermissionRequestHandler)
.SetMethod("setPermissionCheckHandler",
&Session::SetPermissionCheckHandler)
.SetMethod("_setDisplayMediaRequestHandler",
.SetMethod("setDisplayMediaRequestHandler",
&Session::SetDisplayMediaRequestHandler)
.SetMethod("setDevicePermissionHandler",
&Session::SetDevicePermissionHandler)

View File

@@ -206,7 +206,8 @@ class Session final : public gin::Wrappable<Session>,
private:
void SetDisplayMediaRequestHandler(v8::Isolate* isolate,
v8::Local<v8::Value> val);
v8::Local<v8::Value> val,
gin::Arguments* args);
cppgc::Member<api::Cookies> cookies_;
cppgc::Member<api::Extensions> extensions_;

View File

@@ -82,8 +82,6 @@ void WebContentsView::ApplyBorderRadius() {
}
int WebContentsView::NonClientHitTest(const gfx::Point& point) {
if (!view() || !view()->GetVisible())
return HTNOWHERE;
if (api_web_contents_) {
auto* iwc = api_web_contents_->inspectable_web_contents();
if (!iwc)

View File

@@ -63,6 +63,7 @@
#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/ssl/ssl_private_key.h"
#include "pdf/pdf_features.h"
#include "printing/buildflags/buildflags.h"
#include "services/device/public/cpp/geolocation/geolocation_system_permission_manager.h"
#include "services/device/public/cpp/geolocation/location_provider.h"
@@ -229,7 +230,6 @@
#include "components/pdf/browser/pdf_navigation_throttle.h"
#include "components/pdf/browser/pdf_url_loader_request_interceptor.h"
#include "components/pdf/common/constants.h" // nogncheck
#include "pdf/pdf_features.h"
#include "shell/browser/electron_pdf_document_helper_client.h"
#include "ui/webui/resources/cr_components/help_bubble/help_bubble.mojom.h" // nogncheck
#endif

View File

@@ -19,6 +19,7 @@
#include "base/strings/escape.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/bind_post_task.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
@@ -33,6 +34,7 @@
#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/desktop_capture.h"
#include "content/public/browser/host_zoom_map.h"
#include "content/public/browser/preconnect_manager.h"
#include "content/public/browser/render_process_host.h"
@@ -44,6 +46,7 @@
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/originating_process_id.h"
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
#include "shell/browser/api/electron_api_desktop_capturer.h"
#include "shell/browser/cookie_change_notifier.h"
#include "shell/browser/electron_browser_client.h"
#include "shell/browser/electron_browser_main_parts.h"
@@ -701,10 +704,53 @@ void ElectronBrowserContext::SetSSLConfigClient(
}
void ElectronBrowserContext::SetDisplayMediaRequestHandler(
DisplayMediaRequestHandler handler) {
DisplayMediaRequestHandler handler,
bool use_system_picker) {
display_media_request_handler_ = handler;
use_system_picker_ = use_system_picker;
}
namespace {
content::DesktopMediaID::Type PickerTypeForRequest(
const content::MediaStreamRequest& request) {
// Upstream's NativeScreenCapturePickerMac requires exactly TYPE_SCREEN XOR
// TYPE_WINDOW. Use the request's `preferred_display_surface` when it maps
// cleanly; default to TYPE_SCREEN otherwise.
switch (request.preferred_display_surface) {
case blink::mojom::PreferredDisplaySurface::WINDOW:
return content::DesktopMediaID::TYPE_WINDOW;
case blink::mojom::PreferredDisplaySurface::MONITOR:
case blink::mojom::PreferredDisplaySurface::NO_PREFERENCE:
case blink::mojom::PreferredDisplaySurface::BROWSER:
return content::DesktopMediaID::TYPE_SCREEN;
}
}
void FinishSystemPickerRequest(const content::MediaStreamRequest& request,
content::DesktopMediaID::Type type,
content::MediaResponseCallback callback,
webrtc::DesktopCapturer::Source source) {
blink::mojom::StreamDevicesSet stream_devices_set;
stream_devices_set.stream_devices.emplace_back(
blink::mojom::StreamDevices::New());
content::DesktopMediaID media_id(type, source.id);
blink::MediaStreamDevice video_device(request.video_type, media_id.ToString(),
"Screen");
video_device.display_media_info = DesktopMediaIDToDisplayMediaInformation(
nullptr, url::Origin::Create(request.security_origin), media_id);
stream_devices_set.stream_devices[0]->video_device = video_device;
std::move(callback).Run(stream_devices_set,
blink::mojom::MediaStreamRequestResult::OK, nullptr);
}
void FailSystemPickerRequest(content::MediaResponseCallback callback,
blink::mojom::MediaStreamRequestResult result) {
std::move(callback).Run(blink::mojom::StreamDevicesSet(), result, nullptr);
}
} // namespace
void ElectronBrowserContext::DisplayMediaDeviceChosen(
const content::MediaStreamRequest& request,
content::MediaResponseCallback callback,
@@ -840,6 +886,61 @@ void ElectronBrowserContext::DisplayMediaDeviceChosen(
bool ElectronBrowserContext::ChooseDisplayMediaDevice(
const content::MediaStreamRequest& request,
content::MediaResponseCallback callback) {
if (use_system_picker_ &&
api::DesktopCapturer::IsDisplayMediaSystemPickerAvailable()) {
// Route useSystemPicker: true through upstream's
// NativeScreenCapturePickerMac, which owns the system picker lifecycle and
// issues source ids. The JS handler is bypassed (documented behavior).
const content::DesktopMediaID::Type type = PickerTypeForRequest(request);
// `callback` is consumed by exactly one of picker/cancel/error. Wrap it in
// a RefCountedData so we can share across the three BindOnce paths.
auto shared_callback = base::MakeRefCounted<
base::RefCountedData<content::MediaResponseCallback>>(
std::move(callback));
auto created_cb =
base::BindPostTask(base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce([](content::DesktopMediaID::Id) {}));
auto picker_cb = base::BindPostTask(
base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce(
[](content::MediaStreamRequest request,
content::DesktopMediaID::Type type,
scoped_refptr<
base::RefCountedData<content::MediaResponseCallback>> cb,
webrtc::DesktopCapturer::Source source) {
FinishSystemPickerRequest(std::move(request), type,
std::move(cb->data), source);
},
request, type, shared_callback));
auto cancel_cb = base::BindPostTask(
base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce(
[](scoped_refptr<
base::RefCountedData<content::MediaResponseCallback>> cb) {
FailSystemPickerRequest(
std::move(cb->data),
blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED);
},
shared_callback));
auto error_cb = base::BindPostTask(
base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce(
[](scoped_refptr<
base::RefCountedData<content::MediaResponseCallback>> cb) {
FailSystemPickerRequest(std::move(cb->data),
blink::mojom::MediaStreamRequestResult::
FAILED_DUE_TO_SHUTDOWN);
},
shared_callback));
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&content::desktop_capture::OpenNativeScreenCapturePicker,
type, std::move(created_cb), std::move(picker_cb),
std::move(cancel_cb), std::move(error_cb)));
return true;
}
if (!display_media_request_handler_)
return false;
DisplayMediaResponseCallbackJs callbackJs =

View File

@@ -144,7 +144,8 @@ class ElectronBrowserContext : public content::BrowserContext {
bool ChooseDisplayMediaDevice(const content::MediaStreamRequest& request,
content::MediaResponseCallback callback);
void SetDisplayMediaRequestHandler(DisplayMediaRequestHandler handler);
void SetDisplayMediaRequestHandler(DisplayMediaRequestHandler handler,
bool use_system_picker);
~ElectronBrowserContext() override;
@@ -227,6 +228,7 @@ class ElectronBrowserContext : public content::BrowserContext {
mojo::Remote<network::mojom::SSLConfigClient> ssl_config_client_;
DisplayMediaRequestHandler display_media_request_handler_;
bool use_system_picker_ = false;
// In-memory cache that holds objects that have been granted permissions.
DevicePermissionMap granted_devices_;

View File

@@ -11,6 +11,14 @@
namespace electron {
std::string EnablePlatformSpecificFeatures() {
// UseSCContentSharingPicker (media/base/media_switches.cc) gates the
// upstream NativeScreenCapturePickerMac path. Enabling it on macOS 15+
// lets session.setDisplayMediaRequestHandler({ useSystemPicker: true })
// drive the system picker via content::desktop_capture public API.
std::string sck_picker;
if (@available(macOS 15.0, *)) {
sck_picker = ",UseSCContentSharingPicker";
}
if (@available(macOS 14.4, *)) {
// These flags aren't exported so reference them by name directly, they are
// used to ensure that screen and window capture exclusive use
@@ -22,10 +30,12 @@ std::string EnablePlatformSpecificFeatures() {
// kThumbnailCapturerMac,
// chrome/browser/media/webrtc/thumbnail_capturer_mac.mm
#if DCHECK_IS_ON()
return "ScreenCaptureKitPickerScreen,ScreenCaptureKitStreamPickerSonoma";
return "ScreenCaptureKitPickerScreen,ScreenCaptureKitStreamPickerSonoma" +
sck_picker;
#else
return "ScreenCaptureKitPickerScreen,ScreenCaptureKitStreamPickerSonoma,"
"ThumbnailCapturerMac:capture_mode/sc_screenshot_manager";
"ThumbnailCapturerMac:capture_mode/sc_screenshot_manager" +
sck_picker;
#endif
}
return "";

View File

@@ -240,13 +240,6 @@ void NativeWindow::SetShape(const std::vector<gfx::Rect>& rects) {
widget()->SetShape(std::make_unique<std::vector<gfx::Rect>>(rects));
}
void NativeWindow::FlushPendingRootLayout(views::View* view) {
view->InvalidateLayout();
if (views::Widget* widget = view->GetWidget())
widget->LayoutRootViewIfNecessary();
}
bool NativeWindow::IsClosed() const {
return is_closed_;
}

View File

@@ -462,7 +462,6 @@ class NativeWindow : public views::WidgetDelegate {
const views::Widget* GetWidget() const override;
void set_content_view(views::View* view) { content_view_ = view; }
void FlushPendingRootLayout(views::View* view);
static inline constexpr base::cstring_view kNativeWindowKey =
"__ELECTRON_NATIVE_WINDOW__";

View File

@@ -381,7 +381,7 @@ void NativeWindowMac::SetContentView(views::View* view) {
root_view->AddChildViewRaw(content_view());
FlushPendingRootLayout(root_view);
root_view->DeprecatedLayoutImmediately();
}
void NativeWindowMac::Close() {

View File

@@ -533,7 +533,7 @@ void NativeWindowViews::SetContentView(views::View* view) {
set_content_view(view);
focused_view_ = view;
root_view_.GetMainView()->AddChildViewRaw(content_view());
FlushPendingRootLayout(root_view_.GetMainView());
root_view_.GetMainView()->DeprecatedLayoutImmediately();
}
void NativeWindowViews::Close() {

View File

@@ -23,16 +23,6 @@
#include "ui/views/window/client_view.h"
namespace electron {
namespace {
void FlushPendingRootLayout(views::View* view) {
view->InvalidateLayout();
if (views::Widget* widget = view->GetWidget())
widget->LayoutRootViewIfNecessary();
}
} // namespace
class DevToolsWindowDelegate : public views::ClientView,
public views::WidgetDelegate {
@@ -145,7 +135,7 @@ void InspectableWebContentsView::ShowDevTools(bool activate) {
devtools_web_view_->SetWebContents(
inspectable_web_contents_->GetDevToolsWebContents());
devtools_web_view_->RequestFocus();
FlushPendingRootLayout(this);
DeprecatedLayoutImmediately();
}
}
@@ -183,7 +173,7 @@ void InspectableWebContentsView::CloseDevTools() {
} else {
devtools_web_view_->SetVisible(false);
devtools_web_view_->SetWebContents(nullptr);
FlushPendingRootLayout(this);
DeprecatedLayoutImmediately();
}
}
@@ -233,7 +223,7 @@ void InspectableWebContentsView::SetIsDocked(bool docked, bool activate) {
void InspectableWebContentsView::SetContentsResizingStrategy(
const DevToolsContentsResizingStrategy& strategy) {
strategy_.CopyFrom(strategy);
FlushPendingRootLayout(this);
DeprecatedLayoutImmediately();
}
void InspectableWebContentsView::SetTitle(const std::u16string& title) {

View File

@@ -13,7 +13,6 @@ namespace electron {
// Electron-specific WrappablePointerTag values that extend gin's tag range.
enum ElectronWrappablePointerTag : uint16_t {
kElectronApp = gin::kLastPointerTag + 1, // electron::api::App
kElectronAutoUpdater, // electron::api::AutoUpdater
kElectronCookies, // electron::api::Cookies
kElectronDataPipeHolder, // electron::api::DataPipeHolder
kElectronDebugger, // electron::api::Debugger

View File

@@ -9,32 +9,16 @@ import * as fs from 'node:fs';
import * as http from 'node:http';
import * as https from 'node:https';
import * as net from 'node:net';
import * as os from 'node:os';
import * as path from 'node:path';
import * as readline from 'node:readline';
import { setTimeout } from 'node:timers/promises';
import { promisify } from 'node:util';
import { collectStreamBody, getResponse } from './lib/net-helpers';
import { defer, ifdescribe, ifit, isWayland, 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');
const xdgMockFixturePath = path.join(fixturesPath, 'api', 'xdg-mock');
function makeXdgMockDirectories(prefix: string) {
const xdgDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
fs.cpSync(xdgMockFixturePath, xdgDir, { recursive: true });
const xdgDataHome = path.join(xdgDir, 'data');
const xdgConfigHome = path.join(xdgDir, 'config');
const xdgBinDir = path.join(xdgDir, 'bin');
fs.chmodSync(path.join(xdgBinDir, 'xdg-mime'), 0o755);
fs.chmodSync(path.join(xdgBinDir, 'xdg-settings'), 0o755);
return { xdgDir, xdgDataHome, xdgConfigHome, xdgBinDir };
}
const isMacOSx64 = process.platform === 'darwin' && process.arch === 'x64';
@@ -1531,6 +1515,7 @@ describe('app module', () => {
const fixtureApp = path.join(fixturesPath, 'api', 'protocol-name');
const desktopFileId = 'mock-browser.desktop';
const mockScheme = 'mockproto';
const mockMimeType = `x-scheme-handler/${mockScheme}`;
function spawnWithXdgMock(
url: string,
@@ -1579,7 +1564,62 @@ describe('app module', () => {
let xdgConfigHome: string;
let xdgBinDir: string;
before(() => {
({ xdgDir, xdgDataHome, xdgConfigHome, xdgBinDir } = makeXdgMockDirectories('electron-xdg-'));
xdgDir = fs.mkdtempSync(path.join(require('node:os').tmpdir(), 'electron-xdg-'));
xdgDataHome = path.join(xdgDir, 'data');
xdgConfigHome = path.join(xdgDir, 'config');
xdgBinDir = path.join(xdgDir, 'bin');
const appsDir = path.join(xdgDataHome, 'applications');
fs.mkdirSync(appsDir, { recursive: true });
fs.mkdirSync(xdgConfigHome, { recursive: true });
fs.mkdirSync(xdgBinDir, { recursive: true });
fs.writeFileSync(
path.join(appsDir, desktopFileId),
[
'[Desktop Entry]',
'Name=Mock Browser',
'Exec=/usr/bin/true %u',
'Type=Application',
`MimeType=${mockMimeType};`
].join('\n')
);
const mimeAppsContents = [
'[Default Applications]',
`${mockMimeType}=${desktopFileId}`,
'',
'[Added Associations]',
`${mockMimeType}=${desktopFileId};`
].join('\n');
fs.writeFileSync(path.join(xdgConfigHome, 'mimeapps.list'), mimeAppsContents);
fs.writeFileSync(path.join(appsDir, 'mimeapps.list'), mimeAppsContents);
fs.writeFileSync(path.join(appsDir, 'defaults.list'), mimeAppsContents);
// Different xdg-utils versions resolve custom XDG dirs differently, so
// prepend a deterministic xdg-mime shim for this test.
const xdgMimePath = path.join(xdgBinDir, 'xdg-mime');
fs.writeFileSync(
xdgMimePath,
[
'#!/bin/sh',
'if [ "$1" != "query" ] || [ "$2" != "default" ]; then',
' exit 1',
'fi',
'mime="$3"',
'for list in "$XDG_CONFIG_HOME/mimeapps.list" "$XDG_DATA_HOME/applications/mimeapps.list" "$XDG_DATA_HOME/applications/defaults.list"; do',
' if [ -f "$list" ]; then',
' result=$(grep "^$mime=" "$list" | head -n 1 | cut -d= -f2 | cut -d";" -f1)',
' if [ -n "$result" ]; then',
' printf "%s\\n" "$result"',
' exit 0',
' fi',
' fi',
'done',
'exit 0'
].join('\n')
);
fs.chmodSync(xdgMimePath, 0o755);
});
after(() => {
@@ -1619,95 +1659,6 @@ describe('app module', () => {
});
});
ifdescribe(process.platform === 'linux')('default protocol client APIs with mocked XDG settings', () => {
const protocol = 'electron-test-linux';
const desktopFileId = 'electron-test.desktop';
const protocolMimeType = `x-scheme-handler/${protocol}`;
let xdgDir: string;
let xdgDataHome: string;
let xdgConfigHome: string;
let xdgBinDir: string;
let oldEnv: Record<string, string | undefined>;
const getRegisteredHandler = () => {
for (const list of [
path.join(xdgConfigHome, 'mimeapps.list'),
path.join(xdgDataHome, 'applications', 'mimeapps.list'),
path.join(xdgDataHome, 'applications', 'defaults.list')
]) {
if (!fs.existsSync(list)) continue;
const match = fs
.readFileSync(list, 'utf8')
.split('\n')
.find((line) => line.startsWith(`${protocolMimeType}=`));
// foo=bar.desktop; --> bar.desktop
if (match) return match.split('=', 2)[1].split(';', 1)[0];
}
return '';
};
beforeEach(() => {
({ xdgDir, xdgDataHome, xdgConfigHome, xdgBinDir } = makeXdgMockDirectories('electron-xdg-default-client-'));
oldEnv = {
PATH: process.env.PATH,
CHROME_DESKTOP: process.env.CHROME_DESKTOP,
XDG_DATA_HOME: process.env.XDG_DATA_HOME,
XDG_DATA_DIRS: process.env.XDG_DATA_DIRS,
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME
};
defer(() => {
for (const [key, value] of Object.entries(oldEnv)) {
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
fs.rmSync(xdgDir, { recursive: true, force: true });
});
process.env.PATH = [xdgBinDir, oldEnv.PATH].filter(Boolean).join(':');
process.env.XDG_DATA_HOME = xdgDataHome;
process.env.XDG_DATA_DIRS = [xdgDataHome, oldEnv.XDG_DATA_DIRS].filter(Boolean).join(':');
process.env.XDG_CONFIG_HOME = xdgConfigHome;
app.setDesktopName(desktopFileId);
});
it('writes the default handler to the XDG association files', async () => {
expect(getRegisteredHandler()).to.equal('');
expect(app.setAsDefaultProtocolClient(protocol)).to.equal(true);
await waitUntil(() => getRegisteredHandler() === desktopFileId);
expect(getRegisteredHandler()).to.equal(desktopFileId);
});
it('detects whether the app is the default protocol client', async () => {
expect(app.isDefaultProtocolClient(protocol)).to.equal(false);
fs.writeFileSync(
path.join(xdgConfigHome, 'mimeapps.list'),
['[Default Applications]', `${protocolMimeType}=other.desktop`].join('\n')
);
expect(app.isDefaultProtocolClient(protocol)).to.equal(false);
fs.writeFileSync(
path.join(xdgConfigHome, 'mimeapps.list'),
['[Default Applications]', `${protocolMimeType}=${desktopFileId}`].join('\n')
);
await waitUntil(() => app.isDefaultProtocolClient(protocol));
expect(app.isDefaultProtocolClient(protocol)).to.equal(true);
});
});
describe('protocol scheme validation', () => {
it('rejects empty protocol names', () => {
expect(app.setAsDefaultProtocolClient('')).to.equal(false);

View File

@@ -615,53 +615,4 @@ describe('cpp heap', () => {
);
});
});
ifdescribe(process.platform === 'darwin')('autoUpdater module', () => {
it('is retained after garbage collection', async () => {
const rc = await startRemoteControlApp(['--js-flags=--expose-gc']);
const result = await rc.remotely(async () => {
const { autoUpdater } = require('electron');
const v8Util = (process as any)._linkedBinding('electron_common_v8_util');
const wr = new WeakRef(autoUpdater);
for (let i = 0; i < 10; i++) {
await new Promise((resolve) => setTimeout(resolve, 0));
v8Util.requestGarbageCollectionForTesting();
}
return {
retained: wr.deref() !== undefined,
functional: typeof autoUpdater.getFeedURL() === 'string'
};
});
expect(result.retained).to.equal(true, 'autoUpdater should survive GC');
expect(result.functional).to.equal(true, 'autoUpdater should still be functional after GC');
});
it('should record as node in heap snapshot', async () => {
const rc = await startRemoteControlApp(['--expose-internals', '--js-flags=--expose-gc']);
const result = await rc.remotely(
async (heap: string) => {
const { autoUpdater, app } = require('electron');
const { recordState } = require(heap);
const v8Util = (process as any)._linkedBinding('electron_common_v8_util');
console.log(autoUpdater.getFeedURL());
for (let i = 0; i < 10; i++) {
await new Promise((resolve) => setTimeout(resolve, 0));
v8Util.requestGarbageCollectionForTesting();
}
const state = recordState();
const nodes = state.snapshot.filter((node: any) => node.name === 'Electron / AutoUpdater');
const found = nodes.length > 0;
const noDuplicates = nodes.length === 1;
setTimeout(() => app.quit());
return { found, noDuplicates };
},
path.join(__dirname, '../../third_party/electron_node/test/common/heap')
);
const [code] = await once(rc.process, 'exit');
expect(code).to.equal(0);
expect(result.found).to.equal(true, 'AutoUpdater should be in snapshot (held by JS module)');
expect(result.noDuplicates).to.equal(true, 'should have exactly one AutoUpdater instance');
});
});
});

View File

@@ -130,19 +130,6 @@ describe('deprecate', () => {
expect(msg).to.include('oldFn');
});
it('preserves the return value and `this` of a deprecated function with no replacement', () => {
deprecate.setHandler(() => {});
function oldFn(this: any, a: number, b: number) {
return { self: this, sum: a + b };
}
const deprecatedFn = deprecate.removeFunction(oldFn, 'oldFn');
const context = { name: 'ctx' };
const result = deprecatedFn.call(context, 2, 3);
expect(result).to.deep.equal({ self: context, sum: 5 });
});
it('warns exactly once when a function is deprecated with a replacement', () => {
let msg;
deprecate.setHandler((m) => {

View File

@@ -1,15 +0,0 @@
#!/bin/sh
if [ "$1" != "query" ] || [ "$2" != "default" ]; then
exit 1
fi
mime="$3"
for list in "$XDG_CONFIG_HOME/mimeapps.list" "$XDG_DATA_HOME/applications/mimeapps.list" "$XDG_DATA_HOME/applications/defaults.list"; do
if [ -f "$list" ]; then
result=$(grep "^$mime=" "$list" | head -n 1 | cut -d= -f2 | cut -d";" -f1)
if [ -n "$result" ]; then
printf "%s\n" "$result"
exit 0
fi
fi
done
exit 0

View File

@@ -1,31 +0,0 @@
#!/bin/sh
set -eu
get_handler() {
for list in "$XDG_CONFIG_HOME/mimeapps.list" "$XDG_DATA_HOME/applications/mimeapps.list" "$XDG_DATA_HOME/applications/defaults.list"; do
if [ -f "$list" ]; then
result=$(grep "^x-scheme-handler/$1=" "$list" | head -n 1 | cut -d= -f2 | cut -d";" -f1)
if [ -n "$result" ]; then
printf "%s\n" "$result"
return 0
fi
fi
done
return 1
}
if [ "$1" = "set" ] && [ "$2" = "default-url-scheme-handler" ]; then
mkdir -p "$XDG_CONFIG_HOME"
{
printf "[Default Applications]\n"
printf "x-scheme-handler/%s=%s\n" "$3" "$4"
} > "$XDG_CONFIG_HOME/mimeapps.list"
exit 0
fi
if [ "$1" = "check" ] && [ "$2" = "default-url-scheme-handler" ]; then
if [ "$(get_handler "$3" 2>/dev/null || true)" = "$4" ]; then
printf "yes\n"
else
printf "no\n"
fi
exit 0
fi
exit 1

View File

@@ -1,5 +0,0 @@
[Default Applications]
x-scheme-handler/mockproto=mock-browser.desktop
[Added Associations]
x-scheme-handler/mockproto=mock-browser.desktop;

View File

@@ -1,5 +0,0 @@
[Desktop Entry]
Name=Electron Test
Exec=/usr/bin/true %u
Type=Application
MimeType=x-scheme-handler/electron-test-linux;

View File

@@ -1,5 +0,0 @@
[Desktop Entry]
Name=Mock Browser
Exec=/usr/bin/true %u
Type=Application
MimeType=x-scheme-handler/mockproto;

View File

@@ -142,10 +142,6 @@ declare namespace Electron {
type?: 'backgroundPage' | 'window' | 'browserView' | 'remote' | 'webview' | 'offscreen';
}
interface Session {
_setDisplayMediaRequestHandler: Electron.Session['setDisplayMediaRequestHandler'];
}
type CreateWindowFunction = (options: BrowserWindowConstructorOptions) => WebContents;
interface Menu {