mirror of
https://github.com/electron/electron.git
synced 2026-05-02 03:00:22 -04:00
Compare commits
1 Commits
fix/win-th
...
gxu-sckp-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7da613c70 |
15
.github/actions/build-electron/action.yml
vendored
15
.github/actions/build-electron/action.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
6
.github/workflows/windows-publish.yml
vendored
6
.github/workflows/windows-publish.yml
vendored
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 --"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<<(
|
||||
@@ -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
25
script/run-gn-format.py
Normal 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())
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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 "";
|
||||
|
||||
@@ -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_;
|
||||
}
|
||||
|
||||
@@ -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__";
|
||||
|
||||
@@ -381,7 +381,7 @@ void NativeWindowMac::SetContentView(views::View* view) {
|
||||
|
||||
root_view->AddChildViewRaw(content_view());
|
||||
|
||||
FlushPendingRootLayout(root_view);
|
||||
root_view->DeprecatedLayoutImmediately();
|
||||
}
|
||||
|
||||
void NativeWindowMac::Close() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
15
spec/fixtures/api/xdg-mock/bin/xdg-mime
vendored
15
spec/fixtures/api/xdg-mock/bin/xdg-mime
vendored
@@ -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
|
||||
31
spec/fixtures/api/xdg-mock/bin/xdg-settings
vendored
31
spec/fixtures/api/xdg-mock/bin/xdg-settings
vendored
@@ -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
|
||||
@@ -1,5 +0,0 @@
|
||||
[Default Applications]
|
||||
x-scheme-handler/mockproto=mock-browser.desktop
|
||||
|
||||
[Added Associations]
|
||||
x-scheme-handler/mockproto=mock-browser.desktop;
|
||||
@@ -1,5 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=Electron Test
|
||||
Exec=/usr/bin/true %u
|
||||
Type=Application
|
||||
MimeType=x-scheme-handler/electron-test-linux;
|
||||
@@ -1,5 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=Mock Browser
|
||||
Exec=/usr/bin/true %u
|
||||
Type=Application
|
||||
MimeType=x-scheme-handler/mockproto;
|
||||
4
typings/internal-electron.d.ts
vendored
4
typings/internal-electron.d.ts
vendored
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user