mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
14 Commits
cherry-pic
...
41-x-y
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93387921c0 | ||
|
|
0bb55bb6f2 | ||
|
|
e9e28f4f8f | ||
|
|
f2cbab1115 | ||
|
|
9042667690 | ||
|
|
1ad20e5ba4 | ||
|
|
a26aee5a6d | ||
|
|
7394591138 | ||
|
|
d37b4f5d9f | ||
|
|
6f1d53ae8f | ||
|
|
fb150b2f17 | ||
|
|
c219f2c990 | ||
|
|
3fa5280fde | ||
|
|
45ad6b3525 |
12
.github/actions/build-electron/action.yml
vendored
12
.github/actions/build-electron/action.yml
vendored
@@ -205,7 +205,17 @@ runs:
|
||||
if: ${{ inputs.is-release == 'true' }}
|
||||
run: |
|
||||
cd src
|
||||
gn gen out/ffmpeg --args="import(\"//electron/build/args/ffmpeg.gn\") use_remoteexec=true use_siso=true $GN_EXTRA_ARGS"
|
||||
# Reuse the hermetic mac_sdk_path that `e build` wrote for out/Default so
|
||||
# out/ffmpeg builds against the same SDK instead of the runner's system Xcode.
|
||||
# The path has to live under root_build_dir, so copy the symlink tree and
|
||||
# rewrite Default -> ffmpeg.
|
||||
MAC_SDK_ARG=""
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
mkdir -p out/ffmpeg
|
||||
cp -a out/Default/xcode_links out/ffmpeg/
|
||||
MAC_SDK_ARG=$(sed -n 's|^\(mac_sdk_path = "//out/\)Default/|\1ffmpeg/|p' out/Default/args.gn)
|
||||
fi
|
||||
gn gen out/ffmpeg --args="import(\"//electron/build/args/ffmpeg.gn\") use_remoteexec=true use_siso=true $MAC_SDK_ARG $GN_EXTRA_ARGS"
|
||||
e build --target electron:electron_ffmpeg_zip -C ../../out/ffmpeg
|
||||
- name: Remove Clang problem matcher
|
||||
shell: bash
|
||||
|
||||
20
.github/actions/checkout/action.yml
vendored
20
.github/actions/checkout/action.yml
vendored
@@ -191,19 +191,31 @@ runs:
|
||||
# only permits .tar/.tgz so we keep the extension and decode on restore.
|
||||
tar -cf - src | zstd -T0 --long=30 -f -o $CACHE_FILE
|
||||
echo "Compressed src to $(du -sh $CACHE_FILE | cut -f1 -d' ')"
|
||||
cp ./$CACHE_FILE $CACHE_DRIVE/
|
||||
- name: Persist Src Cache
|
||||
if: ${{ steps.check-cache.outputs.cache_exists == 'false' && inputs.use-cache == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
final_cache_path=$CACHE_DRIVE/$CACHE_FILE
|
||||
# Upload to a run-unique temp name first so concurrent readers never
|
||||
# observe a partially-written file, and an interrupted copy can't leave
|
||||
# a truncated file at the final path. Orphaned temp files get swept by
|
||||
# the clean-orphaned-cache-uploads workflow.
|
||||
tmp_cache_path=$final_cache_path.upload-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}
|
||||
echo "Uploading to temp path: $tmp_cache_path"
|
||||
cp ./$CACHE_FILE $tmp_cache_path
|
||||
|
||||
echo "Using cache key: $DEPSHASH"
|
||||
echo "Checking path: $final_cache_path"
|
||||
if [ -f "$final_cache_path" ]; then
|
||||
echo "Cache already persisted at $final_cache_path by a concurrent run; discarding ours"
|
||||
rm -f $tmp_cache_path
|
||||
else
|
||||
mv -f $tmp_cache_path $final_cache_path
|
||||
echo "Cache key persisted in $final_cache_path"
|
||||
fi
|
||||
|
||||
if [ ! -f "$final_cache_path" ]; then
|
||||
echo "Cache key not found"
|
||||
exit 1
|
||||
else
|
||||
echo "Cache key persisted in $final_cache_path"
|
||||
fi
|
||||
- name: Wait for active SSH sessions
|
||||
shell: bash
|
||||
|
||||
32
.github/workflows/clean-orphaned-cache-uploads.yml
vendored
Normal file
32
.github/workflows/clean-orphaned-cache-uploads.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Clean Orphaned Cache Uploads
|
||||
|
||||
# Description:
|
||||
# Sweeps orphaned in-flight upload temp files left on the src-cache volumes
|
||||
# by checkout/action.yml when its cp-to-share step dies before the rename.
|
||||
# A successful upload finishes in minutes, so anything older than 4h is dead.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 */4 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
clean-orphaned-uploads:
|
||||
if: github.repository == 'electron/electron'
|
||||
runs-on: electron-arc-centralus-linux-amd64-32core
|
||||
permissions:
|
||||
contents: read
|
||||
container:
|
||||
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
|
||||
options: --user root
|
||||
volumes:
|
||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||
- /mnt/win-cache:/mnt/win-cache
|
||||
steps:
|
||||
- name: Remove Orphaned Upload Temp Files
|
||||
shell: bash
|
||||
run: |
|
||||
find /mnt/cross-instance-cache -maxdepth 1 -type f -name '*.tar.upload-*' -mmin +240 -print -delete
|
||||
find /mnt/win-cache -maxdepth 1 -type f -name '*.tar.upload-*' -mmin +240 -print -delete
|
||||
4
DEPS
4
DEPS
@@ -2,9 +2,9 @@ gclient_gn_args_from = 'src'
|
||||
|
||||
vars = {
|
||||
'chromium_version':
|
||||
'146.0.7680.166',
|
||||
'146.0.7680.188',
|
||||
'node_version':
|
||||
'v24.14.0',
|
||||
'v24.14.1',
|
||||
'nan_version':
|
||||
'675cefebca42410733da8a454c8d9391fcebfbc2',
|
||||
'squirrel.mac_version':
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { shell } from 'electron/common';
|
||||
import { app, dialog, BrowserWindow, ipcMain } from 'electron/main';
|
||||
import { app, dialog, BrowserWindow, ipcMain, Menu } from 'electron/main';
|
||||
|
||||
import * as path from 'node:path';
|
||||
import * as url from 'node:url';
|
||||
@@ -11,6 +11,53 @@ app.on('window-all-closed', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
const isMac = process.platform === 'darwin';
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const helpMenu: Electron.MenuItemConstructorOptions = {
|
||||
role: 'help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Learn More',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://electronjs.org');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Documentation',
|
||||
click: async () => {
|
||||
const version = process.versions.electron;
|
||||
await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Community Discussions',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://discord.gg/electronjs');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Search Issues',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://github.com/electron/electron/issues');
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' };
|
||||
const template: Electron.MenuItemConstructorOptions[] = [
|
||||
...(isMac ? [macAppMenu] : []),
|
||||
{ role: 'fileMenu' },
|
||||
{ role: 'editMenu' },
|
||||
{ role: 'viewMenu' },
|
||||
{ role: 'windowMenu' },
|
||||
helpMenu
|
||||
];
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
|
||||
});
|
||||
|
||||
function decorateURL (url: string) {
|
||||
// safely add `?utm_source=default_app
|
||||
const parsedUrl = new URL(url);
|
||||
|
||||
@@ -44,8 +44,8 @@ See [`Menu`](menu.md) for examples.
|
||||
menu items.
|
||||
* `registerAccelerator` boolean (optional) _Linux_ _Windows_ - If false, the accelerator won't be registered
|
||||
with the system, but it will still be displayed. Defaults to true.
|
||||
* `sharingItem` SharingItem (optional) _macOS_ - The item to share when the `role` is `shareMenu`.
|
||||
* `submenu` (MenuItemConstructorOptions[] | [Menu](menu.md)) (optional) - Should be specified
|
||||
* `sharingItem` [SharingItem](structures/sharing-item.md) (optional) _macOS_ - The item to share when the `role` is `shareMenu`.
|
||||
* `submenu` ([MenuItemConstructorOptions](#new-menuitemoptions)[] | [Menu](menu.md)) (optional) - Should be specified
|
||||
for `submenu` type menu items. If `submenu` is specified, the `type: 'submenu'` can be omitted.
|
||||
If the value is not a [`Menu`](menu.md) then it will be automatically converted to one using
|
||||
`Menu.buildFromTemplate`.
|
||||
@@ -89,7 +89,7 @@ A `Function` that is fired when the MenuItem receives a click event.
|
||||
It can be called with `menuItem.click(event, focusedWindow, focusedWebContents)`.
|
||||
|
||||
* `event` [KeyboardEvent](structures/keyboard-event.md)
|
||||
* `focusedWindow` [BaseWindow](browser-window.md)
|
||||
* `focusedWindow` [BaseWindow](base-window.md)
|
||||
* `focusedWebContents` [WebContents](web-contents.md)
|
||||
|
||||
#### `menuItem.submenu`
|
||||
@@ -110,11 +110,11 @@ A `string` (optional) indicating the item's role, if set. Can be `undo`, `redo`,
|
||||
|
||||
#### `menuItem.accelerator`
|
||||
|
||||
An `Accelerator | null` indicating the item's accelerator, if set.
|
||||
An [`Accelerator | null`](../tutorial/keyboard-shortcuts.md#accelerators) indicating the item's accelerator, if set.
|
||||
|
||||
#### `menuItem.userAccelerator` _Readonly_ _macOS_
|
||||
|
||||
An `Accelerator | null` indicating the item's [user-assigned accelerator](https://developer.apple.com/documentation/appkit/nsmenuitem/1514850-userkeyequivalent?language=objc) for the menu item.
|
||||
An [`Accelerator | null`](../tutorial/keyboard-shortcuts.md#accelerators) indicating the item's [user-assigned accelerator](https://developer.apple.com/documentation/appkit/nsmenuitem/1514850-userkeyequivalent?language=objc) for the menu item.
|
||||
|
||||
> [!NOTE]
|
||||
> This property is only initialized after the `MenuItem` has been added to a `Menu`. Either via `Menu.buildFromTemplate` or via `Menu.append()/insert()`. Accessing before initialization will just return `null`.
|
||||
@@ -170,7 +170,7 @@ This property can be dynamically changed.
|
||||
|
||||
#### `menuItem.sharingItem` _macOS_
|
||||
|
||||
A `SharingItem` indicating the item to share when the `role` is `shareMenu`.
|
||||
A [`SharingItem`](structures/sharing-item.md) indicating the item to share when the `role` is `shareMenu`.
|
||||
|
||||
This property can be dynamically changed.
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ this has the additional effect of removing the menu bar from the window.
|
||||
|
||||
> [!NOTE]
|
||||
> The default menu will be created automatically if the app does not set one.
|
||||
> It contains standard items such as `File`, `Edit`, `View`, `Window` and `Help`.
|
||||
> It contains standard items such as `File`, `Edit`, `View`, and `Window`.
|
||||
|
||||
#### `Menu.getApplicationMenu()`
|
||||
|
||||
@@ -70,7 +70,7 @@ for more information on macOS' native actions.
|
||||
|
||||
#### `Menu.buildFromTemplate(template)`
|
||||
|
||||
- `template` (MenuItemConstructorOptions | [MenuItem](menu-item.md))[]
|
||||
- `template` ([MenuItemConstructorOptions](menu-item.md#new-menuitemoptions) | [MenuItem](menu-item.md))[]
|
||||
|
||||
Returns [`Menu`](menu.md)
|
||||
|
||||
@@ -162,7 +162,7 @@ Emitted when a popup is closed either manually or with `menu.closePopup()`.
|
||||
|
||||
#### `menu.items`
|
||||
|
||||
A `MenuItem[]` array containing the menu's items.
|
||||
A [`MenuItem[]`](menu-item.md) array containing the menu's items.
|
||||
|
||||
Each `Menu` consists of multiple [`MenuItem`](menu-item.md) instances and each `MenuItem`
|
||||
can nest a `Menu` into its `submenu` property.
|
||||
|
||||
@@ -79,7 +79,7 @@ $ ../../electron/script/git-import-patches ../../electron/patches/node
|
||||
$ ../../electron/script/git-export-patches -o ../../electron/patches/node
|
||||
```
|
||||
|
||||
Note that `git-import-patches` will mark the commit that was `HEAD` when it was run as `refs/patches/upstream-head`. This lets you keep track of which commits are from Electron patches (those that come after `refs/patches/upstream-head`) and which commits are in upstream (those before `refs/patches/upstream-head`).
|
||||
Note that `git-import-patches` will mark the commit that was `HEAD` when it was run as `refs/patches/upstream-head` (and a checkout-specific `refs/patches/upstream-head-<hash>` so that gclient worktrees sharing a `.git/refs` directory don't clobber each other). This lets you keep track of which commits are from Electron patches (those that come after `refs/patches/upstream-head`) and which commits are in upstream (those before `refs/patches/upstream-head`).
|
||||
|
||||
#### Resolving conflicts
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { shell } from 'electron/common';
|
||||
import { app, Menu } from 'electron/main';
|
||||
import { Menu } from 'electron/main';
|
||||
|
||||
const isMac = process.platform === 'darwin';
|
||||
|
||||
@@ -12,47 +11,13 @@ export const setApplicationMenuWasSet = () => {
|
||||
export const setDefaultApplicationMenu = () => {
|
||||
if (applicationMenuWasSet) return;
|
||||
|
||||
const helpMenu: Electron.MenuItemConstructorOptions = {
|
||||
role: 'help',
|
||||
submenu: app.isPackaged
|
||||
? []
|
||||
: [
|
||||
{
|
||||
label: 'Learn More',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://electronjs.org');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Documentation',
|
||||
click: async () => {
|
||||
const version = process.versions.electron;
|
||||
await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Community Discussions',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://discord.gg/electronjs');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Search Issues',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://github.com/electron/electron/issues');
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' };
|
||||
const template: Electron.MenuItemConstructorOptions[] = [
|
||||
...(isMac ? [macAppMenu] : []),
|
||||
{ role: 'fileMenu' },
|
||||
{ role: 'editMenu' },
|
||||
{ role: 'viewMenu' },
|
||||
{ role: 'windowMenu' },
|
||||
helpMenu
|
||||
{ role: 'windowMenu' }
|
||||
];
|
||||
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
|
||||
@@ -121,7 +121,7 @@ build_disable_thin_lto_mac.patch
|
||||
feat_corner_smoothing_css_rule_and_blink_painting.patch
|
||||
build_add_public_config_simdutf_config.patch
|
||||
fix_multiple_scopedpumpmessagesinprivatemodes_instances.patch
|
||||
revert_code_health_clean_up_stale_macwebcontentsocclusion.patch
|
||||
fix_handle_embedder_windows_shown_after_webcontentsviewcocoa_attach.patch
|
||||
feat_add_signals_when_embedder_cleanup_callbacks_run_for.patch
|
||||
feat_separate_content_settings_callback_for_sync_and_async_clipboard.patch
|
||||
fix_win32_synchronous_spellcheck.patch
|
||||
|
||||
@@ -43,7 +43,7 @@ index 21d5ab99800c0830cc31ec4ebb24e3f05cd904d8..3f8f514519d6e4a0abe3690f5df35de8
|
||||
// When the enterprise policy is not set, use finch/feature flag choice.
|
||||
return base::FeatureList::IsEnabled(chrome_pdf::features::kPdfXfaSupport);
|
||||
diff --git a/chrome/browser/pdf/pdf_extension_util.cc b/chrome/browser/pdf/pdf_extension_util.cc
|
||||
index 83bc44f0c1928b9023efa54bfb57bed69d77484a..9c79f96931a0b2a05d98191ea8eb31a3a01818fc 100644
|
||||
index 24ca200ac662028d45180b21c3d79f2a4b96636e..b35025f7a06cae964858452c8f9e96655e34c47a 100644
|
||||
--- a/chrome/browser/pdf/pdf_extension_util.cc
|
||||
+++ b/chrome/browser/pdf/pdf_extension_util.cc
|
||||
@@ -259,10 +259,13 @@ bool IsPrintingEnabled(content::BrowserContext* context) {
|
||||
|
||||
@@ -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 d5fe1468676285540909ee078d5b88a9bd8e197c..427c77b17d3e130c43a69d79d330bf5c4759f4cc 100644
|
||||
index 80b0abeb7f3c18019aca2f431240a84c6dd749d7..0ebf7f1be37e0b0fccb594e83ecaacc3d4c6f510 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 @@
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Samuel Attard <sattard@anthropic.com>
|
||||
Date: Mon, 30 Mar 2026 03:05:40 -0700
|
||||
Subject: fix: handle embedder windows shown after WebContentsViewCocoa attach
|
||||
|
||||
The occlusion checker assumes windows are shown before or at the same
|
||||
time as a WebContentsViewCocoa is attached. Embedders like Electron
|
||||
support creating a window hidden, attaching web contents, and showing
|
||||
later. This breaks three assumptions:
|
||||
|
||||
1. updateWebContentsVisibility only checks -[NSWindow isOccluded], which
|
||||
defaults to NO for never-shown windows, so viewDidMoveToWindow
|
||||
incorrectly reports kVisible for hidden windows.
|
||||
|
||||
2. windowChangedOcclusionState: only responds to checker-originated
|
||||
notifications, but setOccluded: early-returns when isOccluded doesn't
|
||||
change. A hidden window's isOccluded is NO and stays NO after show(),
|
||||
so no checker notification fires on show and the view never updates
|
||||
to kVisible.
|
||||
|
||||
3. performOcclusionStateUpdates iterates orderedWindows and marks
|
||||
not-yet-shown windows as occluded (their occlusionState lacks the
|
||||
Visible bit), which stops painting before first show.
|
||||
|
||||
Fix by also checking occlusionState in updateWebContentsVisibility,
|
||||
responding to macOS-originated notifications in
|
||||
windowChangedOcclusionState:, and skipping non-visible windows in
|
||||
performOcclusionStateUpdates.
|
||||
|
||||
This patch can be removed if the changes are upstreamed to Chromium.
|
||||
|
||||
diff --git a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
|
||||
index a5570988c3721d9f6bd05c402a7658d3af6f2c2c..54aaffde30c14a27068f89b6de6123abd6ea0660 100644
|
||||
--- a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
|
||||
+++ b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
|
||||
@@ -400,9 +400,11 @@ - (void)performOcclusionStateUpdates {
|
||||
for (NSWindow* window in windowsFromFrontToBack) {
|
||||
// The fullscreen transition causes spurious occlusion notifications.
|
||||
// See https://crbug.com/1081229 . Also, ignore windows that don't have
|
||||
- // web contentses.
|
||||
+ // web contentses, and windows that aren't visible (embedders like
|
||||
+ // Electron may create windows hidden with web contents already attached;
|
||||
+ // marking these as occluded would stop painting before first show).
|
||||
if (window == _windowReceivingFullscreenTransitionNotifications ||
|
||||
- ![window containsWebContentsViewCocoa])
|
||||
+ ![window isVisible] || ![window containsWebContentsViewCocoa])
|
||||
continue;
|
||||
|
||||
[window setOccluded:[self isWindowOccluded:window
|
||||
diff --git a/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm b/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
|
||||
index 1ef2c9052262eccdbc40030746a858b7f30ac469..34708d45274f95b5f35cdefad98ad4a1c3c28e1c 100644
|
||||
--- a/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
|
||||
+++ b/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
|
||||
@@ -477,7 +477,8 @@ - (void)updateWebContentsVisibility {
|
||||
Visibility visibility = Visibility::kVisible;
|
||||
if ([self isHiddenOrHasHiddenAncestor] || ![self window])
|
||||
visibility = Visibility::kHidden;
|
||||
- else if ([[self window] isOccluded])
|
||||
+ else if ([[self window] isOccluded] ||
|
||||
+ !([[self window] occlusionState] & NSWindowOcclusionStateVisible))
|
||||
visibility = Visibility::kOccluded;
|
||||
|
||||
[self updateWebContentsVisibility:visibility];
|
||||
@@ -521,11 +522,12 @@ - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
|
||||
}
|
||||
|
||||
- (void)windowChangedOcclusionState:(NSNotification*)aNotification {
|
||||
- // Only respond to occlusion notifications sent by the occlusion checker.
|
||||
- NSDictionary* userInfo = [aNotification userInfo];
|
||||
- NSString* occlusionCheckerKey = [WebContentsOcclusionCheckerMac className];
|
||||
- if (userInfo[occlusionCheckerKey] != nil)
|
||||
- [self updateWebContentsVisibility];
|
||||
+ // Respond to occlusion notifications from both macOS and the occlusion
|
||||
+ // checker. Embedders (e.g. Electron) may attach a WebContentsViewCocoa to
|
||||
+ // a window that has not yet been shown; macOS will notify us when the
|
||||
+ // window's occlusion state changes, but the occlusion checker will not
|
||||
+ // because -[NSWindow isOccluded] remains NO before and after show.
|
||||
+ [self updateWebContentsVisibility];
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow {
|
||||
@@ -1,279 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: David Sanders <dsanders11@ucsbalum.com>
|
||||
Date: Wed, 8 Jan 2025 23:53:27 -0800
|
||||
Subject: Revert "Code Health: Clean up stale MacWebContentsOcclusion"
|
||||
|
||||
Chrome has removed this WebContentsOcclusion feature flag upstream,
|
||||
which is now causing our visibility tests to break. This patch
|
||||
restores the legacy occlusion behavior to ensure the roll can continue
|
||||
while we debug the issue.
|
||||
|
||||
This patch can be removed when the root cause because the visibility
|
||||
specs failing on MacOS only is debugged and fixed. It should be removed
|
||||
before Electron 35's stable date.
|
||||
|
||||
Refs: https://chromium-review.googlesource.com/c/chromium/src/+/6078344
|
||||
|
||||
This partially (leaves the removal of the feature flag) reverts
|
||||
ef865130abd5539e7bce12308659b19980368f12.
|
||||
|
||||
diff --git a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.h b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.h
|
||||
index 04c7635cc093d9d676869383670a8f2199f14ac6..52d76e804e47ab0b56016d26262d6d67cbc00875 100644
|
||||
--- a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.h
|
||||
+++ b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.h
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "base/metrics/field_trial_params.h"
|
||||
#import "content/app_shim_remote_cocoa/web_contents_view_cocoa.h"
|
||||
|
||||
+extern CONTENT_EXPORT const base::FeatureParam<bool>
|
||||
+ kEnhancedWindowOcclusionDetection;
|
||||
extern CONTENT_EXPORT const base::FeatureParam<bool>
|
||||
kDisplaySleepAndAppHideDetection;
|
||||
|
||||
diff --git a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
|
||||
index a5570988c3721d9f6bd05c402a7658d3af6f2c2c..0a2dba6aa2d48bc39d2a55c8b4d6606744c10ca7 100644
|
||||
--- a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
|
||||
+++ b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
|
||||
@@ -14,9 +14,16 @@
|
||||
#include "base/mac/mac_util.h"
|
||||
#include "base/metrics/field_trial_params.h"
|
||||
#include "base/no_destructor.h"
|
||||
+#include "content/common/features.h"
|
||||
#include "content/public/browser/content_browser_client.h"
|
||||
#include "content/public/common/content_client.h"
|
||||
|
||||
+using features::kMacWebContentsOcclusion;
|
||||
+
|
||||
+// Experiment features.
|
||||
+const base::FeatureParam<bool> kEnhancedWindowOcclusionDetection{
|
||||
+ &kMacWebContentsOcclusion, "EnhancedWindowOcclusionDetection", false};
|
||||
+
|
||||
namespace {
|
||||
|
||||
NSString* const kWindowDidChangePositionInWindowList =
|
||||
@@ -125,7 +132,8 @@ - (void)dealloc {
|
||||
|
||||
- (BOOL)isManualOcclusionDetectionEnabled {
|
||||
return [WebContentsOcclusionCheckerMac
|
||||
- manualOcclusionDetectionSupportedForCurrentMacOSVersion];
|
||||
+ manualOcclusionDetectionSupportedForCurrentMacOSVersion] &&
|
||||
+ kEnhancedWindowOcclusionDetection.Get();
|
||||
}
|
||||
|
||||
// Alternative implementation of orderWindow:relativeTo:. Replaces
|
||||
diff --git a/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm b/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
|
||||
index 1ef2c9052262eccdbc40030746a858b7f30ac469..c7101b0d71826b05f61bfe0e74429d922769e792 100644
|
||||
--- a/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
|
||||
+++ b/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
|
||||
@@ -15,6 +15,7 @@
|
||||
#import "content/app_shim_remote_cocoa/web_drag_source_mac.h"
|
||||
#import "content/browser/web_contents/web_contents_view_mac.h"
|
||||
#import "content/browser/web_contents/web_drag_dest_mac.h"
|
||||
+#include "content/common/features.h"
|
||||
#include "content/public/browser/content_browser_client.h"
|
||||
#include "content/public/common/content_client.h"
|
||||
#include "ui/base/clipboard/clipboard_constants.h"
|
||||
@@ -27,6 +28,7 @@
|
||||
#include "ui/resources/grit/ui_resources.h"
|
||||
|
||||
using content::DropData;
|
||||
+using features::kMacWebContentsOcclusion;
|
||||
using remote_cocoa::mojom::DraggingInfo;
|
||||
using remote_cocoa::mojom::SelectionDirection;
|
||||
|
||||
@@ -122,12 +124,15 @@ @implementation WebContentsViewCocoa {
|
||||
WebDragSource* __strong _dragSource;
|
||||
NSDragOperation _dragOperation;
|
||||
|
||||
+ BOOL _inFullScreenTransition;
|
||||
BOOL _willSetWebContentsOccludedAfterDelay;
|
||||
}
|
||||
|
||||
+ (void)initialize {
|
||||
- // Create the WebContentsOcclusionCheckerMac shared instance.
|
||||
- [WebContentsOcclusionCheckerMac sharedInstance];
|
||||
+ if (base::FeatureList::IsEnabled(kMacWebContentsOcclusion)) {
|
||||
+ // Create the WebContentsOcclusionCheckerMac shared instance.
|
||||
+ [WebContentsOcclusionCheckerMac sharedInstance];
|
||||
+ }
|
||||
}
|
||||
|
||||
- (instancetype)initWithViewsHostableView:(ui::ViewsHostableView*)v {
|
||||
@@ -438,6 +443,7 @@ - (void)updateWebContentsVisibility:
|
||||
(remote_cocoa::mojom::Visibility)visibility {
|
||||
using remote_cocoa::mojom::Visibility;
|
||||
|
||||
+ DCHECK(base::FeatureList::IsEnabled(kMacWebContentsOcclusion));
|
||||
if (!_host)
|
||||
return;
|
||||
|
||||
@@ -483,6 +489,20 @@ - (void)updateWebContentsVisibility {
|
||||
[self updateWebContentsVisibility:visibility];
|
||||
}
|
||||
|
||||
+- (void)legacyUpdateWebContentsVisibility {
|
||||
+ using remote_cocoa::mojom::Visibility;
|
||||
+ if (!_host || _inFullScreenTransition)
|
||||
+ return;
|
||||
+ Visibility visibility = Visibility::kVisible;
|
||||
+ if ([self isHiddenOrHasHiddenAncestor] || ![self window])
|
||||
+ visibility = Visibility::kHidden;
|
||||
+ else if ([[self window] occlusionState] & NSWindowOcclusionStateVisible)
|
||||
+ visibility = Visibility::kVisible;
|
||||
+ else
|
||||
+ visibility = Visibility::kOccluded;
|
||||
+ _host->OnWindowVisibilityChanged(visibility);
|
||||
+}
|
||||
+
|
||||
- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
|
||||
// Subviews do not participate in auto layout unless the the size this view
|
||||
// changes. This allows RenderWidgetHostViewMac::SetBounds(..) to select a
|
||||
@@ -505,11 +525,39 @@ - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
|
||||
|
||||
NSWindow* oldWindow = [self window];
|
||||
|
||||
+ if (base::FeatureList::IsEnabled(kMacWebContentsOcclusion)) {
|
||||
+ if (oldWindow) {
|
||||
+ [notificationCenter
|
||||
+ removeObserver:self
|
||||
+ name:NSWindowDidChangeOcclusionStateNotification
|
||||
+ object:oldWindow];
|
||||
+ }
|
||||
+
|
||||
+ if (newWindow) {
|
||||
+ [notificationCenter
|
||||
+ addObserver:self
|
||||
+ selector:@selector(windowChangedOcclusionState:)
|
||||
+ name:NSWindowDidChangeOcclusionStateNotification
|
||||
+ object:newWindow];
|
||||
+ }
|
||||
+
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ _inFullScreenTransition = NO;
|
||||
if (oldWindow) {
|
||||
- [notificationCenter
|
||||
- removeObserver:self
|
||||
- name:NSWindowDidChangeOcclusionStateNotification
|
||||
- object:oldWindow];
|
||||
+ NSArray* notificationsToRemove = @[
|
||||
+ NSWindowDidChangeOcclusionStateNotification,
|
||||
+ NSWindowWillEnterFullScreenNotification,
|
||||
+ NSWindowDidEnterFullScreenNotification,
|
||||
+ NSWindowWillExitFullScreenNotification,
|
||||
+ NSWindowDidExitFullScreenNotification
|
||||
+ ];
|
||||
+ for (NSString* notificationName in notificationsToRemove) {
|
||||
+ [notificationCenter removeObserver:self
|
||||
+ name:notificationName
|
||||
+ object:oldWindow];
|
||||
+ }
|
||||
}
|
||||
|
||||
if (newWindow) {
|
||||
@@ -517,26 +565,66 @@ - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
|
||||
selector:@selector(windowChangedOcclusionState:)
|
||||
name:NSWindowDidChangeOcclusionStateNotification
|
||||
object:newWindow];
|
||||
+ // The fullscreen transition causes spurious occlusion notifications.
|
||||
+ // See https://crbug.com/1081229
|
||||
+ [notificationCenter addObserver:self
|
||||
+ selector:@selector(fullscreenTransitionStarted:)
|
||||
+ name:NSWindowWillEnterFullScreenNotification
|
||||
+ object:newWindow];
|
||||
+ [notificationCenter addObserver:self
|
||||
+ selector:@selector(fullscreenTransitionComplete:)
|
||||
+ name:NSWindowDidEnterFullScreenNotification
|
||||
+ object:newWindow];
|
||||
+ [notificationCenter addObserver:self
|
||||
+ selector:@selector(fullscreenTransitionStarted:)
|
||||
+ name:NSWindowWillExitFullScreenNotification
|
||||
+ object:newWindow];
|
||||
+ [notificationCenter addObserver:self
|
||||
+ selector:@selector(fullscreenTransitionComplete:)
|
||||
+ name:NSWindowDidExitFullScreenNotification
|
||||
+ object:newWindow];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)windowChangedOcclusionState:(NSNotification*)aNotification {
|
||||
- // Only respond to occlusion notifications sent by the occlusion checker.
|
||||
- NSDictionary* userInfo = [aNotification userInfo];
|
||||
- NSString* occlusionCheckerKey = [WebContentsOcclusionCheckerMac className];
|
||||
- if (userInfo[occlusionCheckerKey] != nil)
|
||||
- [self updateWebContentsVisibility];
|
||||
+ if (!base::FeatureList::IsEnabled(kMacWebContentsOcclusion)) {
|
||||
+ [self legacyUpdateWebContentsVisibility];
|
||||
+ return;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+- (void)fullscreenTransitionStarted:(NSNotification*)notification {
|
||||
+ _inFullScreenTransition = YES;
|
||||
+}
|
||||
+
|
||||
+- (void)fullscreenTransitionComplete:(NSNotification*)notification {
|
||||
+ _inFullScreenTransition = NO;
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow {
|
||||
+ if (!base::FeatureList::IsEnabled(kMacWebContentsOcclusion)) {
|
||||
+ [self legacyUpdateWebContentsVisibility];
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
[self updateWebContentsVisibility];
|
||||
}
|
||||
|
||||
- (void)viewDidHide {
|
||||
+ if (!base::FeatureList::IsEnabled(kMacWebContentsOcclusion)) {
|
||||
+ [self legacyUpdateWebContentsVisibility];
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
[self updateWebContentsVisibility];
|
||||
}
|
||||
|
||||
- (void)viewDidUnhide {
|
||||
+ if (!base::FeatureList::IsEnabled(kMacWebContentsOcclusion)) {
|
||||
+ [self legacyUpdateWebContentsVisibility];
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
[self updateWebContentsVisibility];
|
||||
}
|
||||
|
||||
diff --git a/content/common/features.cc b/content/common/features.cc
|
||||
index ee2fa2cd950a6c5cddc5904ee7a4656a18b9d73d..10a1e1e14777b61f6c42266f6f085bd47b759efd 100644
|
||||
--- a/content/common/features.cc
|
||||
+++ b/content/common/features.cc
|
||||
@@ -364,6 +364,14 @@ BASE_FEATURE(kInterestGroupUpdateIfOlderThan, base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
BASE_FEATURE(kIOSurfaceCapturer, base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
#endif
|
||||
|
||||
+// Feature that controls whether WebContentsOcclusionChecker should handle
|
||||
+// occlusion notifications.
|
||||
+#if BUILDFLAG(IS_MAC)
|
||||
+BASE_FEATURE(kMacWebContentsOcclusion,
|
||||
+ "MacWebContentsOcclusion",
|
||||
+ base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
+#endif
|
||||
+
|
||||
// When enabled, child process will not terminate itself when IPC is reset.
|
||||
BASE_FEATURE(kKeepChildProcessAfterIPCReset, base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
|
||||
diff --git a/content/common/features.h b/content/common/features.h
|
||||
index 24443780a7196b40096f44826232f77eaab68ffa..9164f2cf39542525ef2c30f572c7d0b557473f5d 100644
|
||||
--- a/content/common/features.h
|
||||
+++ b/content/common/features.h
|
||||
@@ -140,6 +140,9 @@ CONTENT_EXPORT BASE_DECLARE_FEATURE(kInterestGroupUpdateIfOlderThan);
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
CONTENT_EXPORT BASE_DECLARE_FEATURE(kIOSurfaceCapturer);
|
||||
#endif
|
||||
+#if BUILDFLAG(IS_MAC)
|
||||
+CONTENT_EXPORT BASE_DECLARE_FEATURE(kMacWebContentsOcclusion);
|
||||
+#endif
|
||||
CONTENT_EXPORT BASE_DECLARE_FEATURE(kKeepChildProcessAfterIPCReset);
|
||||
|
||||
CONTENT_EXPORT BASE_DECLARE_FEATURE(kLocalNetworkAccessForWorkers);
|
||||
@@ -533,10 +533,10 @@ index 55a0c986c5b6989ee9ce277bb6a9778abb2ad2ee..809d88f21e5572807e38132d40ee7587
|
||||
READONLY_PROPERTY(target, "exitCodes", exit_codes);
|
||||
|
||||
diff --git a/src/node_file.cc b/src/node_file.cc
|
||||
index ba6ffc2b6565dea500bc8dd4818c8fcb7648694a..e834325a763f7ea8f53210145b5edd134d6b67e6 100644
|
||||
index 96aac2d86695732bf6805f2ad2168a62241b5045..547455bb5011677719a8de1f98cb447561bce6aa 100644
|
||||
--- a/src/node_file.cc
|
||||
+++ b/src/node_file.cc
|
||||
@@ -3843,7 +3843,7 @@ void BindingData::Deserialize(Local<Context> context,
|
||||
@@ -3850,7 +3850,7 @@ void BindingData::Deserialize(Local<Context> context,
|
||||
int index,
|
||||
InternalFieldInfoBase* info) {
|
||||
DCHECK_IS_SNAPSHOT_SLOT(index);
|
||||
@@ -686,7 +686,7 @@ index d33ee3c26c111e53edf27e6368ca8f64ff30a349..f1c53c44f201b295888e7932c5e3e2b1
|
||||
|
||||
Environment* env = Environment::GetCurrent(isolate);
|
||||
diff --git a/src/node_url.cc b/src/node_url.cc
|
||||
index 9d1e8ec05161570db11f7b662395509774668d78..9b91f83d879ea02fd3d61913c8dfd35b3bf1ac31 100644
|
||||
index 9b676a0156ab8ef47f62627be953c23d4fcbf4f4..6294cd03667980e2ad23cae9e7961262369efb62 100644
|
||||
--- a/src/node_url.cc
|
||||
+++ b/src/node_url.cc
|
||||
@@ -70,7 +70,7 @@ void BindingData::Deserialize(Local<Context> context,
|
||||
|
||||
@@ -7,7 +7,7 @@ Subject: build: ensure native module compilation fails if not using a new
|
||||
This should not be upstreamed, it is a quality-of-life patch for downstream module builders.
|
||||
|
||||
diff --git a/common.gypi b/common.gypi
|
||||
index b3b5c23e471ece7584d209b3ae4197c46011d50e..bdcea65ad3e0315c85b1818e695d8b63093aed34 100644
|
||||
index d9eb9527e3cbb3b101274ab19e6d6ace42f0e022..a1243ad39b8fcf564285ace0b51b1482bd85071b 100644
|
||||
--- a/common.gypi
|
||||
+++ b/common.gypi
|
||||
@@ -89,6 +89,8 @@
|
||||
|
||||
@@ -11,7 +11,7 @@ node-gyp will use the result of `process.config` that reflects the environment
|
||||
in which the binary got built.
|
||||
|
||||
diff --git a/common.gypi b/common.gypi
|
||||
index bdcea65ad3e0315c85b1818e695d8b63093aed34..0653735a0b154e326e5df7049a7beb395f0015c8 100644
|
||||
index a1243ad39b8fcf564285ace0b51b1482bd85071b..60ac7a50718fd8239fd96b811cdccd1c73b2d606 100644
|
||||
--- a/common.gypi
|
||||
+++ b/common.gypi
|
||||
@@ -128,6 +128,7 @@
|
||||
|
||||
@@ -10,7 +10,7 @@ M151, and so we should allow for building until then.
|
||||
This patch can be removed at the M151 branch point.
|
||||
|
||||
diff --git a/common.gypi b/common.gypi
|
||||
index 0653735a0b154e326e5df7049a7beb395f0015c8..006f52ed18d955da0d9a06e881e86e6e724095ac 100644
|
||||
index 60ac7a50718fd8239fd96b811cdccd1c73b2d606..709eb83801eeed81f79c4305a86d1a19710298c2 100644
|
||||
--- a/common.gypi
|
||||
+++ b/common.gypi
|
||||
@@ -677,7 +677,7 @@
|
||||
|
||||
@@ -7,7 +7,7 @@ common.gypi is a file that's included in the node header bundle, despite
|
||||
the fact that we do not build node with gyp.
|
||||
|
||||
diff --git a/common.gypi b/common.gypi
|
||||
index c5a7dc9cacf8b983984e7c7de9e63d26e418cc8d..b3b5c23e471ece7584d209b3ae4197c46011d50e 100644
|
||||
index 283c60eab356a5befc15027cd186ea0416914ee6..d9eb9527e3cbb3b101274ab19e6d6ace42f0e022 100644
|
||||
--- a/common.gypi
|
||||
+++ b/common.gypi
|
||||
@@ -91,6 +91,23 @@
|
||||
|
||||
@@ -53,10 +53,10 @@ index 81799fc159cf20344aac64cd7129240deb9a4fe8..12b476ff97603718186dd25b1f435d37
|
||||
const maybeMain = resolvedOption <= legacyMainResolveExtensionsIndexes.kResolvedByMainIndexNode ?
|
||||
packageConfig.main || './' : '';
|
||||
diff --git a/src/node_file.cc b/src/node_file.cc
|
||||
index 58476306172433db98a3e3a1ab31d13bf42014f1..ba6ffc2b6565dea500bc8dd4818c8fcb7648694a 100644
|
||||
index c69b4eb461cab79906833152d02f76f81149ad7e..96aac2d86695732bf6805f2ad2168a62241b5045 100644
|
||||
--- a/src/node_file.cc
|
||||
+++ b/src/node_file.cc
|
||||
@@ -3592,13 +3592,25 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
|
||||
@@ -3599,13 +3599,25 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
|
||||
BindingData::FilePathIsFileReturnType BindingData::FilePathIsFile(
|
||||
@@ -83,7 +83,7 @@ index 58476306172433db98a3e3a1ab31d13bf42014f1..ba6ffc2b6565dea500bc8dd4818c8fcb
|
||||
uv_fs_t req;
|
||||
|
||||
int rc = uv_fs_stat(env->event_loop(), &req, file_path.c_str(), nullptr);
|
||||
@@ -3656,6 +3668,11 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
|
||||
@@ -3663,6 +3675,11 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
|
||||
std::optional<std::string> initial_file_path;
|
||||
std::string file_path;
|
||||
|
||||
@@ -95,7 +95,7 @@ index 58476306172433db98a3e3a1ab31d13bf42014f1..ba6ffc2b6565dea500bc8dd4818c8fcb
|
||||
if (args.Length() >= 2 && args[1]->IsString()) {
|
||||
auto package_config_main = Utf8Value(isolate, args[1]).ToString();
|
||||
|
||||
@@ -3676,7 +3693,7 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
|
||||
@@ -3683,7 +3700,7 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
|
||||
BufferValue buff_file_path(isolate, local_file_path);
|
||||
ToNamespacedPath(env, &buff_file_path);
|
||||
|
||||
@@ -104,7 +104,7 @@ index 58476306172433db98a3e3a1ab31d13bf42014f1..ba6ffc2b6565dea500bc8dd4818c8fcb
|
||||
case BindingData::FilePathIsFileReturnType::kIsFile:
|
||||
return args.GetReturnValue().Set(i);
|
||||
case BindingData::FilePathIsFileReturnType::kIsNotFile:
|
||||
@@ -3713,7 +3730,7 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
|
||||
@@ -3720,7 +3737,7 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
|
||||
BufferValue buff_file_path(isolate, local_file_path);
|
||||
ToNamespacedPath(env, &buff_file_path);
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ libc++ added [[nodiscard]] to std::filesystem::copy_options operator|=
|
||||
which causes build failures with -Werror.
|
||||
|
||||
diff --git a/src/node_file.cc b/src/node_file.cc
|
||||
index e834325a763f7ea8f53210145b5edd134d6b67e6..5197202255bcd9345ac19c7fb6106f49c2800770 100644
|
||||
index 547455bb5011677719a8de1f98cb447561bce6aa..385db5fd6fe5db6bb7ff17e98309b6cd605a82d3 100644
|
||||
--- a/src/node_file.cc
|
||||
+++ b/src/node_file.cc
|
||||
@@ -3453,11 +3453,11 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
|
||||
@@ -3460,11 +3460,11 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
auto file_copy_opts = std::filesystem::copy_options::recursive;
|
||||
if (force) {
|
||||
|
||||
@@ -6,6 +6,7 @@ Everything here should be project agnostic: it shouldn't rely on project's
|
||||
structure, or make assumptions about the passed arguments or calls' outcomes.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import io
|
||||
import os
|
||||
import posixpath
|
||||
@@ -18,7 +19,14 @@ sys.path.append(SCRIPT_DIR)
|
||||
|
||||
from patches import PATCH_FILENAME_PREFIX, is_patch_location_line
|
||||
|
||||
UPSTREAM_HEAD='refs/patches/upstream-head'
|
||||
# In gclient-new-workdir worktrees, .git/refs is symlinked back to the source
|
||||
# checkout, so a single fixed ref name would be shared (and clobbered) across
|
||||
# worktrees. Derive a per-checkout suffix from this script's absolute path so
|
||||
# each worktree records its own upstream head in the shared refs directory.
|
||||
_LEGACY_UPSTREAM_HEAD = 'refs/patches/upstream-head'
|
||||
UPSTREAM_HEAD = (
|
||||
_LEGACY_UPSTREAM_HEAD + '-' + hashlib.md5(SCRIPT_DIR.encode()).hexdigest()[:8]
|
||||
)
|
||||
|
||||
def is_repo_root(path):
|
||||
path_exists = os.path.exists(path)
|
||||
@@ -83,6 +91,8 @@ def import_patches(repo, ref=UPSTREAM_HEAD, **kwargs):
|
||||
"""same as am(), but we save the upstream HEAD so we can refer to it when we
|
||||
later export patches"""
|
||||
update_ref(repo=repo, ref=ref, newvalue='HEAD')
|
||||
if ref != _LEGACY_UPSTREAM_HEAD:
|
||||
update_ref(repo=repo, ref=_LEGACY_UPSTREAM_HEAD, newvalue='HEAD')
|
||||
am(repo=repo, **kwargs)
|
||||
|
||||
|
||||
@@ -102,19 +112,21 @@ def get_commit_count(repo, commit_range):
|
||||
|
||||
def guess_base_commit(repo, ref):
|
||||
"""Guess which commit the patches might be based on"""
|
||||
try:
|
||||
upstream_head = get_commit_for_ref(repo, ref)
|
||||
num_commits = get_commit_count(repo, upstream_head + '..')
|
||||
return [upstream_head, num_commits]
|
||||
except subprocess.CalledProcessError:
|
||||
args = [
|
||||
'git',
|
||||
'-C',
|
||||
repo,
|
||||
'describe',
|
||||
'--tags',
|
||||
]
|
||||
return subprocess.check_output(args).decode('utf-8').rsplit('-', 2)[0:2]
|
||||
for candidate in (ref, _LEGACY_UPSTREAM_HEAD):
|
||||
try:
|
||||
upstream_head = get_commit_for_ref(repo, candidate)
|
||||
num_commits = get_commit_count(repo, upstream_head + '..')
|
||||
return [upstream_head, num_commits]
|
||||
except subprocess.CalledProcessError:
|
||||
continue
|
||||
args = [
|
||||
'git',
|
||||
'-C',
|
||||
repo,
|
||||
'describe',
|
||||
'--tags',
|
||||
]
|
||||
return subprocess.check_output(args).decode('utf-8').rsplit('-', 2)[0:2]
|
||||
|
||||
|
||||
def format_patch(repo, since):
|
||||
|
||||
@@ -308,6 +308,7 @@ void Menu::OnMenuWillClose() {
|
||||
}
|
||||
|
||||
void Menu::OnMenuWillShow() {
|
||||
keep_alive_ = this;
|
||||
Emit("menu-will-show");
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ class Menu : public gin::Wrappable<Menu>,
|
||||
bool IsVisibleAt(int index) const;
|
||||
bool WorksWhenHiddenAt(int index) const;
|
||||
|
||||
gin_helper::SelfKeepAlive<Menu> keep_alive_{this};
|
||||
gin_helper::SelfKeepAlive<Menu> keep_alive_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace electron::api
|
||||
|
||||
@@ -3126,16 +3126,18 @@ void OnGetDeviceNameToUse(base::WeakPtr<content::WebContents> web_contents,
|
||||
.Set(printing::kSettingMediaSizeIsDefault, true);
|
||||
};
|
||||
|
||||
const bool use_default_size =
|
||||
print_settings.FindBool(kUseDefaultPrinterPageSize).value_or(false);
|
||||
std::optional<gfx::Size> paper_size;
|
||||
if (use_default_size)
|
||||
paper_size = GetPrinterDefaultPaperSize(base::UTF16ToUTF8(info.second));
|
||||
if (!print_settings.Find(printing::kSettingMediaSize)) {
|
||||
const bool use_default_size =
|
||||
print_settings.FindBool(kUseDefaultPrinterPageSize).value_or(false);
|
||||
std::optional<gfx::Size> paper_size;
|
||||
if (use_default_size)
|
||||
paper_size = GetPrinterDefaultPaperSize(base::UTF16ToUTF8(info.second));
|
||||
|
||||
print_settings.Set(
|
||||
printing::kSettingMediaSize,
|
||||
paper_size ? make_media_size(paper_size->height(), paper_size->width())
|
||||
: make_media_size(297000, 210000));
|
||||
print_settings.Set(
|
||||
printing::kSettingMediaSize,
|
||||
paper_size ? make_media_size(paper_size->height(), paper_size->width())
|
||||
: make_media_size(297000, 210000));
|
||||
}
|
||||
|
||||
content::RenderFrameHost* rfh = GetRenderFrameHostToUse(web_contents.get());
|
||||
if (!rfh)
|
||||
|
||||
@@ -87,13 +87,6 @@ void InitializeFeatureList() {
|
||||
std::string(",") + sandbox::policy::features::kNetworkServiceSandbox.name;
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
disable_features +=
|
||||
// MacWebContentsOcclusion is causing some odd visibility
|
||||
// issues with multiple web contents
|
||||
std::string(",") + features::kMacWebContentsOcclusion.name;
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
// Enable window.showSaveFilePicker api for saving pdf files.
|
||||
// Refs https://issues.chromium.org/issues/373852607
|
||||
|
||||
@@ -136,24 +136,10 @@ NativeWindow::~NativeWindow() {
|
||||
|
||||
void NativeWindow::InitFromOptions(const gin_helper::Dictionary& options) {
|
||||
// Setup window from options.
|
||||
if (int x, y; options.Get(options::kX, &x) && options.Get(options::kY, &y)) {
|
||||
SetPosition(gfx::Point{x, y});
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// FIXME(felixrieseberg): Dirty, dirty workaround for
|
||||
// https://github.com/electron/electron/issues/10862
|
||||
// Somehow, we need to call `SetBounds` twice to get
|
||||
// usable results. The root cause is still unknown.
|
||||
SetPosition(gfx::Point{x, y});
|
||||
#endif
|
||||
} else if (bool center; options.Get(options::kCenter, ¢er) && center) {
|
||||
Center();
|
||||
}
|
||||
|
||||
const bool use_content_size =
|
||||
options.ValueOrDefault(options::kUseContentSize, false);
|
||||
|
||||
// On Linux and Window we may already have maximum size defined.
|
||||
// On Linux and Windows we may already have minimum and maximum size defined.
|
||||
extensions::SizeConstraints size_constraints(
|
||||
use_content_size ? GetContentSizeConstraints() : GetSizeConstraints());
|
||||
|
||||
@@ -180,10 +166,32 @@ void NativeWindow::InitFromOptions(const gin_helper::Dictionary& options) {
|
||||
size_constraints.set_maximum_size(gfx::Size(max_width, max_height));
|
||||
|
||||
if (use_content_size) {
|
||||
gfx::Size clamped = size_constraints.ClampSize(GetContentSize());
|
||||
if (clamped != GetContentSize()) {
|
||||
SetContentSize(clamped);
|
||||
}
|
||||
SetContentSizeConstraints(size_constraints);
|
||||
} else {
|
||||
gfx::Size clamped = size_constraints.ClampSize(GetSize());
|
||||
if (clamped != GetSize()) {
|
||||
SetSize(clamped);
|
||||
}
|
||||
SetSizeConstraints(size_constraints);
|
||||
}
|
||||
|
||||
if (int x, y; options.Get(options::kX, &x) && options.Get(options::kY, &y)) {
|
||||
SetPosition(gfx::Point{x, y});
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// FIXME(felixrieseberg): Dirty, dirty workaround for
|
||||
// https://github.com/electron/electron/issues/10862
|
||||
// Somehow, we need to call `SetBounds` twice to get
|
||||
// usable results. The root cause is still unknown.
|
||||
SetPosition(gfx::Point{x, y});
|
||||
#endif
|
||||
} else if (bool center; options.Get(options::kCenter, ¢er) && center) {
|
||||
Center();
|
||||
}
|
||||
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
|
||||
if (bool val; options.Get(options::kClosable, &val))
|
||||
SetClosable(val);
|
||||
|
||||
@@ -43,6 +43,14 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
|
||||
#pragma mark - NSWindowDelegate
|
||||
|
||||
- (void)windowDidChangeOcclusionState:(NSNotification*)notification {
|
||||
// Chromium's WebContentsOcclusionCheckerMac posts synthetic occlusion
|
||||
// notifications tagged with its class name in userInfo. These reflect the
|
||||
// checker's manual frame-intersection heuristic, not an actual macOS
|
||||
// occlusion state change, so the real occlusionState hasn't changed and
|
||||
// emitting show/hide in response would be spurious.
|
||||
if (notification.userInfo[@"WebContentsOcclusionCheckerMac"] != nil)
|
||||
return;
|
||||
|
||||
// notification.object is the window that changed its state.
|
||||
// It's safe to use self.window instead if you don't assign one delegate to
|
||||
// many windows
|
||||
@@ -150,45 +158,43 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
|
||||
windowSize.width() - contentSize.width() + extraSize.width();
|
||||
double extraHeightPlusFrame = titleBarHeight + extraSize.height();
|
||||
|
||||
newSize.width =
|
||||
roundf((frameSize.height - extraHeightPlusFrame) * aspectRatio +
|
||||
extraWidthPlusFrame);
|
||||
newSize.height =
|
||||
roundf((newSize.width - extraWidthPlusFrame) / aspectRatio +
|
||||
extraHeightPlusFrame);
|
||||
auto widthForHeight = [&](double h) {
|
||||
return (h - extraHeightPlusFrame) * aspectRatio + extraWidthPlusFrame;
|
||||
};
|
||||
auto heightForWidth = [&](double w) {
|
||||
return (w - extraWidthPlusFrame) / aspectRatio + extraHeightPlusFrame;
|
||||
};
|
||||
|
||||
newSize.width = roundf(widthForHeight(frameSize.height));
|
||||
newSize.height = roundf(heightForWidth(newSize.width));
|
||||
|
||||
// Clamp to minimum width/height while ensuring aspect ratio remains.
|
||||
NSSize minSize = [window minSize];
|
||||
NSSize zeroSize =
|
||||
shell_->has_frame() ? NSMakeSize(0, titleBarHeight) : NSZeroSize;
|
||||
if (!NSEqualSizes(minSize, zeroSize)) {
|
||||
double minWidthForAspectRatio =
|
||||
(minSize.height - titleBarHeight) * aspectRatio;
|
||||
bool atMinHeight =
|
||||
minSize.height > zeroSize.height && newSize.height <= minSize.height;
|
||||
newSize.width = atMinHeight ? minWidthForAspectRatio
|
||||
newSize.width = atMinHeight ? widthForHeight(minSize.height)
|
||||
: std::max(newSize.width, minSize.width);
|
||||
|
||||
double minHeightForAspectRatio = minSize.width / aspectRatio;
|
||||
bool atMinWidth =
|
||||
minSize.width > zeroSize.width && newSize.width <= minSize.width;
|
||||
newSize.height = atMinWidth ? minHeightForAspectRatio
|
||||
newSize.height = atMinWidth ? heightForWidth(minSize.width)
|
||||
: std::max(newSize.height, minSize.height);
|
||||
}
|
||||
|
||||
// Clamp to maximum width/height while ensuring aspect ratio remains.
|
||||
NSSize maxSize = [window maxSize];
|
||||
if (!NSEqualSizes(maxSize, NSMakeSize(FLT_MAX, FLT_MAX))) {
|
||||
double maxWidthForAspectRatio = maxSize.height * aspectRatio;
|
||||
bool atMaxHeight =
|
||||
maxSize.height < FLT_MAX && newSize.height >= maxSize.height;
|
||||
newSize.width = atMaxHeight ? maxWidthForAspectRatio
|
||||
newSize.width = atMaxHeight ? widthForHeight(maxSize.height)
|
||||
: std::min(newSize.width, maxSize.width);
|
||||
|
||||
double maxHeightForAspectRatio = maxSize.width / aspectRatio;
|
||||
bool atMaxWidth =
|
||||
maxSize.width < FLT_MAX && newSize.width >= maxSize.width;
|
||||
newSize.height = atMaxWidth ? maxHeightForAspectRatio
|
||||
newSize.height = atMaxWidth ? heightForWidth(maxSize.width)
|
||||
: std::min(newSize.height, maxSize.height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,7 +402,7 @@ void InspectableWebContents::SetDockState(const std::string& state) {
|
||||
can_dock_ = false;
|
||||
} else {
|
||||
can_dock_ = true;
|
||||
dock_state_ = IsValidDockState(state) ? state : "right";
|
||||
dock_state_ = (state.empty() || IsValidDockState(state)) ? state : "right";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1671,6 +1671,32 @@ describe('BrowserWindow module', () => {
|
||||
expectBoundsEqual(w.getMaximumSize(), [900, 600]);
|
||||
});
|
||||
|
||||
it('creates window at min size when a smaller size is requested', () => {
|
||||
const w1 = new BrowserWindow({
|
||||
show: false,
|
||||
width: 200,
|
||||
height: 200,
|
||||
minWidth: 300,
|
||||
minHeight: 300
|
||||
});
|
||||
const size = w1.getSize();
|
||||
expect(size[0]).to.equal(300);
|
||||
expect(size[1]).to.equal(300);
|
||||
});
|
||||
|
||||
it('creates window at max size when a larger size is requested', () => {
|
||||
const w1 = new BrowserWindow({
|
||||
show: false,
|
||||
width: 300,
|
||||
height: 300,
|
||||
maxWidth: 200,
|
||||
maxHeight: 200
|
||||
});
|
||||
const size = w1.getSize();
|
||||
expect(size[0]).to.equal(200);
|
||||
expect(size[1]).to.equal(200);
|
||||
});
|
||||
|
||||
it('enforces minimum size', async () => {
|
||||
w.setMinimumSize(300, 300);
|
||||
const resize = once(w, 'resize');
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BaseWindow, BrowserWindow, View, WebContentsView, webContents, screen }
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { once } from 'node:events';
|
||||
import { setTimeout as setTimeoutAsync } from 'node:timers/promises';
|
||||
|
||||
import { HexColors, ScreenCapture, hasCapturableScreen, nextFrameTime } from './lib/screen-helpers';
|
||||
import { defer, ifdescribe, waitUntil } from './lib/spec-helpers';
|
||||
@@ -309,6 +310,94 @@ describe('WebContentsView', () => {
|
||||
}
|
||||
expect(visibilityState).to.equal('visible');
|
||||
});
|
||||
|
||||
it('tracks visibility for multiple child WebContentsViews', async () => {
|
||||
const w = new BaseWindow({ show: false });
|
||||
const cv = new View();
|
||||
w.setContentView(cv);
|
||||
|
||||
const v1 = new WebContentsView();
|
||||
const v2 = new WebContentsView();
|
||||
cv.addChildView(v1);
|
||||
cv.addChildView(v2);
|
||||
v1.setBounds({ x: 0, y: 0, width: 400, height: 300 });
|
||||
v2.setBounds({ x: 0, y: 300, width: 400, height: 300 });
|
||||
|
||||
await v1.webContents.loadURL('about:blank');
|
||||
await v2.webContents.loadURL('about:blank');
|
||||
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v1, 'hidden'))).to.eventually.be.fulfilled();
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v2, 'hidden'))).to.eventually.be.fulfilled();
|
||||
|
||||
w.show();
|
||||
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v1, 'visible'))).to.eventually.be.fulfilled();
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v2, 'visible'))).to.eventually.be.fulfilled();
|
||||
|
||||
w.hide();
|
||||
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v1, 'hidden'))).to.eventually.be.fulfilled();
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v2, 'hidden'))).to.eventually.be.fulfilled();
|
||||
});
|
||||
|
||||
it('tracks visibility independently when a child WebContentsView is hidden via setVisible', async () => {
|
||||
const w = new BaseWindow();
|
||||
const cv = new View();
|
||||
w.setContentView(cv);
|
||||
|
||||
const v1 = new WebContentsView();
|
||||
const v2 = new WebContentsView();
|
||||
cv.addChildView(v1);
|
||||
cv.addChildView(v2);
|
||||
v1.setBounds({ x: 0, y: 0, width: 400, height: 300 });
|
||||
v2.setBounds({ x: 0, y: 300, width: 400, height: 300 });
|
||||
|
||||
await v1.webContents.loadURL('about:blank');
|
||||
await v2.webContents.loadURL('about:blank');
|
||||
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v1, 'visible'))).to.eventually.be.fulfilled();
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v2, 'visible'))).to.eventually.be.fulfilled();
|
||||
|
||||
v1.setVisible(false);
|
||||
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v1, 'hidden'))).to.eventually.be.fulfilled();
|
||||
// v2 should remain visible while v1 is hidden
|
||||
expect(await v2.webContents.executeJavaScript('document.visibilityState')).to.equal('visible');
|
||||
|
||||
v1.setVisible(true);
|
||||
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v1, 'visible'))).to.eventually.be.fulfilled();
|
||||
});
|
||||
|
||||
it('fires a single visibilitychange event per show/hide transition', async () => {
|
||||
const w = new BaseWindow({ show: false });
|
||||
const v = new WebContentsView();
|
||||
w.setContentView(v);
|
||||
await v.webContents.loadURL('about:blank');
|
||||
|
||||
await v.webContents.executeJavaScript(`
|
||||
window.__visChanges = [];
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
window.__visChanges.push(document.visibilityState);
|
||||
});
|
||||
`);
|
||||
|
||||
w.show();
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v, 'visible'))).to.eventually.be.fulfilled();
|
||||
|
||||
// Give any delayed/queued occlusion updates time to fire.
|
||||
await setTimeoutAsync(1500);
|
||||
|
||||
w.hide();
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v, 'hidden'))).to.eventually.be.fulfilled();
|
||||
|
||||
await setTimeoutAsync(1500);
|
||||
|
||||
const changes = await v.webContents.executeJavaScript('window.__visChanges');
|
||||
// Expect exactly one 'visible' followed by one 'hidden'. Extra events
|
||||
// would indicate the occlusion checker is causing spurious transitions.
|
||||
expect(changes).to.deep.equal(['visible', 'hidden']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setBorderRadius', () => {
|
||||
|
||||
Reference in New Issue
Block a user