Compare commits

...

10 Commits

Author SHA1 Message Date
Shelley Vohr
398dde9dfb build: use inputs for target_arch in build (#42684) 2024-06-27 08:11:10 -07:00
Shelley Vohr
32e5012017 feat: enable Windows Control Overlay on Linux (#41769) 2024-06-27 14:56:36 +02:00
David Sanders
1d4b00692d build: bump version in .nvmrc to 20 (#42668) 2024-06-27 13:46:11 +02:00
electron-roller[bot]
ec24b51808 chore: bump node to v20.15.0 (main) (#42616)
* chore: bump node in DEPS to v20.15.0

* doc: Add OpenSSL errors to API docs

https://github.com/nodejs/node/pull/34213

* test: crypto-rsa-dsa testing for dynamic openssl

https://github.com/nodejs/node/pull/52781

* src: allow preventing debug signal handler start

https://github.com/nodejs/node/pull/46681

* cli: allow running wasm in limited vmemory with --disable-wasm-trap-handler

https://github.com/nodejs/node/pull/52766

* chore: fixup indices

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2024-06-27 13:34:39 +02:00
Shelley Vohr
c628de52d9 fix: defaultPath should apply on all dialog types in Linux Portal (#42655)
fix: defaultPath should apply on all dialog types in Linux Portal dialogs
2024-06-27 11:07:26 +02:00
Shelley Vohr
2b31d8459d fix: param passed to showSaveDialogSync on Linux (#42652)
fix: pass correct param to sync functions on Linux
2024-06-27 10:23:18 +02:00
Keeley Hammond
912706b23a build: strip linux release binaries (#42675) 2024-06-26 17:41:36 -07:00
Samuel Attard
b31acfb706 docs: clarify security semantics of safeStorage (#42666)
* docs: clarify security semantics of safeStorage

* Apply suggestions from code review

Co-authored-by: Erick Zhao <erick@hotmail.ca>

* Update safe-storage.md

* Update safe-storage.md

---------

Co-authored-by: Erick Zhao <erick@hotmail.ca>
2024-06-26 13:24:06 -07:00
Shelley Vohr
607b9b8f11 build: clean up upload-index-json error handling (#42657) 2024-06-26 09:25:58 -07:00
Keeley Hammond
a8baa6f1ad build: remove CircleCI from release pipeline (#42651)
* build: remove CircleCI from release pipeline

* build: remove IS_GHA_RELEASE and CHECK_DIST_MANIFEST
2024-06-25 14:25:04 -07:00
55 changed files with 1158 additions and 494 deletions

View File

@@ -17,6 +17,9 @@ inputs:
is-release:
description: 'Is release build'
required: true
strip-binaries:
description: 'Strip binaries (Linux only)'
required: false
generate-symbols:
description: 'Generate symbols'
required: true
@@ -54,6 +57,14 @@ runs:
NINJA_SUMMARIZE_BUILD=1 e build -j $NUMBER_OF_NINJA_PROCESSES
cp out/Default/.ninja_log out/electron_ninja_log
node electron/script/check-symlinks.js
- name: Strip Electron Binaries ${{ inputs.step-suffix }}
shell: bash
if: ${{ inputs.strip-binaries == 'true' }}
run: |
cd src
electron/script/copy-debug-symbols.py --target-cpu="${{ inputs.target-arch }}" --out-dir=out/Default/debug --compress
electron/script/strip-binaries.py --target-cpu="${{ inputs.target-arch }}"
electron/script/add-debug-link.py --target-cpu="${{ inputs.target-arch }}" --debug-dir=out/Default/debug
- name: Build Electron dist.zip ${{ inputs.step-suffix }}
shell: bash
run: |
@@ -170,7 +181,6 @@ runs:
run: |
cd src/electron
node script/yarn create-typescript-definitions
# TODO(vertedinde): These uploads currently point to a different Azure bucket & GitHub Repo
- name: Publish Electron Dist ${{ inputs.step-suffix }}
if: ${{ inputs.is-release == 'true' }}
shell: bash
@@ -188,9 +198,9 @@ runs:
shell: bash
run: |
if [ "${{ inputs.is-asan }}" = "true" ]; then
ARTIFACT_KEY=${{ inputs.artifact-platform }}_${{ env.TARGET_ARCH }}_asan
ARTIFACT_KEY=${{ inputs.artifact-platform }}_${{ inputs.target-arch }}_asan
else
ARTIFACT_KEY=${{ inputs.artifact-platform }}_${{ env.TARGET_ARCH }}
ARTIFACT_KEY=${{ inputs.artifact-platform }}_${{ inputs.target-arch }}
fi
echo "ARTIFACT_KEY=$ARTIFACT_KEY" >> $GITHUB_ENV
# The current generated_artifacts_<< artifact.key >> name was taken from CircleCI
@@ -202,9 +212,9 @@ runs:
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808
with:
name: generated_artifacts_${{ env.ARTIFACT_KEY }}
path: ./generated_artifacts_${{ inputs.artifact-platform }}_${{ env.TARGET_ARCH }}
path: ./generated_artifacts_${{ inputs.artifact-platform }}_${{ inputs.target-arch }}
- name: Upload Src Artifacts ${{ inputs.step-suffix }}
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808
with:
name: src_artifacts_${{ env.ARTIFACT_KEY }}
path: ./src_artifacts_${{ inputs.artifact-platform }}_${{ env.TARGET_ARCH }}
path: ./src_artifacts_${{ inputs.artifact-platform }}_${{ inputs.target-arch }}

View File

@@ -49,6 +49,7 @@ jobs:
is-release: true
gn-build-type: release
generate-symbols: true
strip-binaries: true
upload-to-storage: ${{ inputs.upload-to-storage }}
secrets: inherit
@@ -64,6 +65,7 @@ jobs:
is-release: true
gn-build-type: release
generate-symbols: true
strip-binaries: true
upload-to-storage: ${{ inputs.upload-to-storage }}
secrets: inherit
@@ -79,5 +81,6 @@ jobs:
is-release: true
gn-build-type: release
generate-symbols: true
strip-binaries: true
upload-to-storage: ${{ inputs.upload-to-storage }}
secrets: inherit

View File

@@ -44,6 +44,11 @@ on:
required: true
type: string
default: '0'
strip-binaries:
description: 'Strip the binaries before release (Linux only)'
required: false
type: boolean
default: false
is-asan:
description: 'Building the Address Sanitizer (ASan) Linux build'
required: false
@@ -62,9 +67,6 @@ env:
ELECTRON_RBE_JWT: ${{ secrets.ELECTRON_RBE_JWT }}
ELECTRON_GITHUB_TOKEN: ${{ secrets.ELECTRON_GITHUB_TOKEN }}
GCLIENT_EXTRA_ARGS: ${{ inputs.target-platform == 'macos' && '--custom-var=checkout_mac=True --custom-var=host_os=mac' || '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True' }}
# Only disable this in the Asan build
CHECK_DIST_MANIFEST: true
IS_GHA_RELEASE: true
ELECTRON_OUT_DIR: Default
jobs:
@@ -189,6 +191,7 @@ jobs:
artifact-platform: ${{ inputs.target-platform == 'linux' && 'linux' || 'darwin' }}
is-release: '${{ inputs.is-release }}'
generate-symbols: '${{ inputs.generate-symbols }}'
strip-binaries: '${{ inputs.strip-binaries }}'
upload-to-storage: '${{ inputs.upload-to-storage }}'
is-asan: '${{ inputs.is-asan }}'
- name: Set GN_EXTRA_ARGS for MAS Build

2
.nvmrc
View File

@@ -1 +1 @@
16
20

2
DEPS
View File

@@ -4,7 +4,7 @@ vars = {
'chromium_version':
'127.0.6521.0',
'node_version':
'v20.14.0',
'v20.15.0',
'nan_version':
'e14bdcd1f72d62bca1d541b66da43130384ec213',
'squirrel.mac_version':

View File

@@ -108,6 +108,7 @@ static_library("chrome") {
"//chrome/browser/ui/frame/window_frame_util.h",
"//chrome/browser/ui/ui_features.cc",
"//chrome/browser/ui/ui_features.h",
"//chrome/browser/ui/view_ids.h",
"//chrome/browser/ui/views/eye_dropper/eye_dropper.cc",
"//chrome/browser/ui/views/eye_dropper/eye_dropper.h",
"//chrome/browser/ui/views/overlay/back_to_tab_label_button.cc",
@@ -151,7 +152,6 @@ static_library("chrome") {
"//chrome/browser/media/webrtc/window_icon_util_win.cc",
"//chrome/browser/process_singleton_win.cc",
"//chrome/browser/ui/frame/window_frame_util.h",
"//chrome/browser/ui/view_ids.h",
"//chrome/browser/win/chrome_process_finder.cc",
"//chrome/browser/win/chrome_process_finder.h",
"//chrome/browser/win/titlebar_config.cc",

View File

@@ -1370,15 +1370,16 @@ machine has a touch bar.
**Note:** The TouchBar API is currently experimental and may change or be
removed in future Electron releases.
#### `win.setTitleBarOverlay(options)` _Windows_
#### `win.setTitleBarOverlay(options)` _Windows_ _Linux_
* `options` Object
* `color` String (optional) _Windows_ - The CSS color of the Window Controls Overlay when enabled.
* `symbolColor` String (optional) _Windows_ - The CSS color of the symbols on the Window Controls Overlay when enabled.
* `height` Integer (optional) _Windows_ - The height of the title bar and Window Controls Overlay in pixels.
* `color` String (optional) - The CSS color of the Window Controls Overlay when enabled.
* `symbolColor` String (optional) - The CSS color of the symbols on the Window Controls Overlay when enabled.
* `height` Integer (optional) - The height of the title bar and Window Controls Overlay in pixels.
On a Window with Window Controls Overlay already enabled, this method updates
the style of the title bar overlay.
On a Window with Window Controls Overlay already enabled, this method updates the style of the title bar overlay.
On Linux, the `symbolColor` is automatically calculated to have minimum accessible contrast to the `color` if not explicitly set.
[quick-look]: https://en.wikipedia.org/wiki/Quick_Look
[vibrancy-docs]: https://developer.apple.com/documentation/appkit/nsvisualeffectview?preferredLanguage=objc

View File

@@ -1641,15 +1641,16 @@ with `addBrowserView` or `setBrowserView`. The top-most BrowserView is the last
> The `BrowserView` class is deprecated, and replaced by the new
> [`WebContentsView`](web-contents-view.md) class.
#### `win.setTitleBarOverlay(options)` _Windows_
#### `win.setTitleBarOverlay(options)` _Windows_ _Linux_
* `options` Object
* `color` String (optional) _Windows_ - The CSS color of the Window Controls Overlay when enabled.
* `symbolColor` String (optional) _Windows_ - The CSS color of the symbols on the Window Controls Overlay when enabled.
* `height` Integer (optional) _macOS_ _Windows_ - The height of the title bar and Window Controls Overlay in pixels.
* `color` String (optional) - The CSS color of the Window Controls Overlay when enabled.
* `symbolColor` String (optional) - The CSS color of the symbols on the Window Controls Overlay when enabled.
* `height` Integer (optional) - The height of the title bar and Window Controls Overlay in pixels.
On a Window with Window Controls Overlay already enabled, this method updates
the style of the title bar overlay.
On a window with Window Controls Overlay already enabled, this method updates the style of the title bar overlay.
On Linux, the `symbolColor` is automatically calculated to have minimum accessible contrast to the `color` if not explicitly set.
[page-visibility-api]: https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
[quick-look]: https://en.wikipedia.org/wiki/Quick_Look

View File

@@ -4,7 +4,19 @@
Process: [Main](../glossary.md#main-process)
This module protects data stored on disk from being accessed by other applications or users with full disk access.
This module adds extra protection to data being stored on disk by using OS-provided cryptography systems. Current
security semantics for each platform are outlined below.
* **macOS**: Encryption keys are stored for your app in [Keychain Access](https://support.apple.com/en-ca/guide/keychain-access/kyca1083/mac) in a way that prevents
other applications from loading them without user override. Therefore, content is protected from other users and other apps running in the same userspace.
* **Windows**: Encryption keys are generated via [DPAPI](https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata).
As per the Windows documentation: "Typically, only a user with the same logon credential as the user who encrypted the data can typically
decrypt the data". Therefore, content is protected from other users on the same machine, but not from other apps running in the
same userspace.
* **Linux**: Encryption keys are generated and stored in a secret store that varies depending on your window manager and system setup. Options currently supported are `kwallet`, `kwallet5`, `kwallet6` and `gnome-libsecret`, but more may be available in future versions of Electron. As such, the
security semantics of content protected via the `safeStorage` API vary between window managers and secret stores.
* Note that not all Linux setups have an available secret store. If no secret store is available, items stored in using the `safeStorage` API will be unprotected
as they are encrypted via hardcoded plaintext password. You can detect when this happens when `safeStorage.getSelectedStorageBackend()` returns `basic_text`.
Note that on Mac, access to the system Keychain is required and
these calls can block the current thread to collect user input.

View File

@@ -80,14 +80,14 @@
* `followWindow` - The backdrop should automatically appear active when the window is active, and inactive when it is not. This is the default.
* `active` - The backdrop should always appear active.
* `inactive` - The backdrop should always appear inactive.
* `titleBarStyle` string (optional) _macOS_ _Windows_ - The style of window title bar.
* `titleBarStyle` string (optional) - The style of window title bar.
Default is `default`. Possible values are:
* `default` - Results in the standard title bar for macOS or Windows respectively.
* `hidden` - Results in a hidden title bar and a full size content window. On macOS, the window still has the standard window controls (“traffic lights”) in the top left. On Windows, when combined with `titleBarOverlay: true` it will activate the Window Controls Overlay (see `titleBarOverlay` for more information), otherwise no window controls will be shown.
* `hiddenInset` _macOS_ - Only on macOS, results in a hidden title bar
* `hidden` - Results in a hidden title bar and a full size content window. On macOS, the window still has the standard window controls (“traffic lights”) in the top left. On Windows and Linux, when combined with `titleBarOverlay: true` it will activate the Window Controls Overlay (see `titleBarOverlay` for more information), otherwise no window controls will be shown.
* `hiddenInset` _macOS_ - Results in a hidden title bar
with an alternative look where the traffic light buttons are slightly
more inset from the window edge.
* `customButtonsOnHover` _macOS_ - Only on macOS, results in a hidden
* `customButtonsOnHover` _macOS_ - Results in a hidden
title bar and a full size content window, the traffic light buttons will
display when being hovered over in the top left of the window.
**Note:** This option is currently experimental.

View File

@@ -3,9 +3,9 @@
* `webPreferences` [WebPreferences](web-preferences.md?inline) (optional) - Settings of web page's features.
* `paintWhenInitiallyHidden` boolean (optional) - Whether the renderer should be active when `show` is `false` and it has just been created. In order for `document.visibilityState` to work correctly on first load with `show: false` you should set this to `false`. Setting this to `false` will cause the `ready-to-show` event to not fire. Default is `true`.
* `titleBarOverlay` Object | Boolean (optional) - When using a frameless window in conjunction with `win.setWindowButtonVisibility(true)` on macOS or using a `titleBarStyle` so that the standard window controls ("traffic lights" on macOS) are visible, this property enables the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and [CSS Environment Variables][overlay-css-env-vars]. Specifying `true` will result in an overlay with default system colors. Default is `false`.
* `color` String (optional) _Windows_ - The CSS color of the Window Controls Overlay when enabled. Default is the system color.
* `color` String (optional) _Windows_ _Linux_ - The CSS color of the Window Controls Overlay when enabled. Default is the system color.
* `symbolColor` String (optional) _Windows_ - The CSS color of the symbols on the Window Controls Overlay when enabled. Default is the system color.
* `height` Integer (optional) _macOS_ _Windows_ - The height of the title bar and Window Controls Overlay in pixels. Default is system height.
* `height` Integer (optional) - The height of the title bar and Window Controls Overlay in pixels. Default is system height.
[overlay-css-env-vars]: https://github.com/WICG/window-controls-overlay/blob/main/explainer.md#css-environment-variables
[overlay-javascript-apis]: https://github.com/WICG/window-controls-overlay/blob/main/explainer.md#javascript-apis

View File

@@ -91,7 +91,7 @@ win.setWindowButtonVisibility(false)
> combining `frame: false` with `win.setWindowButtonVisibility(true)` will yield the same
> layout outcome as setting `titleBarStyle: 'hidden'`.
## Window Controls Overlay _macOS_ _Windows_
## Window Controls Overlay
The [Window Controls Overlay API][] is a web standard that gives web apps the ability to
customize their title bar region when installed on desktop. Electron exposes this API
@@ -115,12 +115,11 @@ const win = new BrowserWindow({
})
```
On either platform `titleBarOverlay` can also be an object. On both macOS and Windows, the height of the overlay can be specified with the `height` property. On Windows, the color of the overlay and its symbols can be specified using the `color` and `symbolColor` properties respectively. `rgba()`, `hsla()`, and `#RRGGBBAA` color formats are supported to apply transparency.
On either platform `titleBarOverlay` can also be an object. The height of the overlay can be specified with the `height` property. On Windows and Linux, the color of the overlay and can be specified using the `color` property. On Windows and Linux, the color of the overlay and its symbols can be specified using the `color` and `symbolColor` properties respectively. The `rgba()`, `hsla()`, and `#RRGGBBAA` color formats are supported to apply transparency.
If a color option is not specified, the color will default to its system color for the window control buttons. Similarly, if the height option is not specified it will default to the default height:
```js title='main.js'
// on Windows
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({
titleBarStyle: 'hidden',

View File

@@ -44,6 +44,10 @@ filenames = {
"shell/browser/ui/status_icon_gtk.h",
"shell/browser/ui/tray_icon_linux.cc",
"shell/browser/ui/tray_icon_linux.h",
"shell/browser/ui/views/opaque_frame_view.cc",
"shell/browser/ui/views/opaque_frame_view.h",
"shell/browser/ui/views/caption_button_placeholder_container.cc",
"shell/browser/ui/views/caption_button_placeholder_container.h",
"shell/browser/ui/views/client_frame_view_linux.cc",
"shell/browser/ui/views/client_frame_view_linux.h",
"shell/common/application_info_linux.cc",

View File

@@ -130,3 +130,4 @@ fix_font_face_resolution_when_renderer_is_blocked.patch
feat_enable_passing_exit_code_on_service_process_crash.patch
chore_remove_reference_to_chrome_browser_themes.patch
x11_use_localized_display_label_only_for_browser_process.patch
feat_enable_customizing_symbol_color_in_framecaptionbutton.patch

View File

@@ -8,6 +8,9 @@ This CL adds support for the following features to //shell_dialogs:
* showHiddenFiles - Show hidden files in dialog.
* showOverwriteConfirmation - Whether the user will be presented a confirmation dialog if the user types a file name that already exists.
It also:
* Changes XDG Portal implementation behavior to set default path regardless of dialog type.
This may be partially upstreamed to Chromium in the future.
diff --git a/ui/gtk/select_file_dialog_linux_gtk.cc b/ui/gtk/select_file_dialog_linux_gtk.cc
@@ -269,7 +272,7 @@ index c79fb47bfba9233da7d2c1438d1e26600684fc78..d7cc7cd70653aaa5b628ef456dcb48a2
&SelectFileDialogLinuxKde::OnSelectSingleFolderDialogResponse, this,
parent, params));
diff --git a/ui/shell_dialogs/select_file_dialog_linux_portal.cc b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
index 65727489ddecb755eeabbd194ce843ca9eaa59c9..b15bace56639d2f914f8f76edfa1b28e33165b48 100644
index 65727489ddecb755eeabbd194ce843ca9eaa59c9..38134183309f89b76e7d2a8cda0a11f530b79d44 100644
--- a/ui/shell_dialogs/select_file_dialog_linux_portal.cc
+++ b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
@@ -219,6 +219,10 @@ void SelectFileDialogLinuxPortal::SelectFileImpl(
@@ -360,7 +363,7 @@ index 65727489ddecb755eeabbd194ce843ca9eaa59c9..b15bace56639d2f914f8f76edfa1b28e
IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON));
}
@@ -563,6 +572,7 @@ void SelectFileDialogLinuxPortal::DialogInfo::AppendOptions(
@@ -563,12 +572,12 @@ void SelectFileDialogLinuxPortal::DialogInfo::AppendOptions(
type == SelectFileDialog::Type::SELECT_UPLOAD_FOLDER ||
type == SelectFileDialog::Type::SELECT_EXISTING_FOLDER) {
AppendBoolOption(&options_writer, kFileChooserOptionDirectory, true);
@@ -368,6 +371,13 @@ index 65727489ddecb755eeabbd194ce843ca9eaa59c9..b15bace56639d2f914f8f76edfa1b28e
} else if (type == SelectFileDialog::Type::SELECT_OPEN_MULTI_FILE) {
AppendBoolOption(&options_writer, kFileChooserOptionMultiple, true);
}
- if (type == SelectFileDialog::Type::SELECT_SAVEAS_FILE &&
- !default_path.empty()) {
+ if (!default_path.empty()) {
if (default_path_exists) {
// If this is an existing directory, navigate to that directory, with no
// filename.
diff --git a/ui/shell_dialogs/select_file_dialog_linux_portal.h b/ui/shell_dialogs/select_file_dialog_linux_portal.h
index c487f7da19e2d05696a8eb72f2fa3e12972149f3..02a40c571570974dcc61e1b1f7ed95fbfc2bedf2 100644
--- a/ui/shell_dialogs/select_file_dialog_linux_portal.h

View File

@@ -0,0 +1,80 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shelley Vohr <shelley.vohr@gmail.com>
Date: Fri, 5 Apr 2024 11:07:22 +0200
Subject: feat: enable customizing symbol color in FrameCaptionButton
This enables customizing the symbol color on a given FrameCaptionButton
for the Window Controls Overlay API on Linux. By default, the symbol color
is dynamically calculated based on the background color of the button to
ensure it has minimum contrast required to be accessible.
This should be upstreamed to Chromium if possible.
diff --git a/ui/views/window/frame_caption_button.cc b/ui/views/window/frame_caption_button.cc
index 73e6020e3b9b6e0d12a8dea991f189b3ddeab14c..b38e5bd1408c26cbbfc995fc2ac5dc5983cc0db7 100644
--- a/ui/views/window/frame_caption_button.cc
+++ b/ui/views/window/frame_caption_button.cc
@@ -107,7 +107,7 @@ FrameCaptionButton::FrameCaptionButton(PressedCallback callback,
FrameCaptionButton::~FrameCaptionButton() = default;
// static
-SkColor FrameCaptionButton::GetButtonColor(SkColor background_color) {
+SkColor FrameCaptionButton::GetAccessibleButtonColor(SkColor background_color) {
// Use IsDark() to change target colors instead of PickContrastingColor(), so
// that DefaultFrameHeader::GetTitleColor() (which uses different target
// colors) can change between light/dark targets at the same time. It looks
@@ -124,6 +124,22 @@ SkColor FrameCaptionButton::GetButtonColor(SkColor background_color) {
.color;
}
+SkColor FrameCaptionButton::GetButtonColor(SkColor background_color) {
+ // If the button color has been overridden, return that.
+ if (button_color_ != SkColor())
+ return button_color_;
+
+ return GetAccessibleButtonColor(background_color);
+}
+
+void FrameCaptionButton::SetButtonColor(SkColor button_color) {
+ if (button_color_ == button_color)
+ return;
+
+ button_color_ = button_color;
+ MaybeRefreshIconAndInkdropBaseColor();
+}
+
// static
float FrameCaptionButton::GetInactiveButtonColorAlphaRatio() {
return 0.38f;
diff --git a/ui/views/window/frame_caption_button.h b/ui/views/window/frame_caption_button.h
index 0ac923a3ca6052d499ed7c1a4f156b0f19ad4e64..3164f79828218d57843eba823e0f14ff456b2df4 100644
--- a/ui/views/window/frame_caption_button.h
+++ b/ui/views/window/frame_caption_button.h
@@ -44,8 +44,18 @@ class VIEWS_EXPORT FrameCaptionButton : public Button {
FrameCaptionButton& operator=(const FrameCaptionButton&) = delete;
~FrameCaptionButton() override;
+ // Gets the color to use for a frame caption button with accessible contrast
+ // to the given background color.
+ static SkColor GetAccessibleButtonColor(SkColor background_color);
+
// Gets the color to use for a frame caption button.
- static SkColor GetButtonColor(SkColor background_color);
+ SkColor GetButtonColor(SkColor background_color);
+
+ // Sets the color to use for a frame caption button.
+ // The color is by default calculated to be an accessible contrast
+ // to the background color, so you should keep that in mind when
+ // overriding that behavior.
+ void SetButtonColor(SkColor button_color);
// Gets the alpha ratio for the colors of inactive frame caption buttons.
static float GetInactiveButtonColorAlphaRatio();
@@ -134,6 +144,7 @@ class VIEWS_EXPORT FrameCaptionButton : public Button {
// TODO(b/292154873): Store the foreground color instead of the background
// color for the SkColor type.
absl::variant<ui::ColorId, SkColor> color_ = gfx::kPlaceholderColor;
+ SkColor button_color_ = SkColor();
// Whether the button should be painted as active.
bool paint_as_active_ = false;

View File

@@ -13,7 +13,6 @@ fix_account_for_debugger_agent_race_condition.patch
fix_readbarrier_undefined_symbol_error_on_woa_arm64.patch
fix_suppress_clang_-wdeprecated-declarations_in_libuv.patch
fix_serdes_test.patch
feat_add_knostartdebugsignalhandler_to_environment_to_prevent.patch
feat_add_uv_loop_interrupt_on_io_change_option_to_uv_loop_configure.patch
support_v8_sandboxed_pointers.patch
build_ensure_native_module_compilation_fails_if_not_using_a_new.patch
@@ -33,7 +32,6 @@ fix_assert_module_in_the_renderer_process.patch
fix_add_trusted_space_and_trusted_lo_space_to_the_v8_heap.patch
win_process_avoid_assert_after_spawning_store_app_4152.patch
chore_remove_use_of_deprecated_kmaxlength.patch
feat_optionally_prevent_calling_v8_enablewebassemblytraphandler.patch
src_update_default_v8_platform_to_override_functions_with_location.patch
fix_capture_embedder_exceptions_before_entering_v8.patch
spec_add_iterator_to_global_intrinsics.patch

View File

@@ -40,10 +40,10 @@ index 9a27610992612b59eadcc8e481683cf1e6a5dbb6..1fad20645d059ad5f3efaa9279f582f9
# list in v8/BUILD.gn.
['v8_enable_v8_checks == 1', {
diff --git a/configure.py b/configure.py
index 11c0df455451d4ab4bea066a8361246cbf10bf1d..c30052f02f97df78e0d9294784cb3efb3fd2d1a7 100755
index f189ba2bf09fc248b3c1ce3c3535b939a4160ad2..2c24db93618f88f5f07e95a3edf63fb7942ac009 100755
--- a/configure.py
+++ b/configure.py
@@ -1501,6 +1501,7 @@ def configure_library(lib, output, pkgname=None):
@@ -1559,6 +1559,7 @@ def configure_library(lib, output, pkgname=None):
def configure_v8(o):

View File

@@ -22,10 +22,10 @@ index 1fad20645d059ad5f3efaa9279f582f99cb1006b..4fbb33550e8a044b92997a42eb6603f1
['target_arch in "ppc64 s390x"', {
'v8_enable_backtrace': 1,
diff --git a/configure.py b/configure.py
index c30052f02f97df78e0d9294784cb3efb3fd2d1a7..5fb89e56fde52d01b3806ec4e8549cbeb5ffca66 100755
index 2c24db93618f88f5f07e95a3edf63fb7942ac009..4e6dc7378e4435b90246eb072379cea1bfdad09c 100755
--- a/configure.py
+++ b/configure.py
@@ -1515,6 +1515,7 @@ def configure_v8(o):
@@ -1573,6 +1573,7 @@ def configure_v8(o):
o['variables']['v8_use_siphash'] = 0 if options.without_siphash else 1
o['variables']['v8_enable_maglev'] = 1 if options.v8_enable_maglev else 0
o['variables']['v8_enable_pointer_compression'] = 1 if options.enable_pointer_compression else 0

View File

@@ -1,69 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samuel Attard <samuel.r.attard@gmail.com>
Date: Mon, 7 Mar 2022 16:36:28 -0800
Subject: feat: add kNoStartDebugSignalHandler to Environment to prevent
SIGUSR1 handling
This patch should be upstreamed, it allows embedders to prevent the call to StartDebugSignalHandler which handles SIGUSR1 and starts the inspector agent. Apps that have --inspect disabled also don't want SIGUSR1 to have this affect.
diff --git a/src/env-inl.h b/src/env-inl.h
index 099e7352e68af6179f143741c626d8f539b568bb..63ce35ba68b48a55d8150395304bf86c2bf23aae 100644
--- a/src/env-inl.h
+++ b/src/env-inl.h
@@ -677,6 +677,10 @@ inline bool Environment::no_global_search_paths() const {
!options_->global_search_paths;
}
+inline bool Environment::should_start_debug_signal_handler() const {
+ return (flags_ & EnvironmentFlags::kNoStartDebugSignalHandler) == 0;
+}
+
inline bool Environment::no_browser_globals() const {
// configure --no-browser-globals
#ifdef NODE_NO_BROWSER_GLOBALS
diff --git a/src/env.h b/src/env.h
index c0c99456471788d40218152291a32db5fbdf1101..910c69b6d1d17ef25201dbb39d3d074f4f3f011f 100644
--- a/src/env.h
+++ b/src/env.h
@@ -801,6 +801,7 @@ class Environment : public MemoryRetainer {
inline bool tracks_unmanaged_fds() const;
inline bool hide_console_windows() const;
inline bool no_global_search_paths() const;
+ inline bool should_start_debug_signal_handler() const;
inline bool no_browser_globals() const;
inline uint64_t thread_id() const;
inline worker::Worker* worker_context() const;
diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc
index 63c8ae14abb3dee3c4a628d1042c2e9e25688806..4e1eb17dd174e18a814fce6b170d75d161937ace 100644
--- a/src/inspector_agent.cc
+++ b/src/inspector_agent.cc
@@ -717,8 +717,10 @@ bool Agent::Start(const std::string& path,
StartIoThreadAsyncCallback));
uv_unref(reinterpret_cast<uv_handle_t*>(&start_io_thread_async));
start_io_thread_async.data = this;
- // Ignore failure, SIGUSR1 won't work, but that should not block node start.
- StartDebugSignalHandler();
+ if (parent_env_->should_start_debug_signal_handler()) {
+ // Ignore failure, SIGUSR1 won't work, but that should not block node start.
+ StartDebugSignalHandler();
+ }
parent_env_->AddCleanupHook([](void* data) {
Environment* env = static_cast<Environment*>(data);
diff --git a/src/node.h b/src/node.h
index 24dd1d9fe8de5993863f9fcd0ce7f65ec5460cce..c267ed1eeaa2d8b6ecd5a1563a4e9440bf0cca00 100644
--- a/src/node.h
+++ b/src/node.h
@@ -658,7 +658,11 @@ enum Flags : uint64_t {
// This control is needed by embedders who may not want to initialize the V8
// inspector in situations where one has already been created,
// e.g. Blink's in Chromium.
- kNoCreateInspector = 1 << 9
+ kNoCreateInspector = 1 << 9,
+ // Controls where or not the InspectorAgent for this Environment should
+ // call StartDebugSignalHandler. This control is needed by embedders who may
+ // not want to allow other processes to start the V8 inspector.
+ kNoStartDebugSignalHandler = 1 << 10
};
} // namespace EnvironmentFlags

View File

@@ -1,38 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shelley Vohr <shelley.vohr@gmail.com>
Date: Thu, 16 Nov 2023 16:48:10 +0100
Subject: feat: optionally prevent calling V8::EnableWebAssemblyTrapHandler
V8::EnableWebAssemblyTrapHandler can be called only once or it will
hard crash. We need to prevent Node.js calling it in the event it has
already been called.
This should be upstreamed.
diff --git a/src/node.cc b/src/node.cc
index 3e4f1b5d730865fae61f8bfbc58c14c6a59fb33a..7478ef81eb9b275eb5f45b528582e5bbd30e60ec 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -650,6 +650,7 @@ static void PlatformInit(ProcessInitializationFlags::Flags flags) {
#endif
}
#endif // defined(_WIN32)
+ if (!(flags & ProcessInitializationFlags::kNoEnableWasmTrapHandler))
V8::EnableWebAssemblyTrapHandler(false);
#endif // NODE_USE_V8_WASM_TRAP_HANDLER
}
diff --git a/src/node.h b/src/node.h
index e55256996f2c85b0ae3854cbd1b83ca88a3e22cb..76e2459eceed127e69c686f26fd5bd369b81f1af 100644
--- a/src/node.h
+++ b/src/node.h
@@ -275,6 +275,10 @@ enum Flags : uint32_t {
kNoInitializeCppgc = 1 << 13,
// Initialize the process for predictable snapshot generation.
kGeneratePredictableSnapshot = 1 << 14,
+ // Do not initialize the Web Assembly trap handler. This is used by
+ // embedders to account for the case where it may already have been
+ // initialized - calling it more than once will hard crash.
+ kNoEnableWasmTrapHandler = 1 << 15,
// Emulate the behavior of InitializeNodeWithArgs() when passing
// a flags argument to the InitializeOncePerProcess() replacement

View File

@@ -58,10 +58,10 @@ index f9d29f0065b1de63a62cfdce74a9705c22dd87d7..3f44160f1bd40fc2d4658f10edf0d0b3
}
diff --git a/src/node_options.cc b/src/node_options.cc
index dae3237abc95362db81a1f529becfac43454d709..ee8e6f342bf4956c5f186337b6c051e1d62a6dcd 100644
index 753311e15f161547be4277016efe11cc57d351db..8e98b76e29824565739010b885375d63c992bdf8 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -1301,6 +1301,11 @@ void GetEmbedderOptions(const FunctionCallbackInfo<Value>& args) {
@@ -1317,6 +1317,11 @@ void GetEmbedderOptions(const FunctionCallbackInfo<Value>& args) {
Local<Context> context = env->context();
Local<Object> ret = Object::New(isolate);

View File

@@ -549,7 +549,7 @@ index 1785f5eef3d202976666081d09850ed744d83446..e88227a215ba4f7fa196f7642ae694a5
});
diff --git a/test/parallel/test-crypto-rsa-dsa.js b/test/parallel/test-crypto-rsa-dsa.js
index ecda345989789d0745b631385e7411305c1c6375..542a0a16f3f2eba4efd31e807a3bc34b6752d798 100644
index 5f4fafdfffbf726b7cb39c472baa3df25c9794cf..73bb53b0405b20f51b13326cc70e52755c674366 100644
--- a/test/parallel/test-crypto-rsa-dsa.js
+++ b/test/parallel/test-crypto-rsa-dsa.js
@@ -28,12 +28,11 @@ const dsaPkcs8KeyPem = fixtures.readKey('dsa_private_pkcs8.pem');
@@ -577,10 +577,10 @@ index ecda345989789d0745b631385e7411305c1c6375..542a0a16f3f2eba4efd31e807a3bc34b
- if (padding === constants.RSA_PKCS1_PADDING) {
+ // BoringSSL does not support RSA_PKCS1_PADDING.
+ if (false) {
// TODO(richardlau): see if it's possible to determine implicit rejection
// support when dynamically linked against OpenSSL.
if (!process.config.variables.node_shared_openssl) {
@@ -419,7 +419,7 @@ assert.throws(() => {
assert.throws(() => {
crypto.privateDecrypt({
@@ -466,7 +466,7 @@ assert.throws(() => {
assert.strictEqual(verify2.verify(publicKey, signature, 'hex'), true);
}
@@ -589,7 +589,7 @@ index ecda345989789d0745b631385e7411305c1c6375..542a0a16f3f2eba4efd31e807a3bc34b
//
// Test DSA signing and verification
//
@@ -494,3 +494,4 @@ const input = 'I AM THE WALRUS';
@@ -541,3 +541,4 @@ const input = 'I AM THE WALRUS';
assert.strictEqual(verify.verify(dsaPubPem, signature, 'hex'), true);
}

View File

@@ -22,7 +22,7 @@ index cdc2f7aaa8efd8b5d9090455c92d81b2bef460b7..12414204361e7227f7f7736a07388ade
int thread_pool_size,
node::tracing::TracingController* tracing_controller) {
diff --git a/src/node.h b/src/node.h
index 58c021f67e92c3fdcb4b173c00acae0722994222..24dd1d9fe8de5993863f9fcd0ce7f65ec5460cce 100644
index 7047a667f7f1b2a09aa409b2933aad4b3a4af929..c267ed1eeaa2d8b6ecd5a1563a4e9440bf0cca00 100644
--- a/src/node.h
+++ b/src/node.h
@@ -133,6 +133,7 @@ struct SnapshotData;
@@ -33,7 +33,7 @@ index 58c021f67e92c3fdcb4b173c00acae0722994222..24dd1d9fe8de5993863f9fcd0ce7f65e
class TracingController;
}
@@ -824,6 +825,8 @@ NODE_EXTERN void GetNodeReport(Environment* env,
@@ -828,6 +829,8 @@ NODE_EXTERN void GetNodeReport(Environment* env,
NODE_EXTERN MultiIsolatePlatform* GetMultiIsolatePlatform(Environment* env);
NODE_EXTERN MultiIsolatePlatform* GetMultiIsolatePlatform(IsolateData* env);

View File

@@ -48,7 +48,7 @@ index 67cd4f2adf15e7d8511f561c54163b1842e971af..7e0e1a62289289b8362870ba4869c974
const EVP_MD* digest = nullptr;
diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc
index 3517c39ad0b71a4d3efefeee2a3d55b70494bc1d..7164e539880e6d33ee1ea1fb77bae8adf4095708 100644
index ee1c7931a5c83eec00fe05807ddb97572fe70cc9..8e297e57fdbc9fd42beb6e4a33cc91b9dd7316b8 100644
--- a/src/crypto/crypto_common.cc
+++ b/src/crypto/crypto_common.cc
@@ -158,7 +158,7 @@ const char* GetClientHelloALPN(const SSLPointer& ssl) {
@@ -77,7 +77,7 @@ index 3517c39ad0b71a4d3efefeee2a3d55b70494bc1d..7164e539880e6d33ee1ea1fb77bae8ad
if (!SSL_client_hello_get0_ext(
ssl.get(),
TLSEXT_TYPE_server_name,
@@ -199,15 +201,20 @@ const char* GetClientHelloServerName(const SSLPointer& ssl) {
@@ -199,6 +201,8 @@ const char* GetClientHelloServerName(const SSLPointer& ssl) {
if (len + 2 > rem)
return nullptr;
return reinterpret_cast<const char*>(buf + 5);
@@ -86,22 +86,18 @@ index 3517c39ad0b71a4d3efefeee2a3d55b70494bc1d..7164e539880e6d33ee1ea1fb77bae8ad
}
const char* GetServerName(SSL* ssl) {
return SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
@@ -206,7 +210,10 @@ const char* GetServerName(SSL* ssl) {
}
-bool SetGroups(SecureContext* sc, const char* groups) {
- return SSL_CTX_set1_groups_list(sc->ctx().get(), groups) == 1;
-}
+ bool SetGroups(SecureContext* sc, const char* groups) {
bool SetGroups(SecureContext* sc, const char* groups) {
+#ifndef OPENSSL_IS_BORINGSSL
+ return SSL_CTX_set1_groups_list(sc->ctx().get(), groups) == 1;
return SSL_CTX_set1_groups_list(sc->ctx().get(), groups) == 1;
+#endif
+ return SSL_CTX_set1_curves_list(sc->ctx().get(), groups) == 1;
+ }
}
const char* X509ErrorCode(long err) { // NOLINT(runtime/int)
const char* code = "UNSPECIFIED";
@@ -1042,14 +1049,14 @@ MaybeLocal<Array> GetClientHelloCiphers(
// When adding or removing errors below, please also update the list in the API
@@ -1044,14 +1051,14 @@ MaybeLocal<Array> GetClientHelloCiphers(
Environment* env,
const SSLPointer& ssl) {
EscapableHandleScope scope(env->isolate());
@@ -375,7 +371,7 @@ index 5734d8fdc5505e1586f571c19b840bd56e9c9f1f..3034b114e081e2b32dd5b71653927a41
} // namespace
diff --git a/src/env.h b/src/env.h
index 904dda4caf9695acec72bd95ba235b92b07eabe2..c0c99456471788d40218152291a32db5fbdf1101 100644
index 3b3724d6c7156b87555be31470e75b1cf28b5e3f..910c69b6d1d17ef25201dbb39d3d074f4f3f011f 100644
--- a/src/env.h
+++ b/src/env.h
@@ -49,7 +49,7 @@
@@ -387,7 +383,7 @@ index 904dda4caf9695acec72bd95ba235b92b07eabe2..c0c99456471788d40218152291a32db5
#include <openssl/evp.h>
#endif
@@ -1035,7 +1035,7 @@ class Environment : public MemoryRetainer {
@@ -1036,7 +1036,7 @@ class Environment : public MemoryRetainer {
kExitInfoFieldCount
};
@@ -423,7 +419,7 @@ index cf051585e779e2b03bd7b95fe5008b89cc7f8162..9de49c6828468fdf846dcd4ad445390f
#if NODE_OPENSSL_HAS_QUIC
#include <openssl/quic.h>
diff --git a/src/node_options.cc b/src/node_options.cc
index 937ce44696175dc80edabcd9fc2709a6716d1a66..dae3237abc95362db81a1f529becfac43454d709 100644
index 7110b4d984b72fa8c9bef2cbe6e37b1871e14d08..753311e15f161547be4277016efe11cc57d351db 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -6,7 +6,7 @@
@@ -436,7 +432,7 @@ index 937ce44696175dc80edabcd9fc2709a6716d1a66..dae3237abc95362db81a1f529becfac4
#endif
diff --git a/src/node_options.h b/src/node_options.h
index 0a2f5512f689f87b73aa582782c484f73a4ebc43..e1985f2188eec6869521aecd39d2d74d5cc1616a 100644
index 3c67c3680b045786dafb8435f5b311c3f386a943..546c3979e2c8d7498aa92df4c89ee867c6485080 100644
--- a/src/node_options.h
+++ b/src/node_options.h
@@ -11,7 +11,7 @@

View File

@@ -142,7 +142,7 @@ index 30f6abd5d2d3fb7aceaa6191ebd99642e34d9ee4..cd5f59a5c27afd74f68c920a6ab5b8bc
const encodedSepRegEx = /%2F|%5C/i;
diff --git a/src/node_file.cc b/src/node_file.cc
index 0ec5c6f4845e7b1f279caabca43a06ab41193d00..1e66de2fb7860a0d0dcab6c7bded90a048886cf8 100644
index b0aa53420c4efb45ccb129cd4db5579de9f3e401..3e3aa383f54f45ab6dd134be29a2dbcf35760163 100644
--- a/src/node_file.cc
+++ b/src/node_file.cc
@@ -19,14 +19,11 @@

View File

@@ -104,10 +104,10 @@ index 3f82db324d406e342abee23ab0d7f7c9807ff652..91ee97c0d1308e650730b9977facb924
SetMethod(isolate, target, "getCacheUsage", BuiltinLoader::GetCacheUsage);
diff --git a/src/node_external_reference.h b/src/node_external_reference.h
index 17c0b2d7e1a440b5280a090ad043858faf99f009..24f0c6771720d8a6cdb684a0d562d01564d433f7 100644
index a3317d25ad6a963751073287dba71cc766ada2a2..9238f2d4d7376b22e264dbc9359b480937d29676 100644
--- a/src/node_external_reference.h
+++ b/src/node_external_reference.h
@@ -63,8 +63,6 @@ class ExternalReferenceRegistry {
@@ -57,8 +57,6 @@ class ExternalReferenceRegistry {
V(CFunctionWithBool) \
V(const v8::CFunctionInfo*) \
V(v8::FunctionCallback) \

View File

@@ -52,7 +52,7 @@ index 63ce35ba68b48a55d8150395304bf86c2bf23aae..c6fc53d1666ae51e69257c9bbcaf4cbf
return &(wrapper_data_->cppgc_id);
}
diff --git a/src/env.cc b/src/env.cc
index 5c75e5b99d9415163640ab90f5891c0fee067a88..17b389ae3e1eea52d02b3f3ffdf67fd489e6bf42 100644
index 7bd5edf61c339daa1e1b0df8e2e91680948641de..b75f3d11054a656ac0acadea27cd183d71b5d90f 100644
--- a/src/env.cc
+++ b/src/env.cc
@@ -22,6 +22,7 @@
@@ -161,10 +161,10 @@ index 910c69b6d1d17ef25201dbb39d3d074f4f3f011f..5fd375f143dd529c83aca59348182cce
inline MultiIsolatePlatform* platform() const;
inline const SnapshotData* snapshot_data() const;
diff --git a/src/node.h b/src/node.h
index 76e2459eceed127e69c686f26fd5bd369b81f1af..37f310cbda4b69857ff505ea25c99e43790deeb6 100644
index e55256996f2c85b0ae3854cbd1b83ca88a3e22cb..5aa647a095ed965a3f7e755947be4948c75651d6 100644
--- a/src/node.h
+++ b/src/node.h
@@ -1561,24 +1561,14 @@ void RegisterSignalHandler(int signal,
@@ -1557,24 +1557,14 @@ void RegisterSignalHandler(int signal,
bool reset_handler = false);
#endif // _WIN32

View File

@@ -7,7 +7,7 @@ Instead of disabling the tests, flag them as flaky so they still run
but don't cause CI failures on flakes.
diff --git a/test/parallel/parallel.status b/test/parallel/parallel.status
index 54de053b4046b283fd4bb5b74531723dfa52dfce..762c15fa661314095a1352eeac07bef544883585 100644
index 8840bd004ba87aa4a310b381310bb9612b96d861..892980bb90e433b16756f33c554b299ecc185eb4 100644
--- a/test/parallel/parallel.status
+++ b/test/parallel/parallel.status
@@ -5,6 +5,16 @@ prefix parallel

View File

@@ -2,9 +2,6 @@
const { BlobServiceClient } = require('@azure/storage-blob');
const path = require('node:path');
// TODO(vertedinde): This variable is a test variable in GHA, sending test
// artifacts to a test account. Change to the real electron artifacts
// storage account when ready.
const { ELECTRON_ARTIFACTS_BLOB_STORAGE } = process.env;
if (!ELECTRON_ARTIFACTS_BLOB_STORAGE) {
console.error('Missing required ELECTRON_ARTIFACTS_BLOB_STORAGE environment variable.');

View File

@@ -162,5 +162,6 @@
"sequential/test-single-executable-application-snapshot-and-code-cache",
"sequential/test-single-executable-application-use-code-cache",
"sequential/test-tls-connect",
"wpt/test-webcrypto"
"wpt/test-webcrypto",
"wasm-allocation/test-wasm-allocation"
]

View File

@@ -9,11 +9,9 @@ const octokit = new Octokit({
});
const BUILD_APPVEYOR_URL = 'https://ci.appveyor.com/api/builds';
const CIRCLECI_PIPELINE_URL = 'https://circleci.com/api/v2/project/gh/electron/electron/pipeline';
const GH_ACTIONS_PIPELINE_URL = 'https://github.com/electron/electron/actions';
const GH_ACTIONS_API_URL = '/repos/electron/electron/actions';
const CIRCLECI_WAIT_TIME = process.env.CIRCLECI_WAIT_TIME || 30000;
const GH_ACTIONS_WAIT_TIME = process.env.GH_ACTIONS_WAIT_TIME || 30000;
const appVeyorJobs = {
@@ -22,16 +20,6 @@ const appVeyorJobs = {
'electron-woa': 'electron-woa-release'
};
const circleCIPublishWorkflows = [
'linux-publish',
'macos-publish'
];
const circleCIPublishIndividualArches = {
'macos-publish': ['osx-x64', 'mas-x64', 'osx-arm64', 'mas-arm64'],
'linux-publish': ['arm', 'arm64', 'x64']
};
const ghActionsPublishWorkflows = [
'linux-publish',
'macos-publish'
@@ -121,120 +109,6 @@ async function githubActionsCall (targetBranch, workflowName, options) {
}
}
async function circleCIcall (targetBranch, workflowName, options) {
console.log(`Triggering CircleCI to run build job: ${workflowName} on branch: ${targetBranch} with release flag.`);
const buildRequest = {
branch: targetBranch,
parameters: {}
};
if (options.ghRelease) {
buildRequest.parameters['upload-to-storage'] = '0';
} else {
buildRequest.parameters['upload-to-storage'] = '1';
}
buildRequest.parameters[`run-${workflowName}`] = true;
if (options.arch) {
const validArches = circleCIPublishIndividualArches[workflowName];
assert(validArches.includes(options.arch), `Unknown CircleCI architecture "${options.arch}". Valid values are ${JSON.stringify(validArches)}`);
buildRequest.parameters['macos-publish-arch-limit'] = options.arch;
}
jobRequestedCount++;
// The logic below expects that the CircleCI workflows for releases each
// contain only one job in order to maintain compatibility with sudowoodo.
// If the workflows are changed in the CircleCI config.yml, this logic will
// also need to be changed as well as possibly changing sudowoodo.
try {
const circleResponse = await circleCIRequest(CIRCLECI_PIPELINE_URL, 'POST', buildRequest);
console.log(`CircleCI release build pipeline ${circleResponse.id} for ${workflowName} triggered.`);
const workflowId = await getCircleCIWorkflowId(circleResponse.id);
if (workflowId === -1) {
return;
}
const workFlowUrl = `https://circleci.com/workflow-run/${workflowId}`;
if (options.runningPublishWorkflows) {
console.log(`CircleCI release workflow request for ${workflowName} successful. Check ${workFlowUrl} for status.`);
} else {
console.log(`CircleCI release build workflow running at https://circleci.com/workflow-run/${workflowId} for ${workflowName}.`);
const jobNumber = await getCircleCIJobNumber(workflowId);
if (jobNumber === -1) {
return;
}
const jobUrl = `https://circleci.com/gh/electron/electron/${jobNumber}`;
console.log(`CircleCI release build request for ${workflowName} successful. Check ${jobUrl} for status.`);
}
} catch (err) {
console.log('Error calling CircleCI: ', err);
}
}
async function getCircleCIWorkflowId (pipelineId) {
const pipelineInfoUrl = `https://circleci.com/api/v2/pipeline/${pipelineId}`;
let workflowId = 0;
while (workflowId === 0) {
const pipelineInfo = await circleCIRequest(pipelineInfoUrl, 'GET');
switch (pipelineInfo.state) {
case 'created': {
const workflows = await circleCIRequest(`${pipelineInfoUrl}/workflow`, 'GET');
// The logic below expects three workflow.items: publish, lint, & setup
if (workflows.items.length === 3) {
workflowId = workflows.items.find(item => item.name.includes('publish')).id;
break;
}
console.log('Unexpected number of workflows, response was:', workflows);
workflowId = -1;
break;
}
case 'error': {
console.log('Error retrieving workflows, response was:', pipelineInfo);
workflowId = -1;
break;
}
}
await new Promise(resolve => setTimeout(resolve, CIRCLECI_WAIT_TIME));
}
return workflowId;
}
async function getCircleCIJobNumber (workflowId) {
const jobInfoUrl = `https://circleci.com/api/v2/workflow/${workflowId}/job`;
let jobNumber = 0;
while (jobNumber === 0) {
const jobInfo = await circleCIRequest(jobInfoUrl, 'GET');
if (!jobInfo.items) {
continue;
}
if (jobInfo.items.length !== 1) {
console.log('Unexpected number of jobs, response was:', jobInfo);
jobNumber = -1;
break;
}
switch (jobInfo.items[0].status) {
case 'not_running':
case 'queued':
case 'running': {
if (jobInfo.items[0].job_number && !isNaN(jobInfo.items[0].job_number)) {
jobNumber = jobInfo.items[0].job_number;
}
break;
}
case 'canceled':
case 'error':
case 'infrastructure_fail':
case 'timedout':
case 'not_run':
case 'failed': {
console.log(`Error job returned a status of ${jobInfo.items[0].status}, response was:`, jobInfo);
jobNumber = -1;
break;
}
}
await new Promise(resolve => setTimeout(resolve, CIRCLECI_WAIT_TIME));
}
return jobNumber;
}
async function getGitHubActionsRun (workflowId, headCommit) {
let runNumber = 0;
let actionRun;
@@ -288,33 +162,6 @@ async function getGitHubActionsRun (workflowId, headCommit) {
return runNumber;
}
async function circleCIRequest (url, method, requestBody) {
const requestOpts = {
username: process.env.CIRCLE_TOKEN,
password: '',
method,
url,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
}
};
if (requestBody) {
requestOpts.body = JSON.stringify(requestBody);
}
return makeRequest(requestOpts, true).catch(err => {
if (err.response?.body) {
console.error('Could not call CircleCI: ', {
statusCode: err.response.statusCode,
body: JSON.parse(err.response.body)
});
} else {
console.error('Error calling CircleCI:', err);
}
});
}
async function callAppVeyor (targetBranch, job, options) {
console.log(`Triggering AppVeyor to run build job: ${job} on branch: ${targetBranch} with release flag.`);
const environmentVariables = {
@@ -373,19 +220,6 @@ function buildAppVeyor (targetBranch, options) {
}
}
function buildCircleCI (targetBranch, options) {
if (options.job) {
assert(circleCIPublishWorkflows.includes(options.job), `Unknown CircleCI workflow name: ${options.job}. Valid values are: ${circleCIPublishWorkflows}.`);
circleCIcall(targetBranch, options.job, options);
} else {
assert(!options.arch, 'Cannot provide a single architecture while building all workflows, please specify a single workflow via --workflow');
options.runningPublishWorkflows = true;
for (const job of circleCIPublishWorkflows) {
circleCIcall(targetBranch, job, options);
}
}
}
function buildGHActions (targetBranch, options) {
if (options.job) {
assert(ghActionsPublishWorkflows.includes(options.job), `Unknown GitHub Actions workflow name: ${options.job}. Valid values are: ${ghActionsPublishWorkflows}.`);
@@ -402,10 +236,6 @@ function buildGHActions (targetBranch, options) {
function runRelease (targetBranch, options) {
if (options.ci) {
switch (options.ci) {
case 'CircleCI': {
buildCircleCI(targetBranch, options);
break;
}
case 'GitHubActions': {
buildGHActions(targetBranch, options);
break;
@@ -420,7 +250,6 @@ function runRelease (targetBranch, options) {
}
}
} else {
buildCircleCI(targetBranch, options);
buildAppVeyor(targetBranch, options);
buildGHActions(targetBranch, options);
}
@@ -436,8 +265,8 @@ if (require.main === module) {
const targetBranch = args._[0];
if (args._.length < 1) {
console.log(`Trigger CI to build release builds of electron.
Usage: ci-release-build.js [--job=CI_JOB_NAME] [--arch=INDIVIDUAL_ARCH] [--ci=CircleCI|AppVeyor|GitHubActions]
[--ghRelease] [--circleBuildNum=xxx] [--appveyorJobId=xxx] [--commit=sha] TARGET_BRANCH
Usage: ci-release-build.js [--job=CI_JOB_NAME] [--arch=INDIVIDUAL_ARCH] [--ci=AppVeyor|GitHubActions]
[--ghRelease] [--appveyorJobId=xxx] [--commit=sha] TARGET_BRANCH
`);
process.exit(0);
}

View File

@@ -14,8 +14,7 @@ const version = process.argv[2];
const targetRepo = findRepo();
function findRepo () {
if (process.env.IS_GHA_RELEASE) return 'test-releases';
else return version.indexOf('nightly') > 0 ? 'nightlies' : 'electron';
return version.indexOf('nightly') > 0 ? 'nightlies' : 'electron';
}
async function findRelease () {

View File

@@ -127,23 +127,7 @@ async function createRelease (branchToTarget, isBeta) {
process.exit(1);
});
const ghaTestRelease = await octokit.repos.createRelease({
owner: 'electron',
repo: 'test-releases',
tag_name: newVersion,
draft: true,
name: `electron ${newVersion}`,
body: releaseBody,
prerelease: releaseIsPrelease,
target_commitish: newVersion.includes('nightly') ? 'main' : branchToTarget
}).catch(err => {
console.log(`${fail} Error creating new GHA test release: `, err);
});
console.log(`Release has been created with id: ${release.data.id}.`);
if (ghaTestRelease && ghaTestRelease.data) {
console.log(`Test Release has been created with id: ${ghaTestRelease.data.id}.`);
}
console.log(`${pass} Draft release for ${newVersion} successful.`);
}

View File

@@ -48,7 +48,6 @@ const isNightlyElectronVersion = currentElectronVersion.includes('nightly');
const targetRepo = getRepo();
function getRepo () {
if (process.env.IS_GHA_RELEASE) return 'test-releases';
return isNightlyElectronVersion ? 'nightlies' : 'electron';
}

View File

@@ -33,7 +33,6 @@ const octokit = new Octokit({
});
function getRepo () {
if (process.env.IS_GHA_RELEASE) return 'test-releases';
return pkgVersion.indexOf('nightly') > 0 ? 'nightlies' : 'electron';
}

View File

@@ -15,8 +15,7 @@ OUT_DIR = get_out_dir()
BASE_URL = 'https://electron-metadumper.herokuapp.com/?version='
version = sys.argv[1]
authToken = os.getenv('META_DUMPER_AUTH_HEADER')
AUTH_TOKEN = os.getenv('META_DUMPER_AUTH_HEADER')
def is_json(myjson):
try:
@@ -25,40 +24,55 @@ def is_json(myjson):
return False
return True
def get_content(retry_count = 5):
try:
request = Request(
BASE_URL + version,
headers={"Authorization" : authToken}
)
def get_content(version, retry_count=5):
for attempt in range(retry_count):
try:
request = Request(
BASE_URL + version,
headers={"Authorization": AUTH_TOKEN}
)
with urlopen(request) as resp:
proposed_content = resp.read()
with urlopen(request) as resp:
proposed_content = resp.read()
if is_json(proposed_content):
return proposed_content
print("bad attempt")
raise Exception("Failed to fetch valid JSON from the metadumper service")
except Exception as e:
if retry_count == 0:
raise e
return get_content(retry_count - 1)
if is_json(proposed_content):
return proposed_content
print("Received content is not valid JSON.")
if attempt == retry_count - 1:
return None
except Exception as e:
print(f"Attempt {attempt + 1} failed: {e}")
if attempt == retry_count - 1:
return None
if attempt < retry_count - 1:
print("Retrying...")
return None
def main():
if not authToken or authToken == "":
if not AUTH_TOKEN or AUTH_TOKEN == "":
raise Exception("Please set META_DUMPER_AUTH_HEADER")
# Upload the index.json.
if len(sys.argv) < 2 or not sys.argv[1]:
raise Exception("Version is required")
version = sys.argv[1]
with scoped_cwd(ELECTRON_DIR):
safe_mkdir(OUT_DIR)
index_json = os.path.relpath(os.path.join(OUT_DIR, 'index.json'))
new_content = get_content(version)
new_content = get_content()
if new_content is None:
raise Exception("Failed to fetch valid JSON after maximum retries.")
with open(index_json, "wb") as f:
f.write(new_content)
store_artifact(OUT_DIR, 'headers/dist', [index_json])
if __name__ == '__main__':
sys.exit(main())

View File

@@ -44,7 +44,6 @@ const getHeaders = (filePath: string, fileName: string) => {
};
function getRepo () {
if (process.env.IS_GHA_RELEASE) return 'test-releases';
return releaseVersion.indexOf('nightly') > 0 ? 'nightlies' : 'electron';
}

View File

@@ -40,6 +40,8 @@
#include "shell/browser/ui/views/win_frame_view.h"
#include "shell/browser/ui/win/taskbar_host.h"
#include "ui/base/win/shell.h"
#elif BUILDFLAG(IS_LINUX)
#include "shell/browser/ui/views/opaque_frame_view.h"
#endif
#if BUILDFLAG(IS_WIN)
@@ -1041,11 +1043,13 @@ void BaseWindow::SetAppDetails(const gin_helper::Dictionary& options) {
relaunch_command, relaunch_display_name,
window_->GetAcceleratedWidget());
}
#endif
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
void BaseWindow::SetTitleBarOverlay(const gin_helper::Dictionary& options,
gin_helper::Arguments* args) {
// Ensure WCO is already enabled on this window
if (!window_->titlebar_overlay_enabled()) {
if (!window_->IsWindowControlsOverlayEnabled()) {
args->ThrowError("Titlebar overlay is not enabled");
return;
}
@@ -1090,13 +1094,18 @@ void BaseWindow::SetTitleBarOverlay(const gin_helper::Dictionary& options,
updated = true;
}
// If anything was updated, invalidate the layout and schedule a paint of the
// window's frame view
if (updated) {
auto* frame_view = static_cast<WinFrameView*>(
window->widget()->non_client_view()->frame_view());
frame_view->InvalidateCaptionButtons();
}
if (!updated)
return;
// If anything was updated, ensure the overlay is repainted.
#if BUILDFLAG(IS_WIN)
auto* frame_view = static_cast<WinFrameView*>(
window->widget()->non_client_view()->frame_view());
#else
auto* frame_view = static_cast<OpaqueFrameView*>(
window->widget()->non_client_view()->frame_view());
#endif
frame_view->InvalidateCaptionButtons();
}
#endif
@@ -1286,6 +1295,8 @@ void BaseWindow::BuildPrototype(v8::Isolate* isolate,
.SetMethod("setThumbnailClip", &BaseWindow::SetThumbnailClip)
.SetMethod("setThumbnailToolTip", &BaseWindow::SetThumbnailToolTip)
.SetMethod("setAppDetails", &BaseWindow::SetAppDetails)
#endif
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
.SetMethod("setTitleBarOverlay", &BaseWindow::SetTitleBarOverlay)
#endif
.SetProperty("id", &BaseWindow::GetID);

View File

@@ -241,6 +241,9 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
bool SetThumbnailClip(const gfx::Rect& region);
bool SetThumbnailToolTip(const std::string& tooltip);
void SetAppDetails(const gin_helper::Dictionary& options);
#endif
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
void SetTitleBarOverlay(const gin_helper::Dictionary& options,
gin_helper::Arguments* args);
#endif

View File

@@ -122,10 +122,6 @@ NativeWindow::NativeWindow(const gin_helper::Dictionary& options,
int height;
if (titlebar_overlay_dict.Get(options::kOverlayHeight, &height))
titlebar_overlay_height_ = height;
#if !(BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC))
DCHECK(false);
#endif
}
}

View File

@@ -372,12 +372,23 @@ class NativeWindow : public base::SupportsUserData,
kHiddenInset,
kCustomButtonsOnHover,
};
TitleBarStyle title_bar_style() const { return title_bar_style_; }
bool IsWindowControlsOverlayEnabled() const {
bool valid_titlebar_style = title_bar_style() == TitleBarStyle::kHidden
#if BUILDFLAG(IS_MAC)
||
title_bar_style() == TitleBarStyle::kHiddenInset
#endif
;
return valid_titlebar_style && titlebar_overlay_;
}
int titlebar_overlay_height() const { return titlebar_overlay_height_; }
void set_titlebar_overlay_height(int height) {
titlebar_overlay_height_ = height;
}
bool titlebar_overlay_enabled() const { return titlebar_overlay_; }
bool has_frame() const { return has_frame_; }
void set_has_frame(bool has_frame) { has_frame_ = has_frame; }

View File

@@ -26,6 +26,7 @@
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/desktop_media_id.h"
#include "content/public/common/color_parser.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/ui/inspectable_web_contents.h"
#include "shell/browser/ui/inspectable_web_contents_view.h"
@@ -59,6 +60,7 @@
#include "shell/browser/ui/views/client_frame_view_linux.h"
#include "shell/browser/ui/views/frameless_view.h"
#include "shell/browser/ui/views/native_frame_view.h"
#include "shell/browser/ui/views/opaque_frame_view.h"
#include "shell/common/platform_util.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#include "ui/views/window/native_frame_view.h"
@@ -76,7 +78,6 @@
#elif BUILDFLAG(IS_WIN)
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
#include "content/public/common/color_parser.h"
#include "shell/browser/ui/views/win_frame_view.h"
#include "shell/browser/ui/win/electron_desktop_native_widget_aura.h"
#include "skia/ext/skia_utils_win.h"
@@ -220,6 +221,7 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
overlay_button_color_ = color_utils::GetSysSkColor(COLOR_BTNFACE);
overlay_symbol_color_ = color_utils::GetSysSkColor(COLOR_BTNTEXT);
#endif
v8::Local<v8::Value> titlebar_overlay;
if (options.Get(options::ktitleBarOverlay, &titlebar_overlay) &&
@@ -245,9 +247,11 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
}
}
if (title_bar_style_ != TitleBarStyle::kNormal)
// |hidden| is the only non-default titleBarStyle valid on Windows and Linux.
if (title_bar_style_ == TitleBarStyle::kHidden)
set_has_frame(false);
#if BUILDFLAG(IS_WIN)
// If the taskbar is re-created after we start up, we have to rebuild all of
// our buttons.
taskbar_created_message_ = RegisterWindowMessage(TEXT("TaskbarCreated"));
@@ -1704,11 +1708,15 @@ NativeWindowViews::CreateNonClientFrameView(views::Widget* widget) {
if (has_frame() && !has_client_frame()) {
return std::make_unique<NativeFrameView>(this, widget);
} else {
auto frame_view = has_frame() && has_client_frame()
? std::make_unique<ClientFrameViewLinux>()
: std::make_unique<FramelessView>();
frame_view->Init(this, widget);
return frame_view;
if (has_frame() && has_client_frame()) {
auto frame_view = std::make_unique<ClientFrameViewLinux>();
frame_view->Init(this, widget);
return frame_view;
} else {
auto frame_view = std::make_unique<OpaqueFrameView>();
frame_view->Init(this, widget);
return frame_view;
}
}
#endif
}

View File

@@ -167,13 +167,9 @@ class NativeWindowViews : public NativeWindow,
#if BUILDFLAG(IS_WIN)
TaskbarHost& taskbar_host() { return taskbar_host_; }
void UpdateThickFrame();
#endif
#if BUILDFLAG(IS_WIN)
bool IsWindowControlsOverlayEnabled() const {
return (title_bar_style_ == NativeWindowViews::TitleBarStyle::kHidden) &&
titlebar_overlay_;
}
SkColor overlay_button_color() const { return overlay_button_color_; }
void set_overlay_button_color(SkColor color) {
overlay_button_color_ = color;
@@ -183,9 +179,6 @@ class NativeWindowViews : public NativeWindow,
overlay_symbol_color_ = color;
}
void UpdateThickFrame();
#endif
private:
// views::WidgetObserver:
void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
@@ -263,6 +256,10 @@ class NativeWindowViews : public NativeWindow,
std::unique_ptr<EventDisabler> event_disabler_;
#endif
// The color to use as the theme and symbol colors respectively for WCO.
SkColor overlay_button_color_ = SkColor();
SkColor overlay_symbol_color_ = SkColor();
#if BUILDFLAG(IS_WIN)
ui::WindowShowState last_window_state_;
@@ -306,11 +303,6 @@ class NativeWindowViews : public NativeWindow,
std::optional<gfx::Rect> pending_bounds_change_;
// The color to use as the theme and symbol colors respectively for Window
// Controls Overlay if enabled on Windows.
SkColor overlay_button_color_;
SkColor overlay_symbol_color_;
// The message ID of the "TaskbarCreated" message, sent to us when we need to
// reset our thumbar buttons.
UINT taskbar_created_message_ = 0;

View File

@@ -208,9 +208,6 @@ class FileChooserDialog : public ui::SelectFileDialog::Listener {
bool ShowOpenDialogSync(const DialogSettings& settings,
std::vector<base::FilePath>* paths) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
gin_helper::Promise<gin_helper::Dictionary> promise(isolate);
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
auto cb = base::BindOnce(
[](base::RepeatingClosure cb, std::vector<base::FilePath>* file_paths,
@@ -222,7 +219,6 @@ bool ShowOpenDialogSync(const DialogSettings& settings,
FileChooserDialog* dialog = new FileChooserDialog();
dialog->RunOpenDialog(std::move(cb), settings);
run_loop.Run();
return !paths->empty();
}
@@ -235,8 +231,6 @@ void ShowOpenDialog(const DialogSettings& settings,
bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
gin_helper::Promise<gin_helper::Dictionary> promise(isolate);
auto cb = base::BindOnce(
[](base::RepeatingClosure cb, base::FilePath* file_path,
gin_helper::Dictionary result) {
@@ -246,7 +240,7 @@ bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
run_loop.QuitClosure(), path);
FileChooserDialog* dialog = new FileChooserDialog();
dialog->RunSaveDialog(std::move(promise), settings);
dialog->RunSaveDialog(std::move(cb), settings);
run_loop.Run();
return !path->empty();
}

View File

@@ -0,0 +1,20 @@
// Copyright 2024 Microsoft GmbH.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shell/browser/ui/views/caption_button_placeholder_container.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/views/view.h"
CaptionButtonPlaceholderContainer::CaptionButtonPlaceholderContainer() {
SetPaintToLayer();
}
CaptionButtonPlaceholderContainer::~CaptionButtonPlaceholderContainer() =
default;
BEGIN_METADATA(CaptionButtonPlaceholderContainer)
END_METADATA

View File

@@ -0,0 +1,26 @@
// Copyright 2024 Microsoft GmbH.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_UI_VIEWS_CAPTION_BUTTON_PLACEHOLDER_CONTAINER_H_
#define ELECTRON_SHELL_BROWSER_UI_VIEWS_CAPTION_BUTTON_PLACEHOLDER_CONTAINER_H_
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/views/view.h"
// A placeholder container for control buttons with window controls
// overlay display override. Does not interact with the buttons. It is just
// used to indicate that this is non-client-area.
class CaptionButtonPlaceholderContainer : public views::View {
METADATA_HEADER(CaptionButtonPlaceholderContainer, views::View)
public:
CaptionButtonPlaceholderContainer();
CaptionButtonPlaceholderContainer(const CaptionButtonPlaceholderContainer&) =
delete;
CaptionButtonPlaceholderContainer& operator=(
const CaptionButtonPlaceholderContainer&) = delete;
~CaptionButtonPlaceholderContainer() override;
};
#endif // ELECTRON_SHELL_BROWSER_UI_VIEWS_CAPTION_BUTTON_PLACEHOLDER_CONTAINER_H_

View File

@@ -95,6 +95,8 @@ void FramelessView::ResetWindowControls() {}
void FramelessView::UpdateWindowIcon() {}
void FramelessView::InvalidateCaptionButtons() {}
void FramelessView::UpdateWindowTitle() {}
void FramelessView::SizeConstraintsChanged() {}

View File

@@ -33,6 +33,13 @@ class FramelessView : public views::NonClientFrameView {
// Returns whether the |point| is on frameless window's resizing border.
virtual int ResizingBorderHitTest(const gfx::Point& point);
// Tells the NonClientView to invalidate caption buttons
// and forces a re-layout and re-paint.
virtual void InvalidateCaptionButtons();
NativeWindowViews* window() const { return window_; }
views::Widget* frame() const { return frame_; }
protected:
// Helper function for subclasses to implement ResizingBorderHitTest with a
// custom resize inset.

View File

@@ -0,0 +1,552 @@
// Copyright (c) 2024 Microsoft GmbH.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/ui/views/opaque_frame_view.h"
#include "base/containers/adapters.h"
#include "base/i18n/rtl.h"
#include "chrome/grit/generated_resources.h"
#include "components/strings/grit/components_strings.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/inspectable_web_contents_view.h"
#include "shell/browser/ui/views/caption_button_placeholder_container.h"
#include "ui/aura/window.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/linux/linux_ui.h"
#include "ui/views/background.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/window/frame_caption_button.h"
#include "ui/views/window/vector_icons/vector_icons.h"
namespace electron {
namespace {
// These values should be the same as Chromium uses.
constexpr int kCaptionButtonHeight = 18;
bool HitTestCaptionButton(views::Button* button, const gfx::Point& point) {
return button && button->GetVisible() &&
button->GetMirroredBounds().Contains(point);
}
// The frame has a 2 px 3D edge along the top. This is overridable by
// subclasses, so RestoredFrameEdgeInsets() should be used instead of using this
// constant directly.
const int kTopFrameEdgeThickness = 2;
// The frame has a 1 px 3D edge along the side. This is overridable by
// subclasses, so RestoredFrameEdgeInsets() should be used instead of using this
// constant directly.
const int kSideFrameEdgeThickness = 1;
// The minimum vertical padding between the bottom of the caption buttons and
// the top of the content shadow.
const int kCaptionButtonBottomPadding = 3;
} // namespace
// The content edge images have a shadow built into them.
const int OpaqueFrameView::kContentEdgeShadowThickness = 2;
OpaqueFrameView::OpaqueFrameView() = default;
OpaqueFrameView::~OpaqueFrameView() = default;
void OpaqueFrameView::Init(NativeWindowViews* window, views::Widget* frame) {
FramelessView::Init(window, frame);
if (!window->IsWindowControlsOverlayEnabled())
return;
caption_button_placeholder_container_ =
AddChildView(std::make_unique<CaptionButtonPlaceholderContainer>());
minimize_button_ = CreateButton(
VIEW_ID_MINIMIZE_BUTTON, IDS_ACCNAME_MINIMIZE,
views::CAPTION_BUTTON_ICON_MINIMIZE, HTMINBUTTON,
views::kWindowControlMinimizeIcon,
base::BindRepeating(&views::Widget::Minimize, base::Unretained(frame)));
maximize_button_ = CreateButton(
VIEW_ID_MAXIMIZE_BUTTON, IDS_ACCNAME_MAXIMIZE,
views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE, HTMAXBUTTON,
views::kWindowControlMaximizeIcon,
base::BindRepeating(&views::Widget::Maximize, base::Unretained(frame)));
restore_button_ = CreateButton(
VIEW_ID_RESTORE_BUTTON, IDS_ACCNAME_RESTORE,
views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE, HTMAXBUTTON,
views::kWindowControlRestoreIcon,
base::BindRepeating(&views::Widget::Restore, base::Unretained(frame)));
close_button_ = CreateButton(
VIEW_ID_CLOSE_BUTTON, IDS_ACCNAME_CLOSE, views::CAPTION_BUTTON_ICON_CLOSE,
HTMAXBUTTON, views::kWindowControlCloseIcon,
base::BindRepeating(&views::Widget::CloseWithReason,
base::Unretained(frame),
views::Widget::ClosedReason::kCloseButtonClicked));
// Unretained() is safe because the subscription is saved into an instance
// member and thus will be cancelled upon the instance's destruction.
paint_as_active_changed_subscription_ =
frame->RegisterPaintAsActiveChangedCallback(base::BindRepeating(
&OpaqueFrameView::PaintAsActiveChanged, base::Unretained(this)));
}
int OpaqueFrameView::ResizingBorderHitTest(const gfx::Point& point) {
return FramelessView::ResizingBorderHitTest(point);
}
void OpaqueFrameView::InvalidateCaptionButtons() {
UpdateCaptionButtonPlaceholderContainerBackground();
UpdateFrameCaptionButtons();
LayoutWindowControlsOverlay();
InvalidateLayout();
}
gfx::Rect OpaqueFrameView::GetBoundsForClientView() const {
if (window()->IsWindowControlsOverlayEnabled()) {
auto border_thickness = FrameBorderInsets(false);
int top_height = border_thickness.top();
return gfx::Rect(
border_thickness.left(), top_height,
std::max(0, width() - border_thickness.width()),
std::max(0, height() - top_height - border_thickness.bottom()));
}
return FramelessView::GetBoundsForClientView();
}
gfx::Rect OpaqueFrameView::GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const {
if (window()->IsWindowControlsOverlayEnabled()) {
int top_height = NonClientTopHeight(false);
auto border_insets = FrameBorderInsets(false);
return gfx::Rect(
std::max(0, client_bounds.x() - border_insets.left()),
std::max(0, client_bounds.y() - top_height),
client_bounds.width() + border_insets.width(),
client_bounds.height() + top_height + border_insets.bottom());
}
return FramelessView::GetWindowBoundsForClientBounds(client_bounds);
}
int OpaqueFrameView::NonClientHitTest(const gfx::Point& point) {
if (window()->IsWindowControlsOverlayEnabled()) {
if (HitTestCaptionButton(close_button_, point))
return HTCLOSE;
if (HitTestCaptionButton(restore_button_, point))
return HTMAXBUTTON;
if (HitTestCaptionButton(maximize_button_, point))
return HTMAXBUTTON;
if (HitTestCaptionButton(minimize_button_, point))
return HTMINBUTTON;
if (caption_button_placeholder_container_->GetMirroredBounds().Contains(
point)) {
return HTCAPTION;
}
}
return FramelessView::NonClientHitTest(point);
}
void OpaqueFrameView::ResetWindowControls() {
NonClientFrameView::ResetWindowControls();
if (restore_button_)
restore_button_->SetState(views::Button::STATE_NORMAL);
if (minimize_button_)
minimize_button_->SetState(views::Button::STATE_NORMAL);
if (maximize_button_)
maximize_button_->SetState(views::Button::STATE_NORMAL);
// The close button isn't affected by this constraint.
}
views::View* OpaqueFrameView::TargetForRect(views::View* root,
const gfx::Rect& rect) {
return views::NonClientFrameView::TargetForRect(root, rect);
}
void OpaqueFrameView::Layout(PassKey) {
LayoutSuperclass<FramelessView>(this);
if (!window()->IsWindowControlsOverlayEnabled())
return;
// Reset all our data so that everything is invisible.
TopAreaPadding top_area_padding = GetTopAreaPadding();
available_space_leading_x_ = top_area_padding.leading;
available_space_trailing_x_ = width() - top_area_padding.trailing;
minimum_size_for_buttons_ =
available_space_leading_x_ + width() - available_space_trailing_x_;
placed_leading_button_ = false;
placed_trailing_button_ = false;
LayoutWindowControls();
int height = NonClientTopHeight(false);
auto insets = FrameBorderInsets(false);
int container_x = placed_trailing_button_ ? available_space_trailing_x_ : 0;
caption_button_placeholder_container_->SetBounds(
container_x, insets.top(), minimum_size_for_buttons_ - insets.width(),
height - insets.top());
LayoutWindowControlsOverlay();
}
void OpaqueFrameView::OnPaint(gfx::Canvas* canvas) {
if (!window()->IsWindowControlsOverlayEnabled())
return;
if (frame()->IsFullscreen())
return;
UpdateFrameCaptionButtons();
}
void OpaqueFrameView::PaintAsActiveChanged() {
if (!window()->IsWindowControlsOverlayEnabled())
return;
UpdateCaptionButtonPlaceholderContainerBackground();
UpdateFrameCaptionButtons();
}
void OpaqueFrameView::UpdateFrameCaptionButtons() {
const bool active = ShouldPaintAsActive();
const SkColor symbol_color = window()->overlay_symbol_color();
const SkColor background_color = window()->overlay_button_color();
SkColor frame_color =
background_color == SkColor() ? GetFrameColor() : background_color;
for (views::Button* button :
{minimize_button_, maximize_button_, restore_button_, close_button_}) {
DCHECK_EQ(std::string(views::FrameCaptionButton::kViewClassName),
button->GetClassName());
views::FrameCaptionButton* frame_caption_button =
static_cast<views::FrameCaptionButton*>(button);
frame_caption_button->SetPaintAsActive(active);
frame_caption_button->SetButtonColor(symbol_color);
frame_caption_button->SetBackgroundColor(frame_color);
}
}
void OpaqueFrameView::UpdateCaptionButtonPlaceholderContainerBackground() {
if (caption_button_placeholder_container_) {
const SkColor obc = window()->overlay_button_color();
const SkColor bg_color = obc == SkColor() ? GetFrameColor() : obc;
caption_button_placeholder_container_->SetBackground(
views::CreateSolidBackground(bg_color));
}
}
void OpaqueFrameView::LayoutWindowControls() {
// Keep a list of all buttons that we don't show.
std::vector<views::FrameButton> buttons_not_shown;
buttons_not_shown.push_back(views::FrameButton::kMaximize);
buttons_not_shown.push_back(views::FrameButton::kMinimize);
buttons_not_shown.push_back(views::FrameButton::kClose);
for (const auto& button : leading_buttons_) {
ConfigureButton(button, ALIGN_LEADING);
std::erase(buttons_not_shown, button);
}
for (const auto& button : base::Reversed(trailing_buttons_)) {
ConfigureButton(button, ALIGN_TRAILING);
std::erase(buttons_not_shown, button);
}
for (const auto& button_id : buttons_not_shown)
HideButton(button_id);
}
void OpaqueFrameView::LayoutWindowControlsOverlay() {
int overlay_height = window()->titlebar_overlay_height();
if (overlay_height == 0) {
// Accounting for the 1 pixel margin at the top of the button container
overlay_height =
window()->IsMaximized()
? caption_button_placeholder_container_->size().height()
: caption_button_placeholder_container_->size().height() + 1;
}
int overlay_width = caption_button_placeholder_container_->size().width();
int bounding_rect_width = width() - overlay_width;
auto bounding_rect =
GetMirroredRect(gfx::Rect(0, 0, bounding_rect_width, overlay_height));
window()->SetWindowControlsOverlayRect(bounding_rect);
window()->NotifyLayoutWindowControlsOverlay();
}
views::Button* OpaqueFrameView::CreateButton(
ViewID view_id,
int accessibility_string_id,
views::CaptionButtonIcon icon_type,
int ht_component,
const gfx::VectorIcon& icon_image,
views::Button::PressedCallback callback) {
views::FrameCaptionButton* button = new views::FrameCaptionButton(
views::Button::PressedCallback(), icon_type, ht_component);
button->SetImage(button->GetIcon(), views::FrameCaptionButton::Animate::kNo,
icon_image);
button->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
button->SetCallback(std::move(callback));
button->SetAccessibleName(l10n_util::GetStringUTF16(accessibility_string_id));
button->SetID(view_id);
AddChildView(button);
button->SetPaintToLayer();
button->layer()->SetFillsBoundsOpaquely(false);
return button;
}
gfx::Insets OpaqueFrameView::FrameBorderInsets(bool restored) const {
return !restored && IsFrameCondensed() ? gfx::Insets()
: RestoredFrameBorderInsets();
}
int OpaqueFrameView::FrameTopBorderThickness(bool restored) const {
int thickness = FrameBorderInsets(restored).top();
if ((restored || !IsFrameCondensed()) && thickness > 0)
thickness += NonClientExtraTopThickness();
return thickness;
}
OpaqueFrameView::TopAreaPadding OpaqueFrameView::GetTopAreaPadding(
bool has_leading_buttons,
bool has_trailing_buttons) const {
const auto padding = FrameBorderInsets(false);
return TopAreaPadding{padding.left(), padding.right()};
}
bool OpaqueFrameView::IsFrameCondensed() const {
return frame()->IsMaximized() || frame()->IsFullscreen();
}
gfx::Insets OpaqueFrameView::RestoredFrameBorderInsets() const {
return gfx::Insets();
}
gfx::Insets OpaqueFrameView::RestoredFrameEdgeInsets() const {
return gfx::Insets::TLBR(kTopFrameEdgeThickness, kSideFrameEdgeThickness,
kSideFrameEdgeThickness, kSideFrameEdgeThickness);
}
int OpaqueFrameView::NonClientExtraTopThickness() const {
return kNonClientExtraTopThickness;
}
int OpaqueFrameView::NonClientTopHeight(bool restored) const {
// Adding 2px of vertical padding puts at least 1 px of space on the top and
// bottom of the element.
constexpr int kVerticalPadding = 2;
const int icon_height = GetIconSize() + kVerticalPadding;
const int caption_button_height = DefaultCaptionButtonY(restored) +
kCaptionButtonHeight +
kCaptionButtonBottomPadding;
int custom_height = window()->titlebar_overlay_height();
return custom_height ? custom_height
: std::max(icon_height, caption_button_height) +
kContentEdgeShadowThickness;
}
int OpaqueFrameView::CaptionButtonY(views::FrameButton button_id,
bool restored) const {
return DefaultCaptionButtonY(restored);
}
int OpaqueFrameView::DefaultCaptionButtonY(bool restored) const {
// Maximized buttons start at window top, since the window has no border. This
// offset is for the image (the actual clickable bounds extend all the way to
// the top to take Fitts' Law into account).
const bool start_at_top_of_frame = !restored && IsFrameCondensed();
return start_at_top_of_frame
? FrameBorderInsets(false).top()
: views::NonClientFrameView::kFrameShadowThickness;
}
gfx::Insets OpaqueFrameView::FrameEdgeInsets(bool restored) const {
return RestoredFrameEdgeInsets();
}
int OpaqueFrameView::GetIconSize() const {
// The icon never shrinks below 16 px on a side.
const int kIconMinimumSize = 16;
return std::max(gfx::FontList().GetHeight(), kIconMinimumSize);
}
OpaqueFrameView::TopAreaPadding OpaqueFrameView::GetTopAreaPadding() const {
return GetTopAreaPadding(!leading_buttons_.empty(),
!trailing_buttons_.empty());
}
SkColor OpaqueFrameView::GetFrameColor() const {
return GetColorProvider()->GetColor(
ShouldPaintAsActive() ? ui::kColorFrameActive : ui::kColorFrameInactive);
}
void OpaqueFrameView::ConfigureButton(views::FrameButton button_id,
ButtonAlignment alignment) {
switch (button_id) {
case views::FrameButton::kMinimize: {
bool can_minimize = true; // delegate_->CanMinimize();
if (can_minimize) {
minimize_button_->SetVisible(true);
SetBoundsForButton(button_id, minimize_button_, alignment);
} else {
HideButton(button_id);
}
break;
}
case views::FrameButton::kMaximize: {
bool can_maximize = true; // delegate_->CanMaximize();
if (can_maximize) {
// When the window is restored, we show a maximized button; otherwise,
// we show a restore button.
bool is_restored = !window()->IsMaximized() && !window()->IsMinimized();
views::Button* invisible_button =
is_restored ? restore_button_ : maximize_button_;
invisible_button->SetVisible(false);
views::Button* visible_button =
is_restored ? maximize_button_ : restore_button_;
visible_button->SetVisible(true);
SetBoundsForButton(button_id, visible_button, alignment);
} else {
HideButton(button_id);
}
break;
}
case views::FrameButton::kClose: {
close_button_->SetVisible(true);
SetBoundsForButton(button_id, close_button_, alignment);
break;
}
}
}
void OpaqueFrameView::HideButton(views::FrameButton button_id) {
switch (button_id) {
case views::FrameButton::kMinimize:
minimize_button_->SetVisible(false);
break;
case views::FrameButton::kMaximize:
restore_button_->SetVisible(false);
maximize_button_->SetVisible(false);
break;
case views::FrameButton::kClose:
close_button_->SetVisible(false);
break;
}
}
void OpaqueFrameView::SetBoundsForButton(views::FrameButton button_id,
views::Button* button,
ButtonAlignment alignment) {
const int caption_y = CaptionButtonY(button_id, false);
// There should always be the same number of non-shadow pixels visible to the
// side of the caption buttons. In maximized mode we extend buttons to the
// screen top and the rightmost button to the screen right (or leftmost button
// to the screen left, for left-aligned buttons) to obey Fitts' Law.
const bool is_frame_condensed = IsFrameCondensed();
const int button_width = views::GetCaptionButtonWidth();
gfx::Size button_size = button->GetPreferredSize();
DCHECK_EQ(std::string(views::FrameCaptionButton::kViewClassName),
button->GetClassName());
const int caption_button_center_size =
button_width - 2 * views::kCaptionButtonInkDropDefaultCornerRadius;
const int height = GetTopAreaHeight() - FrameEdgeInsets(false).top();
const int corner_radius =
std::clamp((height - caption_button_center_size) / 2, 0,
views::kCaptionButtonInkDropDefaultCornerRadius);
button_size = gfx::Size(button_width, height);
button->SetPreferredSize(button_size);
static_cast<views::FrameCaptionButton*>(button)->SetInkDropCornerRadius(
corner_radius);
TopAreaPadding top_area_padding = GetTopAreaPadding();
switch (alignment) {
case ALIGN_LEADING: {
int extra_width = top_area_padding.leading;
int button_start_spacing =
GetWindowCaptionSpacing(button_id, true, !placed_leading_button_);
available_space_leading_x_ += button_start_spacing;
minimum_size_for_buttons_ += button_start_spacing;
bool top_spacing_clickable = is_frame_condensed;
bool start_spacing_clickable =
is_frame_condensed && !placed_leading_button_;
button->SetBounds(
available_space_leading_x_ - (start_spacing_clickable
? button_start_spacing + extra_width
: 0),
top_spacing_clickable ? 0 : caption_y,
button_size.width() + (start_spacing_clickable
? button_start_spacing + extra_width
: 0),
button_size.height() + (top_spacing_clickable ? caption_y : 0));
int button_end_spacing =
GetWindowCaptionSpacing(button_id, false, !placed_leading_button_);
available_space_leading_x_ += button_size.width() + button_end_spacing;
minimum_size_for_buttons_ += button_size.width() + button_end_spacing;
placed_leading_button_ = true;
break;
}
case ALIGN_TRAILING: {
int extra_width = top_area_padding.trailing;
int button_start_spacing =
GetWindowCaptionSpacing(button_id, true, !placed_trailing_button_);
available_space_trailing_x_ -= button_start_spacing;
minimum_size_for_buttons_ += button_start_spacing;
bool top_spacing_clickable = is_frame_condensed;
bool start_spacing_clickable =
is_frame_condensed && !placed_trailing_button_;
button->SetBounds(
available_space_trailing_x_ - button_size.width(),
top_spacing_clickable ? 0 : caption_y,
button_size.width() + (start_spacing_clickable
? button_start_spacing + extra_width
: 0),
button_size.height() + (top_spacing_clickable ? caption_y : 0));
int button_end_spacing =
GetWindowCaptionSpacing(button_id, false, !placed_trailing_button_);
available_space_trailing_x_ -= button_size.width() + button_end_spacing;
minimum_size_for_buttons_ += button_size.width() + button_end_spacing;
placed_trailing_button_ = true;
break;
}
}
}
int OpaqueFrameView::GetTopAreaHeight() const {
int top_height = NonClientTopHeight(false);
return top_height;
}
int OpaqueFrameView::GetWindowCaptionSpacing(views::FrameButton button_id,
bool leading_spacing,
bool is_leading_button) const {
return 0;
}
BEGIN_METADATA(OpaqueFrameView)
END_METADATA
} // namespace electron

View File

@@ -0,0 +1,205 @@
// Copyright 2024 Microsoft GmbH.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_UI_VIEWS_OPAQUE_FRAME_VIEW_H_
#define ELECTRON_SHELL_BROWSER_UI_VIEWS_OPAQUE_FRAME_VIEW_H_
#include <memory>
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "chrome/browser/ui/view_ids.h"
#include "shell/browser/ui/views/frameless_view.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/gfx/font_list.h"
#include "ui/linux/nav_button_provider.h"
#include "ui/linux/window_button_order_observer.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/window/caption_button_types.h"
#include "ui/views/window/frame_buttons.h"
#include "ui/views/window/non_client_view.h"
class CaptionButtonPlaceholderContainer;
namespace electron {
class OpaqueFrameView : public FramelessView {
METADATA_HEADER(OpaqueFrameView, FramelessView)
public:
// Constants used by OpaqueBrowserFrameView as well.
static const int kContentEdgeShadowThickness;
static constexpr int kNonClientExtraTopThickness = 1;
OpaqueFrameView();
~OpaqueFrameView() override;
// FramelessView:
void Init(NativeWindowViews* window, views::Widget* frame) override;
int ResizingBorderHitTest(const gfx::Point& point) override;
void InvalidateCaptionButtons() override;
// views::NonClientFrameView:
gfx::Rect GetBoundsForClientView() const override;
gfx::Rect GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const override;
int NonClientHitTest(const gfx::Point& point) override;
void ResetWindowControls() override;
views::View* TargetForRect(views::View* root, const gfx::Rect& rect) override;
// views::View:
void Layout(PassKey) override;
void OnPaint(gfx::Canvas* canvas) override;
private:
enum ButtonAlignment { ALIGN_LEADING, ALIGN_TRAILING };
struct TopAreaPadding {
int leading;
int trailing;
};
void PaintAsActiveChanged();
void UpdateCaptionButtonPlaceholderContainerBackground();
void UpdateFrameCaptionButtons();
void LayoutWindowControls();
void LayoutWindowControlsOverlay();
// Creates and returns an ImageButton with |this| as its listener.
// Memory is owned by the caller.
views::Button* CreateButton(ViewID view_id,
int accessibility_string_id,
views::CaptionButtonIcon icon_type,
int ht_component,
const gfx::VectorIcon& icon_image,
views::Button::PressedCallback callback);
/** Layout-Related Utility Functions **/
// Returns the insets from the native window edge to the client view.
// This does not include any client edge. If |restored| is true, this
// is calculated as if the window was restored, regardless of its
// current node_data.
gfx::Insets FrameBorderInsets(bool restored) const;
// Returns the thickness of the border that makes up the window frame edge
// along the top of the frame. If |restored| is true, this acts as if the
// window is restored regardless of the actual mode.
int FrameTopBorderThickness(bool restored) const;
// Returns the spacing between the edge of the browser window and the first
// frame buttons.
TopAreaPadding GetTopAreaPadding(bool has_leading_buttons,
bool has_trailing_buttons) const;
// Determines whether the top frame is condensed vertically, as when the
// window is maximized. If true, the top frame is just the height of a tab,
// rather than having extra vertical space above the tabs.
bool IsFrameCondensed() const;
// The insets from the native window edge to the client view when the window
// is restored. This goes all the way to the web contents on the left, right,
// and bottom edges.
gfx::Insets RestoredFrameBorderInsets() const;
// The insets from the native window edge to the flat portion of the
// window border. That is, this function returns the "3D portion" of the
// border when the window is restored. The returned insets will not be larger
// than RestoredFrameBorderInsets().
gfx::Insets RestoredFrameEdgeInsets() const;
// Additional vertical padding between tabs and the top edge of the window
// when the window is restored.
int NonClientExtraTopThickness() const;
// Returns the height of the entire nonclient top border, from the edge of the
// window to the top of the tabs. If |restored| is true, this is calculated as
// if the window was restored, regardless of its current state.
int NonClientTopHeight(bool restored) const;
// Returns the y-coordinate of button |button_id|. If |restored| is true,
// acts as if the window is restored regardless of the real mode.
int CaptionButtonY(views::FrameButton button_id, bool restored) const;
// Returns the y-coordinate of the caption button when native frame buttons
// are disabled. If |restored| is true, acts as if the window is restored
// regardless of the real mode.
int DefaultCaptionButtonY(bool restored) const;
// Returns the insets from the native window edge to the flat portion of the
// window border. That is, this function returns the "3D portion" of the
// border. If |restored| is true, acts as if the window is restored
// regardless of the real mode.
gfx::Insets FrameEdgeInsets(bool restored) const;
// Returns the size of the window icon. This can be platform dependent
// because of differences in fonts.
int GetIconSize() const;
// Returns the spacing between the edge of the browser window and the first
// frame buttons.
TopAreaPadding GetTopAreaPadding() const;
// Returns the color of the frame.
SkColor GetFrameColor() const;
// Initializes the button with |button_id| to be aligned according to
// |alignment|.
void ConfigureButton(views::FrameButton button_id, ButtonAlignment alignment);
// Sets the visibility of all buttons associated with |button_id| to false.
void HideButton(views::FrameButton button_id);
// Adds a window caption button to either the leading or trailing side.
void SetBoundsForButton(views::FrameButton button_id,
views::Button* button,
ButtonAlignment alignment);
// Computes the height of the top area of the frame.
int GetTopAreaHeight() const;
// Returns the margin around button |button_id|. If |leading_spacing| is
// true, returns the left margin (in RTL), otherwise returns the right margin
// (in RTL). Extra margin may be added if |is_leading_button| is true.
int GetWindowCaptionSpacing(views::FrameButton button_id,
bool leading_spacing,
bool is_leading_button) const;
// Window controls.
raw_ptr<views::Button> minimize_button_;
raw_ptr<views::Button> maximize_button_;
raw_ptr<views::Button> restore_button_;
raw_ptr<views::Button> close_button_;
// The leading and trailing x positions of the empty space available for
// laying out titlebar elements.
int available_space_leading_x_ = 0;
int available_space_trailing_x_ = 0;
// Whether any of the window control buttons were packed on the leading or
// trailing sides. This state is only valid while layout is being performed.
bool placed_leading_button_ = false;
bool placed_trailing_button_ = false;
// The size of the window buttons. This does not count labels or other
// elements that should be counted in a minimal frame.
int minimum_size_for_buttons_ = 0;
std::vector<views::FrameButton> leading_buttons_;
std::vector<views::FrameButton> trailing_buttons_{
views::FrameButton::kMinimize, views::FrameButton::kMaximize,
views::FrameButton::kClose};
base::CallbackListSubscription paint_as_active_changed_subscription_;
// PlaceholderContainer beneath the controls button for WCO.
raw_ptr<CaptionButtonPlaceholderContainer>
caption_button_placeholder_container_;
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_UI_VIEWS_OPAQUE_FRAME_VIEW_H_

View File

@@ -37,8 +37,6 @@ void WinFrameView::Init(NativeWindowViews* window, views::Widget* frame) {
if (window->IsWindowControlsOverlayEnabled()) {
caption_button_container_ =
AddChildView(std::make_unique<WinCaptionButtonContainer>(this));
} else {
caption_button_container_ = nullptr;
}
}
@@ -82,24 +80,23 @@ views::View* WinFrameView::TargetForRect(views::View* root,
// Custom system titlebar returns non HTCLIENT value, however event should
// be handled by the view, not by the system, because there are no system
// buttons underneath.
if (!ShouldCustomDrawSystemTitlebar()) {
if (!window()->IsWindowControlsOverlayEnabled())
return this;
}
auto local_point = rect.origin();
ConvertPointToTarget(parent(), caption_button_container_, &local_point);
if (!caption_button_container_->HitTestPoint(local_point)) {
if (!caption_button_container_->HitTestPoint(local_point))
return this;
}
}
return NonClientFrameView::TargetForRect(root, rect);
}
int WinFrameView::NonClientHitTest(const gfx::Point& point) {
if (window_->has_frame())
if (window()->has_frame())
return frame_->client_view()->NonClientHitTest(point);
if (ShouldCustomDrawSystemTitlebar()) {
if (window()->IsWindowControlsOverlayEnabled()) {
// See if the point is within any of the window controls.
if (caption_button_container_) {
gfx::Point local_point = point;
@@ -168,10 +165,6 @@ bool WinFrameView::IsMaximized() const {
return frame()->IsMaximized();
}
bool WinFrameView::ShouldCustomDrawSystemTitlebar() const {
return window()->IsWindowControlsOverlayEnabled();
}
void WinFrameView::Layout(PassKey) {
LayoutCaptionButtons();
if (window()->IsWindowControlsOverlayEnabled()) {
@@ -197,7 +190,7 @@ int WinFrameView::FrameTopBorderThicknessPx(bool restored) const {
// See comments in BrowserDesktopWindowTreeHostWin::GetClientAreaInsets().
const bool needs_no_border =
(ShouldCustomDrawSystemTitlebar() && frame()->IsMaximized()) ||
(window()->IsWindowControlsOverlayEnabled() && frame()->IsMaximized()) ||
frame()->IsFullscreen();
if (needs_no_border && !restored)
return 0;
@@ -245,7 +238,7 @@ void WinFrameView::LayoutCaptionButtons() {
return;
// Non-custom system titlebar already contains caption buttons.
if (!ShouldCustomDrawSystemTitlebar()) {
if (!window()->IsWindowControlsOverlayEnabled()) {
caption_button_container_->SetVisible(false);
return;
}

View File

@@ -26,6 +26,7 @@ class WinFrameView : public FramelessView {
~WinFrameView() override;
void Init(NativeWindowViews* window, views::Widget* frame) override;
void InvalidateCaptionButtons() override;
// Alpha to use for features in the titlebar (the window title and caption
// buttons) when the window is inactive. They are opaque when active.
@@ -33,24 +34,17 @@ class WinFrameView : public FramelessView {
SkColor GetReadableFeatureColor(SkColor background_color);
// Tells the NonClientView to invalidate the WinFrameView's caption buttons.
void InvalidateCaptionButtons();
// views::NonClientFrameView:
gfx::Rect GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const override;
int NonClientHitTest(const gfx::Point& point) override;
NativeWindowViews* window() const { return window_; }
views::Widget* frame() const { return frame_; }
WinCaptionButtonContainer* caption_button_container() {
return caption_button_container_;
}
bool IsMaximized() const;
bool ShouldCustomDrawSystemTitlebar() const;
// Visual height of the titlebar when the window is maximized (i.e. excluding
// the area above the top of the screen).
int TitlebarMaximizedVisualHeight() const;
@@ -89,7 +83,7 @@ class WinFrameView : public FramelessView {
// The container holding the caption buttons (minimize, maximize, close, etc.)
// May be null if the caption button container is destroyed before the frame
// view. Always check for validity before using!
raw_ptr<WinCaptionButtonContainer> caption_button_container_;
raw_ptr<WinCaptionButtonContainer> caption_button_container_ = nullptr;
};
} // namespace electron

View File

@@ -530,10 +530,15 @@ void NodeBindings::Initialize(v8::Local<v8::Context> context) {
// Parse and set Node.js cli flags.
std::vector<std::string> args = ParseNodeCliFlags();
// V8::EnableWebAssemblyTrapHandler can be called only once or it will
// hard crash. We need to prevent Node.js calling it in the event it has
// already been called.
node::per_process::cli_options->disable_wasm_trap_handler = true;
uint64_t process_flags =
node::ProcessInitializationFlags::kNoInitializeV8 |
node::ProcessInitializationFlags::kNoInitializeNodeV8Platform |
node::ProcessInitializationFlags::kNoEnableWasmTrapHandler;
node::ProcessInitializationFlags::kNoInitializeNodeV8Platform;
// We do not want the child processes spawned from the utility process
// to inherit the custom stdio handles created for the parent.

View File

@@ -2958,7 +2958,7 @@ describe('BrowserWindow module', () => {
});
});
ifdescribe(['win32', 'darwin'].includes(process.platform))('"titleBarStyle" option', () => {
describe('"titleBarStyle" option', () => {
const testWindowsOverlay = async (style: any) => {
const w = new BrowserWindow({
show: false,
@@ -2997,10 +2997,12 @@ describe('BrowserWindow module', () => {
const [, newOverlayRect] = await geometryChange;
expect(newOverlayRect.width).to.equal(overlayRect.width + 400);
};
afterEach(async () => {
await closeAllWindows();
ipcMain.removeAllListeners('geometrychange');
});
it('creates browser window with hidden title bar', () => {
const w = new BrowserWindow({
show: false,
@@ -3011,6 +3013,7 @@ describe('BrowserWindow module', () => {
const contentSize = w.getContentSize();
expect(contentSize).to.deep.equal([400, 400]);
});
ifit(process.platform === 'darwin')('creates browser window with hidden inset title bar', () => {
const w = new BrowserWindow({
show: false,
@@ -3021,14 +3024,16 @@ describe('BrowserWindow module', () => {
const contentSize = w.getContentSize();
expect(contentSize).to.deep.equal([400, 400]);
});
it('sets Window Control Overlay with hidden title bar', async () => {
await testWindowsOverlay('hidden');
});
ifit(process.platform === 'darwin')('sets Window Control Overlay with hidden inset title bar', async () => {
await testWindowsOverlay('hiddenInset');
});
ifdescribe(process.platform === 'win32')('when an invalid titleBarStyle is initially set', () => {
ifdescribe(process.platform !== 'darwin')('when an invalid titleBarStyle is initially set', () => {
let w: BrowserWindow;
beforeEach(() => {
@@ -3064,7 +3069,7 @@ describe('BrowserWindow module', () => {
});
});
ifdescribe(['win32', 'darwin'].includes(process.platform))('"titleBarOverlay" option', () => {
describe('"titleBarOverlay" option', () => {
const testWindowsOverlayHeight = async (size: any) => {
const w = new BrowserWindow({
show: false,
@@ -3079,6 +3084,7 @@ describe('BrowserWindow module', () => {
height: size
}
});
const overlayHTML = path.join(__dirname, 'fixtures', 'pages', 'overlay.html');
if (process.platform === 'darwin') {
await w.loadFile(overlayHTML);
@@ -3087,48 +3093,52 @@ describe('BrowserWindow module', () => {
await w.loadFile(overlayHTML);
await overlayReady;
}
const overlayEnabled = await w.webContents.executeJavaScript('navigator.windowControlsOverlay.visible');
expect(overlayEnabled).to.be.true('overlayEnabled');
const overlayRectPreMax = await w.webContents.executeJavaScript('getJSOverlayProperties()');
if (!w.isMaximized()) {
const maximize = once(w, 'maximize');
w.show();
w.maximize();
await maximize;
}
expect(w.isMaximized()).to.be.true('not maximized');
const overlayRectPostMax = await w.webContents.executeJavaScript('getJSOverlayProperties()');
expect(overlayRectPreMax.y).to.equal(0);
if (process.platform === 'darwin') {
expect(overlayRectPreMax.x).to.be.greaterThan(0);
} else {
expect(overlayRectPreMax.x).to.equal(0);
}
expect(overlayRectPreMax.width).to.be.greaterThan(0);
expect(overlayRectPreMax.width).to.be.greaterThan(0);
expect(overlayRectPreMax.height).to.equal(size);
// Confirm that maximization only affected the height of the buttons and not the title bar
expect(overlayRectPostMax.height).to.equal(size);
// 'maximize' event is not emitted on Linux in CI.
if (process.platform !== 'linux' && !w.isMaximized()) {
const maximize = once(w, 'maximize');
w.show();
w.maximize();
await maximize;
expect(w.isMaximized()).to.be.true('not maximized');
const overlayRectPostMax = await w.webContents.executeJavaScript('getJSOverlayProperties()');
expect(overlayRectPostMax.height).to.equal(size);
}
};
afterEach(async () => {
await closeAllWindows();
ipcMain.removeAllListeners('geometrychange');
});
it('sets Window Control Overlay with title bar height of 40', async () => {
await testWindowsOverlayHeight(40);
});
});
ifdescribe(process.platform === 'win32')('BrowserWindow.setTitlebarOverlay', () => {
ifdescribe(process.platform !== 'darwin')('BrowserWindow.setTitlebarOverlay', () => {
afterEach(async () => {
await closeAllWindows();
ipcMain.removeAllListeners('geometrychange');
});
it('does not crash when an invalid titleBarStyle was initially set', () => {
it('throws when an invalid titleBarStyle is initially set', () => {
const win = new BrowserWindow({
show: false,
webPreferences: {
@@ -3146,7 +3156,7 @@ describe('BrowserWindow module', () => {
win.setTitleBarOverlay({
color: '#000000'
});
}).to.not.throw();
}).to.throw('Titlebar overlay is not enabled');
});
it('correctly updates the height of the overlay', async () => {
@@ -3160,22 +3170,25 @@ describe('BrowserWindow module', () => {
const overlayEnabled = await w.webContents.executeJavaScript('navigator.windowControlsOverlay.visible');
expect(overlayEnabled).to.be.true('overlayEnabled');
const { height: preMaxHeight } = await w.webContents.executeJavaScript('getJSOverlayProperties()');
if (!w.isMaximized()) {
const { height: preMaxHeight } = await w.webContents.executeJavaScript('getJSOverlayProperties()');
expect(preMaxHeight).to.equal(size);
// 'maximize' event is not emitted on Linux in CI.
if (process.platform !== 'linux' && !w.isMaximized()) {
const maximize = once(w, 'maximize');
w.show();
w.maximize();
await maximize;
}
expect(w.isMaximized()).to.be.true('not maximized');
const { x, y, width, height } = await w.webContents.executeJavaScript('getJSOverlayProperties()');
expect(x).to.equal(0);
expect(y).to.equal(0);
expect(width).to.be.greaterThan(0);
expect(height).to.equal(size);
expect(preMaxHeight).to.equal(size);
await maximize;
expect(w.isMaximized()).to.be.true('not maximized');
const { x, y, width, height } = await w.webContents.executeJavaScript('getJSOverlayProperties()');
expect(x).to.equal(0);
expect(y).to.equal(0);
expect(width).to.be.greaterThan(0);
expect(height).to.equal(size);
}
};
const INITIAL_SIZE = 40;