Compare commits

...

20 Commits

Author SHA1 Message Date
trop[bot]
eb49ed962d fix: outdated execution path for COM activation (#50519)
* fix: outdated execution path

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: use stub exe when detected

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>
2026-03-26 20:32:47 +00:00
electron-roller[bot]
7e36ac67ce chore: bump chromium to 146.0.7680.166 (41-x-y) (#50458)
* chore: bump chromium in DEPS to 146.0.7680.164

* chore: update patches

* chore: bump chromium in DEPS to 146.0.7680.166

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
2026-03-26 15:53:50 -04:00
trop[bot]
cbae32aac6 fix: [a11y] fire AXMenuOpened event when ARIA menu is added to DOM (#50506)
* fix: fire AXMenuOpened event when a visible ARIA menu instance is added to the DOM

Co-authored-by: Keeley Hammond <khammond@slack-corp.com>

* fix: remove redundent FireMenuPopupEndForDeletedMenus

MENU_POPUP_END for deleted menus is already handled by
AXTreeManager::OnNodeWillBeDeleted, which
fires the event directly on the menu node before destruction.

Co-authored-by: Keeley Hammond <khammond@slack-corp.com>

* chore: add feature flag (kDynamicMenuPopupEvents)

Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>

* chore: update patches

Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>

* chore: update patches after trop

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Keeley Hammond <khammond@slack-corp.com>
Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-03-26 09:54:27 -07:00
trop[bot]
880b1e08e7 refactor: remove dead named-window lookup from guest-window-manager (#50497)
The frameNamesToWindow map was a holdover from the BrowserWindowProxy
IPC shim. Since nativeWindowOpen became the only code path, Blink's
FrameTree::FindOrCreateFrameForNavigation resolves named window targets
directly in the renderer, scoped to the opener's browsing context
group. When a matching named window exists, Blink navigates it without
ever sending a CreateNewWindow IPC to the browser, so this map was
never consulted in the legitimate same-opener case.

The only time the map found a match was when two unrelated renderers
happened to use the same target name, in which case openGuestWindow
would short-circuit before consuming the guest WebContents that
Chromium had already created for the new window, leaking it.

Adds a test verifying Blink handles same-opener named-target reuse
end-to-end without any browser-side tracking.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Sam Attard <sattard@anthropic.com>
2026-03-26 11:46:12 -04:00
trop[bot]
aedea576da fix: hex-encode Windows notification icon temp filenames (#50483)
* fix: hex-encode Windows notification icon temp filenames

NotificationPresenterWin was using SHA1HashString(origin.spec()) directly
as the basename for the temporary PNG written for toast icons.

SHA1HashString returns raw digest bytes, so the generated filename could
contain invalid path characters on Windows. That caused WriteFile to fail
when saving notification icons, which left toast XML without the expected
icon path.

Hex-encode the digest before appending .png so the temporary filename is
filesystem-safe while keeping deterministic naming for a given origin.

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* Update shell/browser/notifications/win/notification_presenter_win.cc

Co-authored-by: Robo <hop2deep@gmail.com>

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-26 09:20:32 -04:00
trop[bot]
707541d9b2 fix: fall back to default DPI when GTK returns 0 on Linux (#50489)
GetDefaultPrinterDPI() creates a blank GtkPrintSettings and reads
its resolution, which returns 0 for uninitialized settings. With
DPI=0, SetPrintableAreaIfValid() computes a zero scale factor,
producing empty page dimensions that fail PrintMsgPrintParamsIsValid().

Fall back to kDefaultPdfDpi (72) when GTK returns 0, matching the
existing Windows fallback pattern when CreateDC fails.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-26 09:17:44 -04:00
trop[bot]
3dcb641a99 fix: crash calling OSR shared texture release() after texture GC'd (#50501)
The weak persistent tracking the OffscreenReleaseHolderMonitor was tied
to the texture object, but the release() closure holds a raw pointer to
the monitor via its v8::External data. If JS retained texture.release
while dropping the texture itself, the monitor would be freed on GC and
a later release() call would crash.

Track the release function instead of the texture object. Since the
texture holds release as a property, this keeps the monitor alive as
long as either is reachable.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Sam Attard <sattard@anthropic.com>
2026-03-26 00:49:57 -07:00
trop[bot]
878a763344 fix: crash in clipboard.readImage() on malformed image data (#50492)
gfx::PNGCodec::Decode() returns a null SkBitmap when it fails to decode
the clipboard contents as a PNG. Passing that null bitmap to
gfx::Image::CreateFrom1xBitmap() triggers a crash.

Return an empty gfx::Image instead, matching the existing null-check
pattern in skia_util.cc.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Sam Attard <sattard@anthropic.com>
2026-03-25 22:15:58 -07:00
trop[bot]
6a8d187105 feat: add accessibilityDisplayShouldDifferentiateWithoutColor on macOS (#50408)
feat: add nativeTheme.shouldDifferentiateWithoutColor on macOS

Adds nativeTheme.shouldDifferentiateWithoutColor on macOS that maps to
NSWorkspace.accessibilityDisplayShouldDifferentiateWithoutColor. If true,
the user has indicated that they prefer UI that differentiates items with
something other than color alone. This is useful for users with color
vision deficiency.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Robert Böhnke <robb@robb.is>
2026-03-25 15:53:31 -04:00
trop[bot]
29622930a0 feat: support notification priority on Windows (#50382)
* feat: support notification priority on Windows

Add Windows notifications support urgency/priority levels.
This maps the existing `urgency` option (previously Linux-only) to
Windows toast notification priorities:

- 'critical' maps to ToastNotificationPriority_High, which sorts the
  notification above default-priority items in Action Center.
- 'normal' and 'low' both map to ToastNotificationPriority_Default.

Note that on Windows, 'critical' priority does not prevent the toast
from being auto-dismissed. Users should additionally set `timeoutType`
to 'never' for that behavior.

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* chore: make linter happy

Co-authored-by: Charles Kerr <charles@charleskerr.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
2026-03-25 15:49:00 -04:00
trop[bot]
8b9e721047 fix: don't re-parse URL unnecessarily when handling dialogs (#50399)
* fix: fallback to opaque URL when needed inside dialog callback

Co-authored-by: Noah Gregory <noahmgregory@gmail.com>

* refactor: remove additional URL parsing entirely when showing dialogs

Co-authored-by: Noah Gregory <noahmgregory@gmail.com>

* test: add crash test case for URL-less dialogs

Co-authored-by: Noah Gregory <noahmgregory@gmail.com>

* refactor: exit on events instead of on timeout for dialog crash test

Co-authored-by: Robo <hop2deep@gmail.com>

Co-authored-by: Noah Gregory <noahmgregory@gmail.com>

* style: make linter happy

Co-authored-by: Noah Gregory <noahmgregory@gmail.com>

* style: make linter actually happy

Co-authored-by: Noah Gregory <noahmgregory@gmail.com>

* fix: address failing `safeDialogs` tests

Co-authored-by: Noah Gregory <noahmgregory@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Noah Gregory <noahmgregory@gmail.com>
2026-03-20 13:46:37 -04:00
trop[bot]
43bb93908c fix: correct utility process exit code on Windows (#50386)
* fix: correct utility process exit code on Windows

On Windows, process exit codes are 32-bit unsigned integers (DWORD).
When passed from Chromium to Electron as a signed int and then
implicitly converted to uint64_t, values with the high bit set
(e.g., NTSTATUS codes) undergo sign extension, producing incorrect
values.

Cast the exit code to uint32_t before widening to uint64_t to
prevent sign extension and preserve the original Windows exit code.

Fixes #49455

Co-authored-by: João Silva <joaomrsilva@tecnico.ulisboa.pt>

* fix: narrow HandleTermination and Shutdown to uint32_t, add tests

Co-authored-by: João Silva <joaomrsilva@tecnico.ulisboa.pt>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: João Silva <joaomrsilva@tecnico.ulisboa.pt>
2026-03-19 18:48:27 -07:00
trop[bot]
b0055e0500 fix: improved the appearance of shadows and borders on frameless windows on Wayland (#50213)
fix: improved the appearance of shadows and borders on frameless windows on Wayland (#50007)

* remove painting from linux frame layout

* use chromium csd strategy for frameless windows

* Apply suggestions from code review

Remove unneeded virtual methods



* removed inline destructors

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Mitchell Cohen <mitch.cohen@me.com>
2026-03-19 15:42:58 -04:00
trop[bot]
9a7381a328 ci: output build cache hit rate as GHA annotation (#50370)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2026-03-19 12:04:12 -04:00
trop[bot]
af3e0fca24 fix: always call the original impl in swizzled mousedown impls (#50354)
fix: always call the original implementation in swizzled mousedown implementations

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Noah Gregory <noahmgregory@gmail.com>
2026-03-18 20:11:50 -07:00
trop[bot]
99d879b52e chore: Respect HTTP(S) proxy env variable for Yarn (#50350)
Respect HTTP(S) proxy env variable for Yarn

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Filip Mösner <filip.mosner@seznam.cz>
2026-03-18 20:03:10 -07:00
electron-roller[bot]
3d8105ae7f chore: bump chromium to 146.0.7680.153 (41-x-y) (#50346)
* chore: bump chromium in DEPS to 146.0.7680.153

* chore: update patches

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
2026-03-18 17:49:48 -04:00
trop[bot]
aba01d38dc fix: correctly track BaseWindow::IsActive() on MacOS (#50340)
fix: correctly set IsActive() in BaseWindow on MacOS

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Kyle Cutler <kycutler@microsoft.com>
2026-03-18 17:11:46 -04:00
trop[bot]
a0f01336a3 fix: ensure WebContents::WasShown runs when window is shown (#50343)
Avoids a freeze when failing to enter fullscreen on macOS.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Beutner <beutner.john@gmail.com>
2026-03-18 14:53:09 -04:00
trop[bot]
4a98b4e27e docs: fix markdown formatting in fuses.md (#50333)
* docs: fix markdown formatting in fuses.md

* Use bulleted list (was being run together on one line)
* Wrap ASCII diagram in code block

Co-authored-by: Ryan Zimmerman <ryan@exodus.io>

* docs: apply suggestions from code review

Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
Co-authored-by: Erick Zhao <erick@hotmail.ca>

Co-authored-by: Ryan Zimmerman <ryan@exodus.io>

* docs: fix misapplied suggestion

Co-authored-by: Ryan Zimmerman <ryan@exodus.io>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Ryan Zimmerman <ryan@exodus.io>
2026-03-18 16:09:27 +01:00
50 changed files with 656 additions and 284 deletions

View File

@@ -9,4 +9,8 @@ npmMinimalAgeGate: 10080
npmPreapprovedPackages:
- "@electron/*"
httpProxy: "${HTTP_PROXY:-}"
httpsProxy: "${HTTPS_PROXY:-}"
yarnPath: .yarn/releases/yarn-4.12.0.cjs

2
DEPS
View File

@@ -2,7 +2,7 @@ gclient_gn_args_from = 'src'
vars = {
'chromium_version':
'146.0.7680.80',
'146.0.7680.166',
'node_version':
'v24.14.0',
'nan_version':

View File

@@ -245,6 +245,10 @@ static_library("chrome") {
"//chrome/browser/ui/views/dark_mode_manager_linux.cc",
"//chrome/browser/ui/views/dark_mode_manager_linux.h",
]
sources += [
"//chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.cc",
"//chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.h",
]
public_deps += [ "//components/dbus" ]
}

View File

@@ -84,3 +84,7 @@ Currently, Windows high contrast is the only system setting that triggers forced
### `nativeTheme.prefersReducedTransparency` _Readonly_
A `boolean` that indicates whether the user has chosen via system accessibility settings to reduce transparency at the OS level.
### `nativeTheme.shouldDifferentiateWithoutColor` _macOS_ _Readonly_
A `boolean` that indicates whether the user prefers UI that differentiates items using something other than color alone (e.g. shapes or labels). This maps to [NSWorkspace.accessibilityDisplayShouldDifferentiateWithoutColor](https://developer.apple.com/documentation/appkit/nsworkspace/accessibilitydisplayshoulddifferentiatewithoutcolor).

View File

@@ -42,11 +42,15 @@ Returns `boolean` - Whether or not desktop notifications are supported on the cu
* `timeoutType` string (optional) _Linux_ _Windows_ - The timeout duration of the notification. Can be 'default' or 'never'.
* `replyPlaceholder` string (optional) _macOS_ - The placeholder to write in the inline reply input field.
* `sound` string (optional) _macOS_ - The name of the sound file to play when the notification is shown.
* `urgency` string (optional) _Linux_ - The urgency level of the notification. Can be 'normal', 'critical', or 'low'.
* `urgency` string (optional) _Linux_ _Windows_ - The urgency level of the notification. Can be 'normal', 'critical', or 'low'.
* `actions` [NotificationAction[]](structures/notification-action.md) (optional) _macOS_ - Actions to add to the notification. Please read the available actions and limitations in the `NotificationAction` documentation.
* `closeButtonText` string (optional) _macOS_ - A custom title for the close button of an alert. An empty string will cause the default localized text to be used.
* `toastXml` string (optional) _Windows_ - A custom description of the Notification on Windows superseding all properties above. Provides full customization of design and behavior of the notification.
> [!NOTE]
> On Windows, `urgency` type 'critical' sorts the notification higher in Action Center (above default priority notifications), but does not prevent auto-dismissal. To prevent auto-dismissal, you should also set
> `timeoutType` to 'never'.
### Instance Events
Objects created with `new Notification` emit the following events:

View File

@@ -146,13 +146,15 @@ The extra privileges granted to the `file://` protocol by this fuse are incomple
The `wasmTrapHandlers` fuse controls whether V8 will use signal handlers to trap Out of Bounds memory
access from WebAssembly. The feature works by surrounding the WebAssembly memory with large guard regions
and then installing a signal handler that traps attempt to access memory in the guard region. The feature
is only supported on the following 64-bit systems.
is only supported on the following 64-bit systems:
Linux. MacOS, Windows - x86_64
Linux, MacOS - aarch64
* Linux, macOS, Windows - x86_64
* Linux, macOS - aarch64
```text
| Guard Pages | WASM heap | Guard Pages |
|-----8GB-----| |-----8GB-----|
```
When the fuse is disabled V8 will use explicit bound checks in the generated WebAssembly code to ensure
memory safety. However, this method has some downsides

View File

@@ -782,8 +782,7 @@ WebContents.prototype._init = function () {
const originCounts = new Map<string, number>();
const openDialogs = new Set<AbortController>();
this.on('-run-dialog', async (info, callback) => {
const originUrl = new URL(info.frame.url);
const origin = originUrl.protocol === 'file:' ? originUrl.href : originUrl.origin;
const origin = info.frame.origin === 'file://' ? info.frame.url : info.frame.origin;
if ((originCounts.get(origin) ?? 0) < 0) return callback(false, '');
const prefs = this.getLastWebPreferences();

View File

@@ -17,11 +17,6 @@ export type WindowOpenArgs = {
features: string,
}
const frameNamesToWindow = new Map<string, WebContents>();
const registerFrameNameToGuestWindow = (name: string, webContents: WebContents) => frameNamesToWindow.set(name, webContents);
const unregisterFrameName = (name: string) => frameNamesToWindow.delete(name);
const getGuestWebContentsByFrameName = (name: string) => frameNamesToWindow.get(name);
/**
* `openGuestWindow` is called to create and setup event handling for the new
* window.
@@ -47,20 +42,6 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
...overrideBrowserWindowOptions
};
// To spec, subsequent window.open calls with the same frame name (`target` in
// spec parlance) will reuse the previous window.
// https://html.spec.whatwg.org/multipage/window-object.html#apis-for-creating-and-navigating-browsing-contexts-by-name
const existingWebContents = getGuestWebContentsByFrameName(frameName);
if (existingWebContents) {
if (existingWebContents.isDestroyed()) {
// FIXME(t57ser): The webContents is destroyed for some reason, unregister the frame name
unregisterFrameName(frameName);
} else {
existingWebContents.loadURL(url);
return;
}
}
if (createWindow) {
const webContents = createWindow({
webContents: guest,
@@ -72,7 +53,7 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
throw new Error('Invalid webContents. Created window should be connected to webContents passed with options object.');
}
handleWindowLifecycleEvents({ embedder, frameName, guest, outlivesOpener });
handleWindowLifecycleEvents({ embedder, guest, outlivesOpener });
}
return;
@@ -96,7 +77,7 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
});
}
handleWindowLifecycleEvents({ embedder, frameName, guest: window.webContents, outlivesOpener });
handleWindowLifecycleEvents({ embedder, guest: window.webContents, outlivesOpener });
embedder.emit('did-create-window', window, { url, frameName, options: browserWindowOptions, disposition, referrer, postData });
}
@@ -107,10 +88,9 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
* too is the guest destroyed; this is Electron convention and isn't based in
* browser behavior.
*/
const handleWindowLifecycleEvents = function ({ embedder, guest, frameName, outlivesOpener }: {
const handleWindowLifecycleEvents = function ({ embedder, guest, outlivesOpener }: {
embedder: WebContents,
guest: WebContents,
frameName: string,
outlivesOpener: boolean
}) {
const closedByEmbedder = function () {
@@ -128,13 +108,6 @@ const handleWindowLifecycleEvents = function ({ embedder, guest, frameName, outl
embedder.once('current-render-view-deleted' as any, closedByEmbedder);
}
guest.once('destroyed', closedByUser);
if (frameName) {
registerFrameNameToGuestWindow(frameName, guest);
guest.once('destroyed', function () {
unregisterFrameName(frameName);
});
}
};
// Security options that child windows will always inherit from parent windows

View File

@@ -147,3 +147,4 @@ fix_update_dbus_signal_signature_for_xdg_globalshortcuts_portal.patch
fix_set_correct_app_id_on_linux.patch
fix_pass_trigger_for_global_shortcuts_on_wayland.patch
feat_plumb_node_integration_in_worker_through_workersettings.patch
fix_fire_menu_popup_start_for_dynamically_created_aria_menus.patch

View File

@@ -33,10 +33,10 @@ index 4a742db71f62f9ac891ceeb0604ca0b99d1d89c1..2c5af6482e2b6905552a05b16d3df0a4
"//base",
"//build:branding_buildflags",
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 2fc3a991d89093ff9139eb09d74123197155caff..0862aa96c2a7b496338ac0593f84fcfa21f25572 100644
index a2a14349d40ce34831ab063cd5eb55cd5085c814..1a861ff7867f19935178c8368a9a720230fee026 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4749,7 +4749,7 @@ static_library("browser") {
@@ -4751,7 +4751,7 @@ static_library("browser") {
]
}
@@ -46,10 +46,10 @@ index 2fc3a991d89093ff9139eb09d74123197155caff..0862aa96c2a7b496338ac0593f84fcfa
# than here in :chrome_dll.
deps += [ "//chrome:packed_resources_integrity_header" ]
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 7d5a246787bc3cc3bcb883aa78121d3d3f124780..b5de35620bc636d5e1d0d5770d898f564843bcef 100644
index 40ea51f97470e2b86f8d2d373ea99a2a71ad185e..db6a2291ce77d89c8e28a1435336fd939e436906 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -7728,9 +7728,12 @@ test("unit_tests") {
@@ -7731,9 +7731,12 @@ test("unit_tests") {
"//chrome/notification_helper",
]
@@ -63,7 +63,7 @@ index 7d5a246787bc3cc3bcb883aa78121d3d3f124780..b5de35620bc636d5e1d0d5770d898f56
"//chrome//services/util_win:unit_tests",
"//chrome/app:chrome_dll_resources",
"//chrome/app:win_unit_tests",
@@ -8698,6 +8701,10 @@ test("unit_tests") {
@@ -8703,6 +8706,10 @@ test("unit_tests") {
"../browser/performance_manager/policies/background_tab_loading_policy_unittest.cc",
]
@@ -74,7 +74,7 @@ index 7d5a246787bc3cc3bcb883aa78121d3d3f124780..b5de35620bc636d5e1d0d5770d898f56
sources += [
# The importer code is not used on Android.
"../common/importer/firefox_importer_utils_unittest.cc",
@@ -8755,7 +8762,6 @@ test("unit_tests") {
@@ -8760,7 +8767,6 @@ test("unit_tests") {
# TODO(crbug.com/417513088): Maybe merge with the non-android `deps` declaration above?
deps += [
"../browser/screen_ai:screen_ai_install_state",

View File

@@ -9,10 +9,10 @@ potentially prevent a window from being created.
TODO(loc): this patch is currently broken.
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 46368e70af175d8d0ab0fb5a36d258e48270371e..8d7be769a6c76650ae999338578215dcd324c199 100644
index 2d8a70f5fc0f6c2dc2a7587b7bc2e43dbcee8f0e..a87bd09d7a12c5f003488792843cd1807ee1e30f 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -9990,6 +9990,7 @@ void RenderFrameHostImpl::CreateNewWindow(
@@ -9997,6 +9997,7 @@ void RenderFrameHostImpl::CreateNewWindow(
last_committed_origin_, params->window_container_type,
params->target_url, params->referrer.To<Referrer>(),
params->frame_name, params->disposition, *params->features,

View File

@@ -313,7 +313,7 @@ index 18f283e625101318ee14b50e6e765dfd1c9a1a44..44a3a55974c9e4b9e715574075f25661
auto DrawAsSinglePath = [&]() {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 70a7e2a5203d3cdddbad7eecca28d65945522fed..35751435ebe8205a5c9d73bed0422ccbe61ab8b4 100644
index d5fe1468676285540909ee078d5b88a9bd8e197c..427c77b17d3e130c43a69d79d330bf5c4759f4cc 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -214,6 +214,10 @@

View File

@@ -28,7 +28,7 @@ The patch should be removed in favor of either:
Upstream bug https://bugs.chromium.org/p/chromium/issues/detail?id=1081397.
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 5b79df01e0a5ee81919ebed7d689e430fe7fe305..b11808a69483f4cbcc56d90cc6161984df90c1e4 100644
index 7d101d40116bf743f940f32ba4c9b507aa9a235b..2aa1584fd451fb15ec6084fb0c19724e6c63e0e3 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -11666,6 +11666,11 @@ url::Origin NavigationRequest::GetOriginForURLLoaderFactoryUnchecked() {

View File

@@ -0,0 +1,95 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Keeley Hammond <khammond@slack-corp.com>
Date: Thu, 19 Mar 2026 00:34:37 -0700
Subject: fix: fire MENU_POPUP_START for dynamically created ARIA menus
When an ARIA menu element is dynamically created (e.g. via appendChild)
rather than being shown by toggling visibility, the AXMenuOpened event
was not fired. The OnIgnoredChanged path handles the visibility toggle
case, but OnAtomicUpdateFinished did not fire MENU_POPUP_START for
newly created menu nodes.
Previous attempts to fix this (crbug.com/1254875) were reverted because
they fired the event too eagerly in OnNodeCreated (before the tree was
fully formed) and without filtering, causing regressions with screen
readers on pages that misused role="menu".
This fix addresses both issues:
1. Fires MENU_POPUP_START in OnAtomicUpdateFinished (after the tree
update is complete) rather than in OnNodeCreated.
2. Only fires if the menu has at least one menuitem child, filtering
out false positives from misused role="menu" elements.
MENU_POPUP_END for deleted menus is already handled by
AXTreeManager::OnNodeWillBeDeleted, which fires the event directly
on the menu node before destruction.
The change is behind the DynamicMenuPopupEvents feature flag, disabled
by default, to allow stabilization before enabling by default. Enable
with --enable-features=DynamicMenuPopupEvents.
This patch can be removed when a CL containing the fix is accepted
into Chromium.
Bug: 40794596
diff --git a/ui/accessibility/ax_event_generator.cc b/ui/accessibility/ax_event_generator.cc
index 5e0d7a48b4a039db67b5cc6b7e86103739702b40..517fb5e9904f3907de177e172c76328910bb7333 100644
--- a/ui/accessibility/ax_event_generator.cc
+++ b/ui/accessibility/ax_event_generator.cc
@@ -4,6 +4,7 @@
#include "ui/accessibility/ax_event_generator.h"
+#include "base/feature_list.h"
#include "base/no_destructor.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_event.h"
@@ -12,6 +13,12 @@
namespace ui {
+// Feature flag for firing MENU_POPUP_START for dynamically created ARIA menus.
+// Disabled by default to allow stabilization before enabling globally.
+BASE_FEATURE(kDynamicMenuPopupEvents,
+ "DynamicMenuPopupEvents",
+ base::FEATURE_DISABLED_BY_DEFAULT);
+
namespace {
bool HasEvent(const std::set<AXEventGenerator::EventParams>& node_events,
@@ -914,12 +921,31 @@ void AXEventGenerator::OnAtomicUpdateFinished(
/*new_value*/ true);
}
- if (IsAlert(change.node->GetRole()))
+ if (IsAlert(change.node->GetRole())) {
AddEvent(change.node, Event::ALERT);
- else if (change.node->data().IsActiveLiveRegionRoot())
+ } else if (change.node->data().IsActiveLiveRegionRoot()) {
AddEvent(change.node, Event::LIVE_REGION_CREATED);
- else if (change.node->data().IsContainedInActiveLiveRegion())
+ } else if (change.node->data().IsContainedInActiveLiveRegion()) {
FireLiveRegionEvents(change.node, /* is_removal */ false);
+ }
+
+ // Fire MENU_POPUP_START when a menu is dynamically created (e.g. via
+ // appendChild). The OnIgnoredChanged path handles menus that already exist
+ // in the DOM and are shown/hidden. This handles the case where the menu
+ // element itself is created on the fly.
+ // Only fire if the menu has at least one menuitem child, to avoid false
+ // positives from elements that misuse role="menu".
+ if (base::FeatureList::IsEnabled(kDynamicMenuPopupEvents) &&
+ change.node->GetRole() == ax::mojom::Role::kMenu &&
+ !change.node->IsInvisibleOrIgnored()) {
+ for (auto iter = change.node->UnignoredChildrenBegin();
+ iter != change.node->UnignoredChildrenEnd(); ++iter) {
+ if (IsMenuItem(iter->GetRole())) {
+ AddEvent(change.node, Event::MENU_POPUP_START);
+ break;
+ }
+ }
+ }
}
FireActiveDescendantEvents();

View File

@@ -17,7 +17,7 @@ Revert "Reland "Port net::CookieCryptoDelegate to os_crypt async""
This reverts commit f01b115c7e21a09cc762f65bf7fd9c6ea9d9d0f8.
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 0862aa96c2a7b496338ac0593f84fcfa21f25572..aed5a316bd3d97df715f779273ae4c283cd29c92 100644
index 1a861ff7867f19935178c8368a9a720230fee026..b1ca947122f4ea715be18a0fd4e75b30fffc5a3c 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -714,6 +714,8 @@ static_library("browser") {

View File

@@ -1189,7 +1189,7 @@ index a1068589ad844518038ee7bc15a3de9bc5cba525..1ff781c49f086ec8015c7d3c44567dbe
} // namespace content
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 8575983261c7b57fc85097edb94a8e6f306974f9..aae50b6830450baf27f2834a8187540d7ff6eb35 100644
index d368b2481156bb79c6e74c8b09a828eb2fa2d44c..07cbf495717714d71d977a8820e08050c3062526 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -700,6 +700,7 @@ static_library("test_support") {
@@ -1217,7 +1217,7 @@ index 8575983261c7b57fc85097edb94a8e6f306974f9..aae50b6830450baf27f2834a8187540d
]
if (!(is_chromeos && target_cpu == "arm64" && current_cpu == "arm")) {
@@ -3411,6 +3415,7 @@ test("content_unittests") {
@@ -3412,6 +3416,7 @@ test("content_unittests") {
"//ui/shell_dialogs",
"//ui/webui:test_support",
"//url",

View File

@@ -10,10 +10,10 @@ on Windows. We should refactor our code so that this patch isn't
necessary.
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index eecdbe8a279ef1a7d9aed4f5496e871d54092e0f..16ff3ffbe8300534cef76f857284ef92ad0a88f6 100644
index d17637a54208450504d071a3f10c20668cfbe76d..f3ffc975d794f356d9a83837fd977e758b726501 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -27059,6 +27059,21 @@
@@ -27095,6 +27095,21 @@
]
}
],

View File

@@ -15,7 +15,7 @@ Note that we also need to manually update embedder's
`api::WebContents::IsFullscreenForTabOrPending` value.
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 8d7be769a6c76650ae999338578215dcd324c199..3e8021289c00ec6b15457b17173dfed386eac2fe 100644
index a87bd09d7a12c5f003488792843cd1807ee1e30f..b38240fd422163f09bfb8d4b40213a1940a72acd 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -9097,6 +9097,17 @@ void RenderFrameHostImpl::EnterFullscreen(

View File

@@ -32,7 +32,8 @@ async function main () {
}));
const hitRate = stats.CacheHit / (stats.Remote + stats.CacheHit + stats.LocalFallback);
console.log(`Effective cache hit rate: ${(hitRate * 100).toFixed(2)}%`);
const messagePrefix = process.env.GITHUB_ACTIONS ? '::notice title=Build Stats::' : '';
console.log(`${messagePrefix}Effective cache hit rate: ${(hitRate * 100).toFixed(2)}%`);
if (uploadStats) {
if (!process.env.DD_API_KEY) {

View File

@@ -317,6 +317,12 @@ void BaseWindow::OnWindowSheetEnd() {
Emit("sheet-end");
}
void BaseWindow::OnWindowIsKeyChanged(bool is_key) {
#if BUILDFLAG(IS_MAC)
window()->SetActive(is_key);
#endif
}
void BaseWindow::OnWindowEnterHtmlFullScreen() {
Emit("enter-html-full-screen");
}

View File

@@ -85,6 +85,7 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
void OnWindowRotateGesture(float rotation) override;
void OnWindowSheetBegin() override;
void OnWindowSheetEnd() override;
void OnWindowIsKeyChanged(bool is_key) override;
void OnWindowEnterFullScreen() override;
void OnWindowLeaveFullScreen() override;
void OnWindowEnterHtmlFullScreen() override;

View File

@@ -280,16 +280,22 @@ v8::Local<v8::Value> BrowserWindow::GetWebContents(v8::Isolate* isolate) {
}
void BrowserWindow::OnWindowShow() {
if (!web_contents_shown_) {
web_contents()->WasShown();
web_contents_shown_ = true;
}
BaseWindow::OnWindowShow();
}
void BrowserWindow::OnWindowHide() {
web_contents()->WasOccluded();
web_contents_shown_ = false;
BaseWindow::OnWindowHide();
}
void BrowserWindow::Show() {
web_contents()->WasShown();
web_contents_shown_ = true;
BaseWindow::Show();
}
@@ -298,6 +304,7 @@ void BrowserWindow::ShowInactive() {
if (IsModal())
return;
web_contents()->WasShown();
web_contents_shown_ = true;
BaseWindow::ShowInactive();
}

View File

@@ -80,6 +80,7 @@ class BrowserWindow : public BaseWindow,
// Helpers.
v8::Global<v8::Value> web_contents_;
bool web_contents_shown_ = false;
v8::Global<v8::Value> web_contents_view_;
base::WeakPtr<api::WebContents> api_web_contents_;

View File

@@ -147,7 +147,12 @@ gin::ObjectTemplateBuilder NativeTheme::GetObjectTemplateBuilder(
&NativeTheme::ShouldUseInvertedColorScheme)
.SetProperty("inForcedColorsMode", &NativeTheme::InForcedColorsMode)
.SetProperty("prefersReducedTransparency",
&NativeTheme::GetPrefersReducedTransparency);
&NativeTheme::GetPrefersReducedTransparency)
#if BUILDFLAG(IS_MAC)
.SetProperty("shouldDifferentiateWithoutColor",
&NativeTheme::ShouldDifferentiateWithoutColor)
#endif
;
}
const char* NativeTheme::GetTypeName() {

View File

@@ -56,6 +56,9 @@ class NativeTheme final : public gin_helper::DeprecatedWrappable<NativeTheme>,
bool ShouldUseInvertedColorScheme();
bool InForcedColorsMode();
bool GetPrefersReducedTransparency();
#if BUILDFLAG(IS_MAC)
bool ShouldDifferentiateWithoutColor();
#endif
// ui::NativeThemeObserver:
void OnNativeThemeUpdated(ui::NativeTheme* theme) override;

View File

@@ -26,4 +26,9 @@ void NativeTheme::UpdateMacOSAppearanceForOverrideValue(
[[NSApplication sharedApplication] setAppearance:new_appearance];
}
bool NativeTheme::ShouldDifferentiateWithoutColor() {
return [[NSWorkspace sharedWorkspace]
accessibilityDisplayShouldDifferentiateWithoutColor];
}
} // namespace electron::api

View File

@@ -259,7 +259,7 @@ void UtilityProcessWrapper::OnServiceProcessLaunch(
EmitWithoutEvent("spawn");
}
void UtilityProcessWrapper::HandleTermination(uint64_t exit_code) {
void UtilityProcessWrapper::HandleTermination(uint32_t exit_code) {
// HandleTermination is called from multiple callsites,
// we need to ensure we only process it for the first callsite.
if (terminated_)
@@ -327,7 +327,7 @@ void UtilityProcessWrapper::CloseConnectorPort() {
}
}
void UtilityProcessWrapper::Shutdown(uint64_t exit_code) {
void UtilityProcessWrapper::Shutdown(uint32_t exit_code) {
node_service_remote_.reset();
HandleTermination(exit_code);
}

View File

@@ -57,7 +57,7 @@ class UtilityProcessWrapper final
static gin_helper::Handle<UtilityProcessWrapper> Create(gin::Arguments* args);
static raw_ptr<UtilityProcessWrapper> FromProcessId(base::ProcessId pid);
void Shutdown(uint64_t exit_code);
void Shutdown(uint32_t exit_code);
// gin_helper::Wrappable
static gin::DeprecatedWrapperInfo kWrapperInfo;
@@ -77,7 +77,7 @@ class UtilityProcessWrapper final
void OnServiceProcessLaunch(const base::Process& process);
void CloseConnectorPort();
void HandleTermination(uint64_t exit_code);
void HandleTermination(uint32_t exit_code);
void PostMessage(gin::Arguments* args);
bool Kill();

View File

@@ -45,7 +45,7 @@ struct NotificationOptions {
std::u16string timeout_type;
std::u16string reply_placeholder;
std::u16string sound;
std::u16string urgency; // Linux
std::u16string urgency; // Linux/Windows
std::vector<NotificationAction> actions;
std::u16string close_button_text;
std::u16string toast_xml;

View File

@@ -72,7 +72,8 @@ std::wstring NotificationPresenterWin::SaveIconToFilesystem(
std::string filename;
if (origin.is_valid()) {
filename = base::SHA1HashString(origin.spec()) + ".png";
const auto hash = base::SHA1HashString(origin.spec());
filename = base::HexEncode(hash) + ".png";
} else {
const int64_t now_usec = base::Time::Now().since_origin().InMicroseconds();
filename = base::NumberToString(now_usec) + ".png";

View File

@@ -96,6 +96,21 @@ std::wstring GetExecutablePath() {
return std::wstring(path, len);
}
// Installers sometimes put the running app in a versioned subfolder and ship a
// stub with the same filename one directory up. Point the Start Menu shortcut
// at the stub when it exists so toast activation and updates keep a stable
// launch path.
std::wstring GetShortcutTargetPath(const std::wstring& exe_path) {
if (exe_path.empty())
return L"";
base::FilePath exe_fp(exe_path);
base::FilePath stub_candidate =
exe_fp.DirName().DirName().Append(exe_fp.BaseName());
if (base::PathExists(stub_candidate))
return stub_candidate.value();
return exe_path;
}
void EnsureCLSIDRegistry() {
std::wstring exe = GetExecutablePath();
if (exe.empty())
@@ -116,7 +131,10 @@ void EnsureCLSIDRegistry() {
server_key.WriteValue(nullptr, exe.c_str());
}
bool ExistingShortcutValid(const base::FilePath& lnk_path, PCWSTR aumid) {
bool ExistingShortcutValid(const base::FilePath& lnk_path,
PCWSTR aumid,
const std::wstring& expected_target_path,
const std::wstring& expected_working_dir) {
if (!base::PathExists(lnk_path))
return false;
Microsoft::WRL::ComPtr<IShellLink> existing;
@@ -128,6 +146,31 @@ bool ExistingShortcutValid(const base::FilePath& lnk_path, PCWSTR aumid) {
FAILED(pf->Load(lnk_path.value().c_str(), STGM_READ))) {
return false;
}
// After an auto-update the .lnk may still have the correct AUMID/CLSID but
// point at an old install path; treat that as invalid so we rewrite it.
wchar_t target_path[MAX_PATH];
if (FAILED(existing->GetPath(target_path, MAX_PATH, nullptr, SLGP_RAWPATH)))
return false;
if (base::FilePath::CompareIgnoreCase(
base::FilePath(expected_target_path).value(),
base::FilePath(target_path).value()) != 0) {
return false;
}
wchar_t work_dir[MAX_PATH];
work_dir[0] = L'\0';
if (FAILED(existing->GetWorkingDirectory(work_dir, MAX_PATH)))
return false;
base::FilePath expected_cwd =
base::FilePath(expected_working_dir).NormalizePathSeparators();
base::FilePath actual_cwd =
base::FilePath(work_dir).NormalizePathSeparators();
if (base::FilePath::CompareIgnoreCase(expected_cwd.value(),
actual_cwd.value()) != 0) {
return false;
}
Microsoft::WRL::ComPtr<IPropertyStore> store;
if (FAILED(existing.As(&store)))
return false;
@@ -157,6 +200,7 @@ void EnsureShortcut() {
std::wstring exe = GetExecutablePath();
if (exe.empty())
return;
std::wstring shortcut_target = GetShortcutTargetPath(exe);
PWSTR programs_path = nullptr;
if (FAILED(
@@ -195,18 +239,20 @@ void EnsureShortcut() {
}
}
if (ExistingShortcutValid(lnk_path, aumid))
const std::wstring expected_working_dir =
base::FilePath(exe).DirName().value();
if (ExistingShortcutValid(lnk_path, aumid, shortcut_target,
expected_working_dir))
return;
Microsoft::WRL::ComPtr<IShellLink> shell_link;
if (FAILED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&shell_link))))
return;
shell_link->SetPath(exe.c_str());
shell_link->SetPath(shortcut_target.c_str());
shell_link->SetArguments(L"");
shell_link->SetDescription(product_name.c_str());
shell_link->SetWorkingDirectory(
base::FilePath(exe).DirName().value().c_str());
shell_link->SetWorkingDirectory(expected_working_dir.c_str());
Microsoft::WRL::ComPtr<IPropertyStore> prop_store;
if (SUCCEEDED(shell_link.As(&prop_store))) {

View File

@@ -280,8 +280,9 @@ void WindowsToastNotification::CreateToastNotificationOnBackgroundThread(
// Continue to create the toast notification
ComPtr<ABI::Windows::UI::Notifications::IToastNotification>
toast_notification;
if (!CreateToastNotification(toast_xml, notification_id, weak_notification,
ui_task_runner, &toast_notification)) {
if (!CreateToastNotification(toast_xml, options, notification_id,
weak_notification, ui_task_runner,
&toast_notification)) {
return; // Error already posted to UI thread
}
@@ -349,6 +350,7 @@ bool WindowsToastNotification::CreateToastXmlDocument(
// returns the created notification via out parameter.
bool WindowsToastNotification::CreateToastNotification(
ComPtr<ABI::Windows::Data::Xml::Dom::IXmlDocument> toast_xml,
const NotificationOptions& options,
const std::string& notification_id,
base::WeakPtr<Notification> weak_notification,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
@@ -416,6 +418,27 @@ bool WindowsToastNotification::CreateToastNotification(
return false;
}
ComPtr<winui::Notifications::IToastNotification4> toast4;
hr = (*toast_notification)->QueryInterface(IID_PPV_ARGS(&toast4));
if (SUCCEEDED(hr)) {
winui::Notifications::ToastNotificationPriority priority =
winui::Notifications::ToastNotificationPriority::
ToastNotificationPriority_Default;
if (options.urgency == u"critical") {
priority = winui::Notifications::ToastNotificationPriority::
ToastNotificationPriority_High;
}
hr = toast4->put_Priority(priority);
if (FAILED(hr)) {
std::string err = base::StrCat({"WinAPI: Setting priority failed, ERROR ",
FailureResultToString(hr)});
DebugLog(err);
PostNotificationFailedToUIThread(weak_notification, err, ui_task_runner);
return false;
}
}
return true;
}

View File

@@ -94,6 +94,7 @@ class WindowsToastNotification : public Notification {
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner);
static bool CreateToastNotification(
ComPtr<ABI::Windows::Data::Xml::Dom::IXmlDocument> toast_xml,
const NotificationOptions& options,
const std::string& notification_id,
base::WeakPtr<Notification> weak_notification,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,

View File

@@ -59,6 +59,8 @@ gfx::Size GetDefaultPrinterDPI(const std::u16string& device_name) {
GtkPrintSettings* print_settings = gtk_print_settings_new();
int dpi = gtk_print_settings_get_resolution(print_settings);
g_object_unref(print_settings);
if (dpi <= 0)
dpi = printing::kDefaultPdfDpi;
return {dpi, dpi};
#endif
}

View File

@@ -87,8 +87,8 @@ MouseDownImpl g_nsnextstepframe_mousedown;
(electron::NativeWindowMac*)[(id)self.window shell];
if (shell && !shell->has_frame())
[self cr_mouseDownOnFrameView:event];
g_nsthemeframe_mousedown(self, @selector(mouseDown:), event);
}
g_nsthemeframe_mousedown(self, @selector(mouseDown:), event);
}
- (void)swiz_nsnextstepframe_mouseDown:(NSEvent*)event {
@@ -98,8 +98,8 @@ MouseDownImpl g_nsnextstepframe_mousedown;
if (shell && !shell->has_frame()) {
[self cr_mouseDownOnFrameView:event];
}
g_nsnextstepframe_mousedown(self, @selector(mouseDown:), event);
}
g_nsnextstepframe_mousedown(self, @selector(mouseDown:), event);
}
- (void)swiz_nsview_swipeWithEvent:(NSEvent*)event {

View File

@@ -243,8 +243,8 @@ void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
// The opaque region is a list of rectangles that contain only fully
// opaque pixels of the window. We need to convert the clipping
// rounded-rect into this format.
SkRRect rrect = layout->GetRoundedWindowContentBounds();
gfx::RectF rectf(layout->GetWindowContentBounds());
SkRRect rrect = layout->GetRoundedWindowBounds();
gfx::RectF rectf(layout->GetWindowBounds());
rectf.Scale(scale);
// It is acceptable to omit some pixels that are opaque, but the region
// must not include any translucent pixels. Therefore, we must

View File

@@ -112,7 +112,7 @@ ClientFrameViewLinux::~ClientFrameViewLinux() {
void ClientFrameViewLinux::Init(NativeWindowViews* window,
views::Widget* frame) {
FramelessView::Init(window, frame);
linux_frame_layout_ = std::make_unique<LinuxCSDFrameLayout>(window);
linux_frame_layout_ = std::make_unique<LinuxCSDNativeFrameLayout>(window);
// Unretained() is safe because the subscription is saved into an instance
// member and thus will be cancelled upon the instance's destruction.
@@ -156,7 +156,8 @@ void ClientFrameViewLinux::OnWindowButtonOrderingChange() {
}
int ClientFrameViewLinux::ResizingBorderHitTest(const gfx::Point& point) {
return ResizingBorderHitTestImpl(point, RestoredFrameBorderInsets());
return ResizingBorderHitTestImpl(
point, linux_frame_layout_->GetResizeBorderInsets());
}
gfx::Rect ClientFrameViewLinux::GetBoundsForClientView() const {
@@ -235,8 +236,11 @@ void ClientFrameViewLinux::Layout(PassKey) {
}
void ClientFrameViewLinux::OnPaint(gfx::Canvas* canvas) {
linux_frame_layout_->PaintWindowFrame(
canvas, GetLocalBounds(), GetTitlebarBounds(), ShouldPaintAsActive());
if (auto* frame_provider = linux_frame_layout_->GetFrameProvider()) {
frame_provider->PaintWindowFrame(
canvas, GetLocalBounds(), GetTitlebarBounds().bottom(),
ShouldPaintAsActive(), linux_frame_layout_->GetInputInsets());
}
}
void ClientFrameViewLinux::PaintAsActiveChanged() {
@@ -267,7 +271,7 @@ void ClientFrameViewLinux::UpdateThemeValues() {
}
theme_values_.window_border_radius =
linux_frame_layout_->GetFrameProvider()->GetTopCornerRadiusDip();
linux_frame_layout_->GetTopCornerRadiusDip();
gtk::GtkStyleContextGet(headerbar_context, "min-height",
&theme_values_.titlebar_min_height, nullptr);

View File

@@ -112,7 +112,7 @@ class ClientFrameViewLinux : public FramelessView,
gfx::Insets GetTitlebarContentInsets() const;
gfx::Rect GetTitlebarContentBounds() const;
std::unique_ptr<LinuxFrameLayout> linux_frame_layout_;
std::unique_ptr<LinuxCSDNativeFrameLayout> linux_frame_layout_;
raw_ptr<ui::NativeTheme> theme_;
ThemeValues theme_values_;

View File

@@ -4,14 +4,20 @@
// found in the LICENSE file.
#include "shell/browser/ui/views/linux_frame_layout.h"
#include <algorithm>
#include "base/i18n/rtl.h"
#include "chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.h" // nogncheck
#include "shell/browser/linux/x11_util.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
#include "ui/gfx/canvas.h"
#include "third_party/skia/include/core/SkRRect.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/linux/linux_ui.h"
#include "ui/native_theme/native_theme.h"
#include "ui/linux/window_frame_provider.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/widget/widget.h"
namespace electron {
@@ -21,151 +27,174 @@ namespace {
constexpr int kResizeBorder = 10;
// This should match FramelessView's inside resize band.
constexpr int kResizeInsideBoundsSize = 5;
// These should match Chromium's restored frame edge thickness.
constexpr gfx::Insets kDefaultCustomFrameBorder = gfx::Insets::TLBR(2, 1, 1, 1);
bool CheckClientFrameShadowSupport(NativeWindowViews* window) {
auto* tree_host = static_cast<ElectronDesktopWindowTreeHostLinux*>(
ElectronDesktopWindowTreeHostLinux::GetHostForWidget(
window->GetAcceleratedWidget()));
return tree_host && tree_host->SupportsClientFrameShadow();
}
} // namespace
// static
std::unique_ptr<LinuxFrameLayout> LinuxFrameLayout::Create(
NativeWindowViews* window,
bool wants_shadow) {
bool wants_shadow,
CSDStyle csd_style) {
if (x11_util::IsX11() || window->IsTranslucent() || !wants_shadow) {
return std::make_unique<LinuxUndecoratedFrameLayout>(window);
return std::make_unique<LinuxFrameLayout>(window);
} else if (csd_style == CSDStyle::kCustom) {
return std::make_unique<LinuxCSDCustomFrameLayout>(window);
} else {
return std::make_unique<LinuxCSDFrameLayout>(window);
return std::make_unique<LinuxCSDNativeFrameLayout>(window);
}
}
LinuxCSDFrameLayout::LinuxCSDFrameLayout(NativeWindowViews* window)
: window_(window) {
host_supports_client_frame_shadow_ = SupportsClientFrameShadow();
gfx::Insets LinuxFrameLayout::GetResizeBorderInsets() const {
gfx::Insets insets = RestoredFrameBorderInsets();
return insets.IsEmpty() ? GetInputInsets() : insets;
}
bool LinuxCSDFrameLayout::tiled() const {
return tiled_;
}
void LinuxCSDFrameLayout::set_tiled(bool tiled) {
tiled_ = tiled;
}
gfx::Insets LinuxCSDFrameLayout::RestoredFrameBorderInsets() const {
gfx::Insets insets = GetFrameProvider()->GetFrameThicknessDip();
const gfx::Insets input = GetInputInsets();
auto expand_if_visible = [](int side_thickness, int min_band) {
return side_thickness > 0 ? std::max(side_thickness, min_band) : 0;
};
gfx::Insets merged;
merged.set_top(expand_if_visible(insets.top(), input.top()));
merged.set_left(expand_if_visible(insets.left(), input.left()));
merged.set_bottom(expand_if_visible(insets.bottom(), input.bottom()));
merged.set_right(expand_if_visible(insets.right(), input.right()));
return base::i18n::IsRTL() ? gfx::Insets::TLBR(merged.top(), merged.right(),
merged.bottom(), merged.left())
: merged;
}
gfx::Insets LinuxCSDFrameLayout::GetInputInsets() const {
bool showing_shadow = host_supports_client_frame_shadow_ &&
!window_->IsMaximized() && !window_->IsFullscreen();
return gfx::Insets(showing_shadow ? kResizeBorder : 0);
}
bool LinuxCSDFrameLayout::SupportsClientFrameShadow() const {
auto* tree_host = static_cast<ElectronDesktopWindowTreeHostLinux*>(
ElectronDesktopWindowTreeHostLinux::GetHostForWidget(
window_->GetAcceleratedWidget()));
return tree_host->SupportsClientFrameShadow();
}
void LinuxCSDFrameLayout::PaintWindowFrame(gfx::Canvas* canvas,
gfx::Rect local_bounds,
gfx::Rect titlebar_bounds,
bool active) {
GetFrameProvider()->PaintWindowFrame(
canvas, local_bounds, titlebar_bounds.bottom(), active, GetInputInsets());
}
gfx::Rect LinuxCSDFrameLayout::GetWindowContentBounds() const {
gfx::Rect content_bounds = window_->widget()->GetWindowBoundsInScreen();
content_bounds.Inset(RestoredFrameBorderInsets());
return content_bounds;
}
SkRRect LinuxCSDFrameLayout::GetRoundedWindowContentBounds() const {
SkRect rect = gfx::RectToSkRect(GetWindowContentBounds());
SkRRect LinuxFrameLayout::GetRoundedWindowBounds() const {
SkRect rect = gfx::RectToSkRect(GetWindowBounds());
SkRRect rrect;
if (!window_->IsMaximized()) {
float radius = GetFrameProvider()->GetTopCornerRadiusDip();
float radius = GetTopCornerRadiusDip();
if (radius > 0) {
SkPoint round_point{radius, radius};
SkPoint radii[] = {round_point, round_point, {}, {}};
rrect.setRectRadii(rect, radii);
} else {
rrect.setRect(rect);
}
return rrect;
}
int LinuxCSDFrameLayout::GetTranslucentTopAreaHeight() const {
// Base implementation is suitable for X11/views without shadows
LinuxFrameLayout::LinuxFrameLayout(NativeWindowViews* window)
: window_(window) {
host_supports_client_frame_shadow_ = false;
}
LinuxFrameLayout::~LinuxFrameLayout() = default;
gfx::Insets LinuxFrameLayout::RestoredFrameBorderInsets() const {
return gfx::Insets();
}
gfx::Insets LinuxFrameLayout::GetInputInsets() const {
return gfx::Insets(kResizeInsideBoundsSize);
}
bool LinuxFrameLayout::IsShowingShadow() const {
return host_supports_client_frame_shadow_ && !window_->IsMaximized() &&
!window_->IsFullscreen();
}
bool LinuxFrameLayout::SupportsClientFrameShadow() const {
return host_supports_client_frame_shadow_;
}
bool LinuxFrameLayout::tiled() const {
return tiled_;
}
void LinuxFrameLayout::set_tiled(bool tiled) {
tiled_ = tiled;
}
gfx::Rect LinuxFrameLayout::GetWindowBounds() const {
gfx::Rect bounds = window_->widget()->GetWindowBoundsInScreen();
bounds.Inset(RestoredFrameBorderInsets());
return bounds;
}
float LinuxFrameLayout::GetTopCornerRadiusDip() const {
return 0;
}
ui::WindowFrameProvider* LinuxCSDFrameLayout::GetFrameProvider() const {
int LinuxFrameLayout::GetTranslucentTopAreaHeight() const {
return 0;
}
gfx::Insets LinuxFrameLayout::NormalizeBorderInsets(
const gfx::Insets& frame_insets,
const gfx::Insets& input_insets) const {
auto expand_if_visible = [](int side_thickness, int min_band) {
return side_thickness > 0 ? std::max(side_thickness, min_band) : 0;
};
// Ensure hit testing for resize targets works
// even if borders/shadows are absent on some edges.
gfx::Insets merged;
merged.set_top(expand_if_visible(frame_insets.top(), input_insets.top()));
merged.set_left(expand_if_visible(frame_insets.left(), input_insets.left()));
merged.set_bottom(
expand_if_visible(frame_insets.bottom(), input_insets.bottom()));
merged.set_right(
expand_if_visible(frame_insets.right(), input_insets.right()));
return base::i18n::IsRTL() ? gfx::Insets::TLBR(merged.top(), merged.right(),
merged.bottom(), merged.left())
: merged;
}
// Used for a native-like frame with a FrameProvider
LinuxCSDNativeFrameLayout::LinuxCSDNativeFrameLayout(NativeWindowViews* window)
: LinuxFrameLayout(window) {
host_supports_client_frame_shadow_ = CheckClientFrameShadowSupport(window);
}
LinuxCSDNativeFrameLayout::~LinuxCSDNativeFrameLayout() = default;
gfx::Insets LinuxCSDNativeFrameLayout::RestoredFrameBorderInsets() const {
const gfx::Insets input_insets = GetInputInsets();
const gfx::Insets frame_insets = GetFrameProvider()->GetFrameThicknessDip();
return NormalizeBorderInsets(frame_insets, input_insets);
}
gfx::Insets LinuxCSDNativeFrameLayout::GetInputInsets() const {
return gfx::Insets(IsShowingShadow() ? kResizeBorder : 0);
}
float LinuxCSDNativeFrameLayout::GetTopCornerRadiusDip() const {
return window_->IsMaximized() ? 0
: GetFrameProvider()->GetTopCornerRadiusDip();
}
ui::WindowFrameProvider* LinuxCSDNativeFrameLayout::GetFrameProvider() const {
return ui::LinuxUiTheme::GetForProfile(nullptr)->GetWindowFrameProvider(
!host_supports_client_frame_shadow_, tiled(), window_->IsMaximized());
}
LinuxUndecoratedFrameLayout::LinuxUndecoratedFrameLayout(
NativeWindowViews* window)
: window_(window) {}
gfx::Insets LinuxUndecoratedFrameLayout::RestoredFrameBorderInsets() const {
return gfx::Insets();
// Used for Chromium-like custom CSD
LinuxCSDCustomFrameLayout::LinuxCSDCustomFrameLayout(NativeWindowViews* window)
: LinuxFrameLayout(window) {
host_supports_client_frame_shadow_ = CheckClientFrameShadowSupport(window);
}
gfx::Insets LinuxUndecoratedFrameLayout::GetInputInsets() const {
return gfx::Insets(kResizeInsideBoundsSize);
LinuxCSDCustomFrameLayout::~LinuxCSDCustomFrameLayout() = default;
gfx::Insets LinuxCSDCustomFrameLayout::RestoredFrameBorderInsets() const {
const gfx::Insets input_insets = GetInputInsets();
const bool showing_shadow = IsShowingShadow();
const auto shadow_values = (showing_shadow && !tiled())
? GetFrameShadowValuesLinux(/*active=*/true)
: gfx::ShadowValues();
const gfx::Insets frame_insets = GetRestoredFrameBorderInsetsLinux(
showing_shadow, kDefaultCustomFrameBorder, shadow_values, input_insets);
return NormalizeBorderInsets(frame_insets, input_insets);
}
bool LinuxUndecoratedFrameLayout::SupportsClientFrameShadow() const {
return false;
gfx::Insets LinuxCSDCustomFrameLayout::GetInputInsets() const {
return gfx::Insets(IsShowingShadow() ? kResizeBorder : 0);
}
bool LinuxUndecoratedFrameLayout::tiled() const {
return tiled_;
}
void LinuxUndecoratedFrameLayout::set_tiled(bool tiled) {
tiled_ = tiled;
}
void LinuxUndecoratedFrameLayout::PaintWindowFrame(gfx::Canvas* canvas,
gfx::Rect local_bounds,
gfx::Rect titlebar_bounds,
bool active) {
// No-op
}
gfx::Rect LinuxUndecoratedFrameLayout::GetWindowContentBounds() const {
// With no transparent insets, widget bounds and logical bounds match.
return window_->widget()->GetWindowBoundsInScreen();
}
SkRRect LinuxUndecoratedFrameLayout::GetRoundedWindowContentBounds() const {
SkRRect rrect;
rrect.setRect(gfx::RectToSkRect(GetWindowContentBounds()));
return rrect;
}
int LinuxUndecoratedFrameLayout::GetTranslucentTopAreaHeight() const {
return 0;
}
ui::WindowFrameProvider* LinuxUndecoratedFrameLayout::GetFrameProvider() const {
return nullptr;
gfx::ShadowValues GetFrameShadowValuesLinux(bool active) {
const int elevation = views::LayoutProvider::Get()->GetShadowElevationMetric(
active ? views::Emphasis::kMaximum : views::Emphasis::kMedium);
return gfx::ShadowValue::MakeMdShadowValues(elevation);
}
} // namespace electron

View File

@@ -8,110 +8,96 @@
#include <memory>
#include "base/i18n/rtl.h"
#include "shell/browser/linux/x11_util.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
#include "base/memory/raw_ptr.h"
#include "third_party/skia/include/core/SkRRect.h"
#include "ui/base/ozone_buildflags.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/linux/linux_ui.h"
#include "ui/gfx/shadow_value.h"
#include "ui/linux/window_frame_provider.h"
namespace gfx {
class Insets;
class Rect;
} // namespace gfx
namespace electron {
class NativeWindowViews;
// Shared helper for CSD layout and frame painting on Linux (shadows, resize
// regions, titlebars, etc.). Also helps views determine insets and perform
// bounds conversions between widget and logical coordinates.
// Shared helper for CSD layout on Linux (shadows, resize regions, titlebars,
// etc.). Also helps views determine insets and perform bounds conversions
// between widget and logical coordinates.
//
// The base class is concrete and suitable as-is for the undecorated case (X11,
// translucent windows, or windows without shadows). CSD subclasses override
// the methods that differ.
class LinuxFrameLayout {
public:
virtual ~LinuxFrameLayout() = default;
enum class CSDStyle {
kNativeFrame,
kCustom,
};
explicit LinuxFrameLayout(NativeWindowViews* window);
virtual ~LinuxFrameLayout();
static std::unique_ptr<LinuxFrameLayout> Create(NativeWindowViews* window,
bool wants_shadow);
bool wants_shadow,
CSDStyle csd_style);
// Insets from the transparent widget border to the opaque part of the window
virtual gfx::Insets RestoredFrameBorderInsets() const = 0;
// Insets for parts of the surface that should be counted for user input
virtual gfx::Insets GetInputInsets() const = 0;
// Insets from the transparent widget border to the opaque part of the window.
virtual gfx::Insets RestoredFrameBorderInsets() const;
// Insets for parts of the surface that should be counted for user input.
virtual gfx::Insets GetInputInsets() const;
// Insets to use for non-client resize hit-testing.
gfx::Insets GetResizeBorderInsets() const;
virtual bool SupportsClientFrameShadow() const = 0;
bool IsShowingShadow() const;
bool SupportsClientFrameShadow() const;
virtual bool tiled() const = 0;
virtual void set_tiled(bool tiled) = 0;
bool tiled() const;
void set_tiled(bool tiled);
virtual void PaintWindowFrame(gfx::Canvas* canvas,
gfx::Rect local_bounds,
gfx::Rect titlebar_bounds,
bool active) = 0;
// The logical bounds of the window interior.
gfx::Rect GetWindowBounds() const;
// The logical window bounds as a rounded rect with corner radii applied.
SkRRect GetRoundedWindowBounds() const;
// The corner radius of the top corners of the window, in DIPs.
virtual float GetTopCornerRadiusDip() const;
// The logical bounds of the window
virtual gfx::Rect GetWindowContentBounds() const = 0;
// The logical bounds as a rounded rect with corner radii applied
virtual SkRRect GetRoundedWindowContentBounds() const = 0;
int GetTranslucentTopAreaHeight() const;
virtual int GetTranslucentTopAreaHeight() const = 0;
protected:
gfx::Insets NormalizeBorderInsets(const gfx::Insets& frame_insets,
const gfx::Insets& input_insets) const;
virtual ui::WindowFrameProvider* GetFrameProvider() const = 0;
};
// Client-side decoration (CSD) Linux frame layout implementation.
class LinuxCSDFrameLayout : public LinuxFrameLayout {
public:
explicit LinuxCSDFrameLayout(NativeWindowViews* window);
~LinuxCSDFrameLayout() override = default;
gfx::Insets RestoredFrameBorderInsets() const override;
gfx::Insets GetInputInsets() const override;
bool SupportsClientFrameShadow() const override;
bool tiled() const override;
void set_tiled(bool tiled) override;
void PaintWindowFrame(gfx::Canvas* canvas,
gfx::Rect local_bounds,
gfx::Rect titlebar_bounds,
bool active) override;
gfx::Rect GetWindowContentBounds() const override;
SkRRect GetRoundedWindowContentBounds() const override;
int GetTranslucentTopAreaHeight() const override;
ui::WindowFrameProvider* GetFrameProvider() const override;
private:
raw_ptr<NativeWindowViews> window_;
bool tiled_ = false;
bool host_supports_client_frame_shadow_ = false;
};
// No-decoration Linux frame layout implementation.
//
// Intended for cases where we do not allocate a transparent inset area around
// the window (e.g. X11 / server-side decorations, or when insets are disabled).
// All inset math returns 0 and frame painting is skipped.
class LinuxUndecoratedFrameLayout : public LinuxFrameLayout {
// CSD strategy that uses the GTK window frame provider for metrics.
class LinuxCSDNativeFrameLayout : public LinuxFrameLayout {
public:
explicit LinuxUndecoratedFrameLayout(NativeWindowViews* window);
~LinuxUndecoratedFrameLayout() override = default;
explicit LinuxCSDNativeFrameLayout(NativeWindowViews* window);
~LinuxCSDNativeFrameLayout() override;
gfx::Insets RestoredFrameBorderInsets() const override;
gfx::Insets GetInputInsets() const override;
bool SupportsClientFrameShadow() const override;
bool tiled() const override;
void set_tiled(bool tiled) override;
void PaintWindowFrame(gfx::Canvas* canvas,
gfx::Rect local_bounds,
gfx::Rect titlebar_bounds,
bool active) override;
gfx::Rect GetWindowContentBounds() const override;
SkRRect GetRoundedWindowContentBounds() const override;
int GetTranslucentTopAreaHeight() const override;
ui::WindowFrameProvider* GetFrameProvider() const override;
private:
raw_ptr<NativeWindowViews> window_;
bool tiled_ = false;
float GetTopCornerRadiusDip() const override;
ui::WindowFrameProvider* GetFrameProvider() const;
};
// CSD strategy that uses custom metrics, similar to those used in Chromium.
class LinuxCSDCustomFrameLayout : public LinuxFrameLayout {
public:
explicit LinuxCSDCustomFrameLayout(NativeWindowViews* window);
~LinuxCSDCustomFrameLayout() override;
gfx::Insets RestoredFrameBorderInsets() const override;
gfx::Insets GetInputInsets() const override;
};
gfx::ShadowValues GetFrameShadowValuesLinux(bool active);
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_UI_VIEWS_LINUX_FRAME_LAYOUT_H_

View File

@@ -5,22 +5,24 @@
#include "shell/browser/ui/views/opaque_frame_view.h"
#include "base/containers/adapters.h"
#include "base/i18n/rtl.h"
#include "chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.h" // nogncheck
#include "chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h" // nogncheck
#include "chrome/grit/generated_resources.h"
#include "components/strings/grit/components_strings.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/views/caption_button_placeholder_container.h"
#include "third_party/skia/include/core/SkRRect.h"
#include "ui/base/hit_test.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/font_list.h"
#include "ui/linux/linux_ui.h"
#include "ui/gfx/geometry/insets_f.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/window/frame_background.h"
#include "ui/views/window/frame_caption_button.h"
#include "ui/views/window/vector_icons/vector_icons.h"
@@ -55,12 +57,14 @@ const int kCaptionButtonBottomPadding = 3;
// The content edge images have a shadow built into them.
const int OpaqueFrameView::kContentEdgeShadowThickness = 2;
OpaqueFrameView::OpaqueFrameView() = default;
OpaqueFrameView::OpaqueFrameView()
: frame_background_(std::make_unique<views::FrameBackground>()) {}
OpaqueFrameView::~OpaqueFrameView() = default;
void OpaqueFrameView::Init(NativeWindowViews* window, views::Widget* frame) {
FramelessView::Init(window, frame);
linux_frame_layout_ = LinuxFrameLayout::Create(window, window->HasShadow());
linux_frame_layout_ = LinuxFrameLayout::Create(
window, window->HasShadow(), LinuxFrameLayout::CSDStyle::kCustom);
// Unretained() is safe because the subscription is saved into an instance
// member and thus will be cancelled upon the instance's destruction.
@@ -98,9 +102,8 @@ void OpaqueFrameView::Init(NativeWindowViews* window, views::Widget* frame) {
}
int OpaqueFrameView::ResizingBorderHitTest(const gfx::Point& point) {
auto insets = RestoredFrameBorderInsets();
return ResizingBorderHitTestImpl(
point, insets.IsEmpty() ? linux_frame_layout_->GetInputInsets() : insets);
point, linux_frame_layout_->GetResizeBorderInsets());
}
void OpaqueFrameView::InvalidateCaptionButtons() {
@@ -200,14 +203,31 @@ void OpaqueFrameView::OnPaint(gfx::Canvas* canvas) {
if (frame()->IsFullscreen())
return;
// Titlebar height must be at least the frame border insets to avoid
// a negative height calculation in the GTK frame provider. We add 1 to
// ensure it's always positive even when insets are 0.
int top_area_height = RestoredFrameBorderInsets().top() + 1;
const bool active = ShouldPaintAsActive();
const gfx::Insets border = RestoredFrameBorderInsets();
const bool showing_shadow = linux_frame_layout_->IsShowingShadow();
gfx::RectF bounds_dip(GetLocalBounds());
if (showing_shadow) {
bounds_dip.Inset(gfx::InsetsF(border));
}
linux_frame_layout_->PaintWindowFrame(
canvas, GetLocalBounds(), gfx::Rect(0, 0, width(), top_area_height),
ShouldPaintAsActive());
// TODO: support roundedCorners.
float radius_dip = 0;
SkVector radii[4]{{radius_dip, radius_dip}, {radius_dip, radius_dip}, {}, {}};
SkRRect clip;
clip.setRectRadii(gfx::RectFToSkRect(bounds_dip), radii);
frame_background_->set_frame_color(GetFrameColor());
frame_background_->set_use_custom_frame(true);
frame_background_->set_is_active(active);
frame_background_->set_top_area_height(GetTopAreaHeight());
const bool draw_shadow = showing_shadow && !linux_frame_layout_->tiled();
auto shadow_values =
draw_shadow ? GetFrameShadowValuesLinux(active) : gfx::ShadowValues();
::PaintRestoredFrameBorderLinux(*canvas, *this, frame_background_.get(), clip,
showing_shadow, active, border, shadow_values,
linux_frame_layout_->tiled());
if (!window()->IsWindowControlsOverlayEnabled())
return;

View File

@@ -20,6 +20,10 @@
class CaptionButtonPlaceholderContainer;
namespace views {
class FrameBackground;
}
namespace electron {
class NativeWindowViews;
@@ -166,6 +170,7 @@ class OpaqueFrameView : public FramelessView {
bool is_leading_button) const;
std::unique_ptr<LinuxFrameLayout> linux_frame_layout_;
std::unique_ptr<views::FrameBackground> frame_background_;
// Window controls.
raw_ptr<views::Button> minimize_button_;

View File

@@ -266,7 +266,11 @@ gfx::Image Clipboard::ReadImage(gin::Arguments* const args) {
[](std::optional<gfx::Image>* image, base::RepeatingClosure cb,
const std::vector<uint8_t>& result) {
SkBitmap bitmap = gfx::PNGCodec::Decode(result);
image->emplace(gfx::Image::CreateFrom1xBitmap(bitmap));
if (bitmap.isNull()) {
image->emplace();
} else {
image->emplace(gfx::Image::CreateFrom1xBitmap(bitmap));
}
std::move(cb).Run();
},
&image, std::move(callback)));

View File

@@ -153,9 +153,12 @@ v8::Local<v8::Value> Converter<electron::OffscreenSharedTextureValue>::ToV8(
root.Set("textureInfo", ConvertToV8(isolate, dict));
auto root_local = ConvertToV8(isolate, root);
// Create a persistent reference of the object, so that we can check the
// monitor again when GC collects this object.
auto* tex_persistent = monitor->CreatePersistent(isolate, root_local);
// Create a weak persistent that tracks the release function rather than the
// texture object. The release function holds a raw pointer to |monitor| via
// its v8::External data, so |monitor| must outlive it. Since the texture
// keeps |release| alive via its property, this also covers the case where
// the texture itself is leaked without calling release().
auto* tex_persistent = monitor->CreatePersistent(isolate, releaser);
tex_persistent->SetWeak(
monitor,
[](const v8::WeakCallbackInfo<OffscreenReleaseHolderMonitor>& data) {

View File

@@ -6902,6 +6902,54 @@ describe('BrowserWindow module', () => {
expect(w.webContents.frameRate).to.equal(30);
});
});
describe('shared texture', () => {
const v8Util = process._linkedBinding('electron_common_v8_util');
it('does not crash when release() is called after the texture is garbage collected', async () => {
const sw = new BrowserWindow({
width: 100,
height: 100,
show: false,
webPreferences: {
backgroundThrottling: false,
offscreen: {
useSharedTexture: true
}
}
});
const paint = once(sw.webContents, 'paint') as Promise<[any, Electron.Rectangle, Electron.NativeImage]>;
sw.loadFile(path.join(fixtures, 'api', 'offscreen-rendering.html'));
const [event] = await paint;
sw.webContents.stopPainting();
if (!event.texture) {
// GPU shared texture not available on this host; skip.
sw.destroy();
return;
}
// Keep only the release closure and drop the owning texture object.
const staleRelease = event.texture.release;
const weakTexture = new WeakRef(event.texture);
event.texture = undefined;
// Force GC until the texture object is collected.
let collected = false;
for (let i = 0; i < 30 && !collected; ++i) {
await setTimeout();
v8Util.requestGarbageCollectionForTesting();
collected = weakTexture.deref() === undefined;
}
expect(collected).to.be.true('texture should be garbage collected');
// This should return safely and not crash the main process.
expect(() => staleRelease()).to.not.throw();
sw.destroy();
});
});
});
describe('"transparent" option', () => {

View File

@@ -6,6 +6,7 @@ import { once } from 'node:events';
import * as path from 'node:path';
import { setTimeout } from 'node:timers/promises';
import { ifdescribe } from './lib/spec-helpers';
import { closeAllWindows } from './lib/window-helpers';
describe('nativeTheme module', () => {
@@ -119,4 +120,10 @@ describe('nativeTheme module', () => {
expect(nativeTheme.prefersReducedTransparency).to.be.a('boolean');
});
});
ifdescribe(process.platform === 'darwin')('nativeTheme.shouldDifferentiateWithoutColor', () => {
it('returns a boolean', () => {
expect(nativeTheme.shouldDifferentiateWithoutColor).to.be.a('boolean');
});
});
});

View File

@@ -129,6 +129,22 @@ describe('utilityProcess module', () => {
expect(code).to.equal(exitCode);
});
ifit(process.platform === 'win32')('emits correct exit code when high bit is set on Windows', async () => {
// NTSTATUS code with high bit set should not be mangled by sign extension.
const exitCode = 0xC0000005;
const child = utilityProcess.fork(path.join(fixturesPath, 'custom-exit.js'), [`--exitCode=${exitCode}`]);
const [code] = await once(child, 'exit');
expect(code).to.equal(exitCode);
});
ifit(process.platform !== 'win32')('emits correct exit code when child process crashes on posix', async () => {
// Crash exit codes should not be sign-extended to large 64-bit values.
const child = utilityProcess.fork(path.join(fixturesPath, 'crash.js'));
const [code] = await once(child, 'exit');
expect(code).to.not.equal(0);
expect(code).to.be.lessThanOrEqual(0xFFFFFFFF);
});
it('does not run JS after process.exit is called', async () => {
const file = path.join(os.tmpdir(), `no-js-after-exit-log-${Math.random()}`);
const child = utilityProcess.fork(path.join(fixturesPath, 'no-js-after-exit.js'), [`--testPath=${file}`]);

View File

@@ -0,0 +1,7 @@
<html>
<body>
<script>
window.open('javascript:alert()');
</script>
</body>
</html>

View File

@@ -0,0 +1,22 @@
const { app, BrowserWindow } = require('electron');
process.on('uncaughtException', (err) => {
console.error(err);
process.exit(1);
});
process.on('unhandledRejection', (reason) => {
console.error(reason);
process.exit(1);
});
app.on('browser-window-created', (_, window) => {
window.webContents.once('did-frame-navigate', () => {
process.exit(0);
});
});
app.whenReady().then(() => {
const win = new BrowserWindow({ show: false });
win.loadFile('index.html');
});

View File

@@ -186,6 +186,39 @@ describe('webContents.setWindowOpenHandler', () => {
await once(browserWindow.webContents, 'did-create-window');
});
it('reuses an existing window when window.open is called with the same frame name', async () => {
let handlerCallCount = 0;
browserWindow.webContents.setWindowOpenHandler(() => {
handlerCallCount++;
return { action: 'allow' };
});
const didCreateWindow = once(browserWindow.webContents, 'did-create-window') as Promise<[BrowserWindow, Electron.DidCreateWindowDetails]>;
await browserWindow.webContents.executeJavaScript("window.open('about:blank?one', 'named-target', 'show=no') && true");
const [childWindow] = await didCreateWindow;
expect(handlerCallCount).to.equal(1);
expect(childWindow.webContents.getURL()).to.equal('about:blank?one');
browserWindow.webContents.on('did-create-window', () => {
assert.fail('did-create-window should not fire when reusing a named window');
});
const didNavigate = once(childWindow.webContents, 'did-navigate');
const sameWindow = await browserWindow.webContents.executeJavaScript(`
(() => {
const first = window.open('about:blank?one', 'named-target', 'show=no');
const second = window.open('about:blank?two', 'named-target', 'show=no');
return first === second;
})()
`);
await didNavigate;
expect(sameWindow).to.be.true('window.open with matching frame name should return the same window proxy');
expect(handlerCallCount).to.equal(1, 'setWindowOpenHandler should not be called when Blink resolves the named target');
expect(childWindow.webContents.getURL()).to.equal('about:blank?two');
expect(BrowserWindow.getAllWindows()).to.have.lengthOf(2);
});
it('can change webPreferences of child windows', async () => {
browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { defaultFontSize: 30 } } }));