mirror of
https://github.com/electron/electron.git
synced 2026-02-26 03:01:17 -05:00
Compare commits
13 Commits
v41.0.0-be
...
41-x-y
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b91d3d33fa | ||
|
|
de2ebae944 | ||
|
|
6dcbf464f4 | ||
|
|
3a2b7d3720 | ||
|
|
f86822163f | ||
|
|
e600ad9dac | ||
|
|
f3f3e113ab | ||
|
|
cadea1da52 | ||
|
|
ed26c173b1 | ||
|
|
261f2fcc5e | ||
|
|
19a421a3fc | ||
|
|
c34188ffe9 | ||
|
|
e50f03eceb |
2
.github/workflows/update-website-docs.yml
vendored
2
.github/workflows/update-website-docs.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
echo "isLatestRelease=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Trigger website docs update
|
||||
if: ${{ steps.check-if-latest-release.outputs.isLatestRelease }}
|
||||
if: ${{ steps.check-if-latest-release.outputs.isLatestRelease == 'true' }}
|
||||
env:
|
||||
GH_REPO: electron/website
|
||||
GH_TOKEN: ${{ fromJSON(steps.secret-service.outputs.secrets).WEBSITE_DOCS_UPDATER_APP_TOKEN }}
|
||||
|
||||
@@ -1332,7 +1332,7 @@ Returns `boolean` - Whether the current desktop environment is Unity launcher.
|
||||
### `app.getLoginItemSettings([options])` _macOS_ _Windows_
|
||||
|
||||
* `options` Object (optional)
|
||||
* `type` string (optional) _macOS_ - Can be one of `mainAppService`, `agentService`, `daemonService`, or `loginItemService`. Defaults to `mainAppService`. Only available on macOS 13 and up. See [app.setLoginItemSettings](app.md#appsetloginitemsettingssettings-macos-windows) for more information about each type.
|
||||
* `type` string (optional) _macOS_ - Can be `mainAppService`, `agentService`, `daemonService`, or `loginItemService`. Defaults to `mainAppService`. Only available on macOS 13 and up. See [app.setLoginItemSettings](app.md#appsetloginitemsettingssettings-macos-windows) for more information about each type.
|
||||
* `serviceName` string (optional) _macOS_ - The name of the service. Required if `type` is non-default. Only available on macOS 13 and up.
|
||||
* `path` string (optional) _Windows_ - The executable path to compare against. Defaults to `process.execPath`.
|
||||
* `args` string[] (optional) _Windows_ - The command-line arguments to compare against. Defaults to an empty array.
|
||||
@@ -1347,13 +1347,13 @@ Returns `Object`:
|
||||
* `wasOpenedAtLogin` boolean _macOS_ - `true` if the app was opened at login automatically.
|
||||
* `wasOpenedAsHidden` boolean _macOS_ _Deprecated_ - `true` if the app was opened as a hidden login item. This indicates that the app should not open any windows at startup. This setting is not available on [MAS builds][mas-builds] or on macOS 13 and up.
|
||||
* `restoreState` boolean _macOS_ _Deprecated_ - `true` if the app was opened as a login item that should restore the state from the previous session. This indicates that the app should restore the windows that were open the last time the app was closed. This setting is not available on [MAS builds][mas-builds] or on macOS 13 and up.
|
||||
* `status` string _macOS_ - can be one of `not-registered`, `enabled`, `requires-approval`, or `not-found`.
|
||||
* `status` string _macOS_ - can be `not-registered`, `enabled`, `requires-approval`, or `not-found`.
|
||||
* `executableWillLaunchAtLogin` boolean _Windows_ - `true` if app is set to open at login and its run key is not deactivated. This differs from `openAtLogin` as it ignores the `args` option, this property will be true if the given executable would be launched at login with **any** arguments.
|
||||
* `launchItems` Object[] _Windows_
|
||||
* `name` string _Windows_ - name value of a registry entry.
|
||||
* `path` string _Windows_ - The executable to an app that corresponds to a registry entry.
|
||||
* `args` string[] _Windows_ - the command-line arguments to pass to the executable.
|
||||
* `scope` string _Windows_ - one of `user` or `machine`. Indicates whether the registry entry is under `HKEY_CURRENT USER` or `HKEY_LOCAL_MACHINE`.
|
||||
* `scope` string _Windows_ - can be `user` or `machine`. Indicates whether the registry entry is under `HKEY_CURRENT USER` or `HKEY_LOCAL_MACHINE`.
|
||||
* `enabled` boolean _Windows_ - `true` if the app registry key is startup approved and therefore shows as `enabled` in Task Manager and Windows settings.
|
||||
|
||||
### `app.setLoginItemSettings(settings)` _macOS_ _Windows_
|
||||
|
||||
@@ -354,6 +354,11 @@ Affects the default output directory of [v8.setHeapSnapshotNearHeapLimit](https:
|
||||
|
||||
Disable exposition of [Navigator API][] on the global scope from Node.js.
|
||||
|
||||
### `--experimental-transform-types`
|
||||
|
||||
Enables the [transformation](https://nodejs.org/api/typescript.html#type-stripping)
|
||||
of TypeScript-only syntax into JavaScript code.
|
||||
|
||||
## Chromium Flags
|
||||
|
||||
There isn't a documented list of all Chromium switches, but there are a few ways to find them.
|
||||
|
||||
@@ -30,7 +30,7 @@ The `dialog` module has the following methods:
|
||||
* `openFile` - Allow files to be selected.
|
||||
* `openDirectory` - Allow directories to be selected.
|
||||
* `multiSelections` - Allow multiple paths to be selected.
|
||||
* `showHiddenFiles` - Show hidden files in dialog.
|
||||
* `showHiddenFiles` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
|
||||
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
|
||||
* `promptToCreate` _Windows_ - Prompt for creation if the file path entered
|
||||
in the dialog does not exist. This does not actually create the file at
|
||||
@@ -102,7 +102,7 @@ dialog.showOpenDialogSync(mainWindow, {
|
||||
* `openFile` - Allow files to be selected.
|
||||
* `openDirectory` - Allow directories to be selected.
|
||||
* `multiSelections` - Allow multiple paths to be selected.
|
||||
* `showHiddenFiles` - Show hidden files in dialog.
|
||||
* `showHiddenFiles` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
|
||||
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
|
||||
* `promptToCreate` _Windows_ - Prompt for creation if the file path entered
|
||||
in the dialog does not exist. This does not actually create the file at
|
||||
@@ -185,7 +185,7 @@ dialog.showOpenDialog(mainWindow, {
|
||||
* `showsTagField` boolean (optional) _macOS_ - Show the tags input box,
|
||||
defaults to `true`.
|
||||
* `properties` string[] (optional)
|
||||
* `showHiddenFiles` - Show hidden files in dialog.
|
||||
* `showHiddenFiles` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
|
||||
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
|
||||
* `treatPackageAsDirectory` _macOS_ - Treat packages, such as `.app` folders,
|
||||
as a directory instead of a file.
|
||||
@@ -215,7 +215,7 @@ The `filters` specifies an array of file types that can be displayed, see
|
||||
displayed in front of the filename text field.
|
||||
* `showsTagField` boolean (optional) _macOS_ - Show the tags input box, defaults to `true`.
|
||||
* `properties` string[] (optional)
|
||||
* `showHiddenFiles` - Show hidden files in dialog.
|
||||
* `showHiddenFiles` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
|
||||
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
|
||||
* `treatPackageAsDirectory` _macOS_ - Treat packages, such as `.app` folders,
|
||||
as a directory instead of a file.
|
||||
|
||||
@@ -41,6 +41,12 @@ your preload script and expose it using the [contextBridge](https://www.electron
|
||||
Debug symbols for MacOS (dSYM) now use xz compression in order to handle larger file sizes. `dsym.zip` files are now
|
||||
`dsym.tar.xz` files. End users using debug symbols may need to update their zip utilities.
|
||||
|
||||
### Deprecated: `showHiddenFiles` in Dialogs on Linux
|
||||
|
||||
This property will still be honored on macOS and Windows, but support on Linux
|
||||
will be removed in Electron 42. GTK intends for this to be a user choice rather
|
||||
than an app choice and has removed the API to do this programmatically.
|
||||
|
||||
## Planned Breaking API Changes (39.0)
|
||||
|
||||
### Deprecated: `--host-rules` command line switch
|
||||
|
||||
@@ -50,6 +50,8 @@ filenames = {
|
||||
"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/browser/ui/views/linux_frame_layout.cc",
|
||||
"shell/browser/ui/views/linux_frame_layout.h",
|
||||
"shell/common/application_info_linux.cc",
|
||||
"shell/common/language_util_linux.cc",
|
||||
"shell/common/node_bindings_linux.cc",
|
||||
|
||||
@@ -14,6 +14,23 @@ const DEPS_REGEX = /chromium_version':\n +'(.+?)',/m;
|
||||
const CL_REGEX = /https:\/\/chromium-review\.googlesource\.com\/c\/(?:chromium\/src|v8\/v8)\/\+\/(\d+)(#\S+)?/g;
|
||||
const ROLLER_BRANCH_PATTERN = /^roller\/chromium\/(.+)$/;
|
||||
|
||||
function getCurrentBranch () {
|
||||
// In CI, use `GITHUB_HEAD_REF` since we checkout the PR branch in detached HEAD state
|
||||
if (process.env.GITHUB_HEAD_REF) {
|
||||
return process.env.GITHUB_HEAD_REF;
|
||||
}
|
||||
|
||||
try {
|
||||
return execSync('git rev-parse --abbrev-ref HEAD', {
|
||||
cwd: ELECTRON_DIR,
|
||||
encoding: 'utf8'
|
||||
}).trim();
|
||||
} catch {
|
||||
console.error('Could not determine current git branch');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function getCommitsSinceMergeBase (mergeBase) {
|
||||
try {
|
||||
const output = execSync(`git log --format=%H%n%B%n---COMMIT_END--- ${mergeBase}..HEAD`, {
|
||||
@@ -92,17 +109,7 @@ async function getGerritPatchDetails (clUrl) {
|
||||
}
|
||||
|
||||
async function main () {
|
||||
let currentBranch;
|
||||
|
||||
try {
|
||||
currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {
|
||||
cwd: ELECTRON_DIR,
|
||||
encoding: 'utf8'
|
||||
}).trim();
|
||||
} catch {
|
||||
console.error('Could not determine current git branch');
|
||||
process.exit(1);
|
||||
}
|
||||
const currentBranch = getCurrentBranch();
|
||||
|
||||
// Check if we're on a roller/chromium/* branch
|
||||
const branchMatch = ROLLER_BRANCH_PATTERN.exec(currentBranch);
|
||||
@@ -117,7 +124,7 @@ async function main () {
|
||||
// Get the merge base with the target branch
|
||||
let mergeBase;
|
||||
try {
|
||||
mergeBase = execSync(`git merge-base ${currentBranch} origin/${targetBranch}`, {
|
||||
mergeBase = execSync(`git merge-base HEAD origin/${targetBranch}`, {
|
||||
cwd: ELECTRON_DIR,
|
||||
encoding: 'utf8'
|
||||
}).trim();
|
||||
|
||||
@@ -368,6 +368,9 @@ def upload_io_to_github(release, filename, filepath, version):
|
||||
for c in iter(lambda: upload_process.stdout.read(1), b""):
|
||||
sys.stdout.buffer.write(c)
|
||||
sys.stdout.flush()
|
||||
upload_process.wait()
|
||||
if upload_process.returncode != 0:
|
||||
sys.exit(upload_process.returncode)
|
||||
|
||||
if "GITHUB_OUTPUT" in os.environ:
|
||||
output_path = os.environ["GITHUB_OUTPUT"]
|
||||
|
||||
@@ -4742,6 +4742,19 @@ gin_helper::Handle<WebContents> WebContents::CreateFromWebPreferences(
|
||||
existing_preferences->SetFromDictionary(web_preferences_dict);
|
||||
web_contents->SetBackgroundColor(
|
||||
existing_preferences->GetBackgroundColor());
|
||||
|
||||
double zoom_factor;
|
||||
if (web_preferences.Get(options::kZoomFactor, &zoom_factor)) {
|
||||
auto* zoom_controller = WebContentsZoomController::FromWebContents(
|
||||
web_contents->web_contents());
|
||||
if (zoom_controller) {
|
||||
zoom_controller->SetDefaultZoomFactor(zoom_factor);
|
||||
// Also set the current zoom level immediately, since the page
|
||||
// has already navigated by the time we wrap the webContents.
|
||||
zoom_controller->SetZoomLevel(
|
||||
blink::ZoomFactorToZoomLevel(zoom_factor));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create one if not.
|
||||
|
||||
@@ -33,7 +33,7 @@ WebContentsView::WebContentsView(v8::Isolate* isolate,
|
||||
gin_helper::Handle<WebContents> web_contents)
|
||||
: View(web_contents->inspectable_web_contents()->GetView()),
|
||||
web_contents_(isolate, web_contents.ToV8()),
|
||||
api_web_contents_(web_contents.get()) {
|
||||
api_web_contents_(web_contents->GetWeakPtr()) {
|
||||
set_delete_view(false);
|
||||
view()->SetProperty(
|
||||
views::kFlexBehaviorKey,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "content/public/browser/web_contents_observer.h"
|
||||
#include "shell/browser/api/electron_api_view.h"
|
||||
#include "shell/browser/draggable_region_provider.h"
|
||||
@@ -63,7 +63,7 @@ class WebContentsView : public View,
|
||||
|
||||
// Keep a reference to v8 wrapper.
|
||||
v8::Global<v8::Value> web_contents_;
|
||||
raw_ptr<api::WebContents> api_web_contents_;
|
||||
base::WeakPtr<api::WebContents> api_web_contents_;
|
||||
};
|
||||
|
||||
} // namespace electron::api
|
||||
|
||||
@@ -192,8 +192,7 @@ void NativeWindow::InitFromOptions(const gin_helper::Dictionary& options) {
|
||||
if (bool val; options.Get(options::kMovable, &val))
|
||||
SetMovable(val);
|
||||
|
||||
if (bool val; options.Get(options::kHasShadow, &val))
|
||||
SetHasShadow(val);
|
||||
SetHasShadow(options.ValueOrDefault(options::kHasShadow, true));
|
||||
|
||||
if (double val; options.Get(options::kOpacity, &val))
|
||||
SetOpacity(val);
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
#include "shell/browser/linux/x11_util.h"
|
||||
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
|
||||
#include "shell/browser/ui/views/client_frame_view_linux.h"
|
||||
#include "shell/browser/ui/views/linux_frame_layout.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"
|
||||
@@ -262,6 +263,8 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
|
||||
gfx::Rect bounds{0, 0, width, height};
|
||||
widget_size_ = bounds.size();
|
||||
|
||||
has_shadow_ = options.ValueOrDefault(options::kHasShadow, true);
|
||||
|
||||
widget()->AddObserver(this);
|
||||
|
||||
using InitParams = views::Widget::InitParams;
|
||||
@@ -418,7 +421,7 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
|
||||
}
|
||||
|
||||
gfx::Size size = bounds.size();
|
||||
if (has_frame() && use_content_size_)
|
||||
if ((has_frame() || has_client_frame()) && use_content_size_)
|
||||
size = ContentBoundsToWindowBounds(gfx::Rect(size)).size();
|
||||
|
||||
widget()->CenterWindow(size);
|
||||
@@ -1275,18 +1278,27 @@ void NativeWindowViews::SetBackgroundColor(SkColor background_color) {
|
||||
DeleteObject((HBRUSH)previous_brush);
|
||||
InvalidateRect(GetAcceleratedWidget(), nullptr, 1);
|
||||
#endif
|
||||
widget()->GetCompositor()->SetBackgroundColor(background_color);
|
||||
SkColor compositor_color = background_color;
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
// Widget background needs to stay transparent for CSD shadow regions.
|
||||
LinuxFrameLayout* frame_layout = GetLinuxFrameLayout();
|
||||
const bool uses_csd =
|
||||
frame_layout && frame_layout->SupportsClientFrameShadow();
|
||||
if (transparent() || uses_csd)
|
||||
compositor_color = SK_ColorTRANSPARENT;
|
||||
#endif
|
||||
widget()->GetCompositor()->SetBackgroundColor(compositor_color);
|
||||
}
|
||||
|
||||
void NativeWindowViews::SetHasShadow(bool has_shadow) {
|
||||
has_shadow_ = has_shadow;
|
||||
wm::SetShadowElevation(GetNativeWindow(),
|
||||
has_shadow ? wm::kShadowElevationInactiveWindow
|
||||
: wm::kShadowElevationNone);
|
||||
}
|
||||
|
||||
bool NativeWindowViews::HasShadow() const {
|
||||
return GetNativeWindow()->GetProperty(wm::kShadowElevationKey) !=
|
||||
wm::kShadowElevationNone;
|
||||
return has_shadow_;
|
||||
}
|
||||
|
||||
void NativeWindowViews::SetOpacity(const double opacity) {
|
||||
@@ -1688,7 +1700,7 @@ gfx::Rect NativeWindowViews::WidgetToLogicalBounds(
|
||||
|
||||
gfx::Rect NativeWindowViews::ContentBoundsToWindowBounds(
|
||||
const gfx::Rect& bounds) const {
|
||||
if (!has_frame())
|
||||
if (!has_frame() && !has_client_frame())
|
||||
return bounds;
|
||||
|
||||
gfx::Rect window_bounds(bounds);
|
||||
@@ -1715,7 +1727,7 @@ gfx::Rect NativeWindowViews::ContentBoundsToWindowBounds(
|
||||
|
||||
gfx::Rect NativeWindowViews::WindowBoundsToContentBounds(
|
||||
const gfx::Rect& bounds) const {
|
||||
if (!has_frame())
|
||||
if (!has_frame() && !has_client_frame())
|
||||
return bounds;
|
||||
|
||||
gfx::Rect content_bounds(bounds);
|
||||
@@ -1920,15 +1932,10 @@ std::unique_ptr<views::FrameView> NativeWindowViews::CreateFrameView(
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
electron::ClientFrameViewLinux* NativeWindowViews::GetClientFrameViewLinux() {
|
||||
// Check to make sure this window's non-client frame view is a
|
||||
// ClientFrameViewLinux. If either has_frame() or has_client_frame()
|
||||
// are false, it will be an OpaqueFrameView or NativeFrameView instead.
|
||||
// See NativeWindowViews::CreateFrameView.
|
||||
if (!has_frame() || !has_client_frame())
|
||||
return {};
|
||||
return static_cast<ClientFrameViewLinux*>(
|
||||
LinuxFrameLayout* NativeWindowViews::GetLinuxFrameLayout() {
|
||||
auto* view = views::AsViewClass<FramelessView>(
|
||||
widget()->non_client_view()->frame_view());
|
||||
return view ? view->GetLinuxFrameLayout() : nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace electron {
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
class ClientFrameViewLinux;
|
||||
class GlobalMenuBarX11;
|
||||
class LinuxFrameLayout;
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(SUPPORTS_OZONE_X11)
|
||||
@@ -198,9 +199,7 @@ class NativeWindowViews : public NativeWindow,
|
||||
SkColor overlay_symbol_color() const { return overlay_symbol_color_; }
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
// returns the ClientFrameViewLinux iff that is our FrameView type,
|
||||
// nullptr otherwise.
|
||||
ClientFrameViewLinux* GetClientFrameViewLinux();
|
||||
LinuxFrameLayout* GetLinuxFrameLayout();
|
||||
#endif
|
||||
|
||||
private:
|
||||
@@ -367,6 +366,7 @@ class NativeWindowViews : public NativeWindow,
|
||||
bool maximizable_ = true;
|
||||
bool minimizable_ = true;
|
||||
bool fullscreenable_ = true;
|
||||
bool has_shadow_ = true;
|
||||
gfx::Size widget_size_;
|
||||
double opacity_ = 1.0;
|
||||
bool widget_destroyed_ = false;
|
||||
|
||||
@@ -371,7 +371,7 @@ void HandleToastActivation(const std::wstring& invoked_args,
|
||||
|
||||
int action_index = -1;
|
||||
if (!action_index_str.empty()) {
|
||||
action_index = std::stoi(action_index_str);
|
||||
base::StringToInt(base::WideToUTF8(action_index_str), &action_index);
|
||||
}
|
||||
|
||||
std::string reply_text;
|
||||
|
||||
@@ -35,6 +35,41 @@ int ScopedDisableResize::disable_resize_ = 0;
|
||||
|
||||
typedef void (*MouseDownImpl)(id, SEL, NSEvent*);
|
||||
|
||||
// Work around an Apple bug where the visual tab picker's
|
||||
// grid animation creates NSLayoutConstraints against nil layout anchors,
|
||||
// crashing in NSVisualTabPickerShadowTileView. This happens when a new tabbed
|
||||
// window is created while the tab picker is open — the "+" tile (and possibly
|
||||
// others) have broken internal state. Rather than patching individual tile
|
||||
// animation methods, short-circuit the entire grid animation by swizzling
|
||||
// NSVisualTabPickerGridView's -startGridAnimation:completionHandler: to
|
||||
// immediately invoke the completion handler without running the animation.
|
||||
typedef void (*StartGridAnimationIMP)(id, SEL, id, id);
|
||||
static StartGridAnimationIMP g_orig_startGridAnimation = nullptr;
|
||||
|
||||
static void Patched_startGridAnimation(id self,
|
||||
SEL _cmd,
|
||||
id animation,
|
||||
void (^completionHandler)(void)) {
|
||||
if (completionHandler)
|
||||
completionHandler();
|
||||
}
|
||||
|
||||
static void SwizzleTabPickerGridAnimation() {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
Class cls = NSClassFromString(@"NSVisualTabPickerGridView");
|
||||
if (!cls)
|
||||
return;
|
||||
SEL sel = @selector(startGridAnimation:completionHandler:);
|
||||
Method method = class_getInstanceMethod(cls, sel);
|
||||
if (!method)
|
||||
return;
|
||||
g_orig_startGridAnimation =
|
||||
(StartGridAnimationIMP)method_getImplementation(method);
|
||||
method_setImplementation(method, (IMP)Patched_startGridAnimation);
|
||||
});
|
||||
}
|
||||
|
||||
namespace {
|
||||
MouseDownImpl g_nsthemeframe_mousedown;
|
||||
MouseDownImpl g_nsnextstepframe_mousedown;
|
||||
@@ -125,6 +160,7 @@ void SwizzleSwipeWithEvent(NSView* view, SEL swiz_selector) {
|
||||
|
||||
- (id)initWithShell:(electron::NativeWindowMac*)shell
|
||||
styleMask:(NSUInteger)styleMask {
|
||||
SwizzleTabPickerGridAnimation();
|
||||
if ((self = [super initWithContentRect:ui::kWindowSizeDeterminedLater
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include "shell/browser/linux/x11_util.h"
|
||||
#include "shell/browser/native_window_features.h"
|
||||
#include "shell/browser/native_window_views.h"
|
||||
#include "shell/browser/ui/views/client_frame_view_linux.h"
|
||||
#include "shell/browser/ui/views/linux_frame_layout.h"
|
||||
#include "third_party/skia/include/core/SkRegion.h"
|
||||
#include "ui/aura/window_delegate.h"
|
||||
#include "ui/base/hit_test.h"
|
||||
@@ -85,11 +85,11 @@ gfx::Insets ElectronDesktopWindowTreeHostLinux::CalculateInsetsInDIP(
|
||||
return gfx::Insets();
|
||||
}
|
||||
|
||||
auto* const view = native_window_view_->GetClientFrameViewLinux();
|
||||
if (!view)
|
||||
auto* const layout = native_window_view_->GetLinuxFrameLayout();
|
||||
if (!layout)
|
||||
return {};
|
||||
|
||||
gfx::Insets insets = view->RestoredFrameBorderInsets();
|
||||
gfx::Insets insets = layout->RestoredFrameBorderInsets();
|
||||
return insets;
|
||||
}
|
||||
|
||||
@@ -117,14 +117,14 @@ void ElectronDesktopWindowTreeHostLinux::OnWindowStateChanged(
|
||||
|
||||
void ElectronDesktopWindowTreeHostLinux::OnWindowTiledStateChanged(
|
||||
ui::WindowTiledEdges new_tiled_edges) {
|
||||
if (auto* const view = native_window_view_->GetClientFrameViewLinux()) {
|
||||
if (auto* layout = native_window_view_->GetLinuxFrameLayout()) {
|
||||
// GNOME on Ubuntu reports all edges as tiled
|
||||
// even if the window is only half-tiled so do not trust individual edge
|
||||
// values.
|
||||
bool maximized = native_window_view_->IsMaximized();
|
||||
bool tiled = new_tiled_edges.top || new_tiled_edges.left ||
|
||||
new_tiled_edges.bottom || new_tiled_edges.right;
|
||||
view->set_tiled(tiled && !maximized);
|
||||
layout->set_tiled(tiled && !maximized);
|
||||
}
|
||||
UpdateFrameHints();
|
||||
ScheduleRelayout();
|
||||
@@ -180,15 +180,14 @@ void ElectronDesktopWindowTreeHostLinux::OnDeviceScaleFactorChanged() {
|
||||
|
||||
void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
|
||||
if (base::FeatureList::IsEnabled(features::kWaylandWindowDecorations)) {
|
||||
auto* const view = native_window_view_->GetClientFrameViewLinux();
|
||||
if (!view)
|
||||
auto* const layout = native_window_view_->GetLinuxFrameLayout();
|
||||
if (!layout)
|
||||
return;
|
||||
|
||||
ui::PlatformWindow* window = platform_window();
|
||||
auto window_state = window->GetPlatformWindowState();
|
||||
float scale = device_scale_factor();
|
||||
const gfx::Size widget_size =
|
||||
view->GetWidget()->GetWindowBoundsInScreen().size();
|
||||
const gfx::Size widget_size = GetWidget()->GetWindowBoundsInScreen().size();
|
||||
|
||||
if (SupportsClientFrameShadow()) {
|
||||
auto insets = CalculateInsetsInDIP(window_state);
|
||||
@@ -196,7 +195,7 @@ void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
|
||||
window->SetInputRegion(std::nullopt);
|
||||
} else {
|
||||
gfx::Rect input_bounds(widget_size);
|
||||
input_bounds.Inset(insets - view->GetInputInsets());
|
||||
input_bounds.Inset(insets - layout->GetInputInsets());
|
||||
input_bounds = gfx::ScaleToEnclosingRect(input_bounds, scale);
|
||||
window->SetInputRegion(
|
||||
std::optional<std::vector<gfx::Rect>>({input_bounds}));
|
||||
@@ -210,8 +209,8 @@ void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
|
||||
// The opaque region is a list of rectangles that contain only fully
|
||||
// opaque pixels of the window. We need to convert the clipping
|
||||
// rounded-rect into this format.
|
||||
SkRRect rrect = view->GetRoundedWindowContentBounds();
|
||||
gfx::RectF rectf(view->GetWindowContentBounds());
|
||||
SkRRect rrect = layout->GetRoundedWindowContentBounds();
|
||||
gfx::RectF rectf(layout->GetWindowContentBounds());
|
||||
rectf.Scale(scale);
|
||||
// It is acceptable to omit some pixels that are opaque, but the region
|
||||
// must not include any translucent pixels. Therefore, we must
|
||||
@@ -245,7 +244,8 @@ void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
|
||||
|
||||
auto translucent_top_area_rect = SkIRect::MakeXYWH(
|
||||
rect.x(), rect.y(), rect.width(),
|
||||
std::ceil(view->GetTranslucentTopAreaHeight() * scale - rect.y()));
|
||||
std::ceil(layout->GetTranslucentTopAreaHeight() * scale -
|
||||
rect.y()));
|
||||
region.op(translucent_top_area_rect, SkRegion::kDifference_Op);
|
||||
|
||||
// Convert the region to a list of rectangles.
|
||||
@@ -256,7 +256,7 @@ void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
|
||||
// The entire window except for the translucent top is opaque.
|
||||
gfx::Rect opaque_region_dip(widget_size);
|
||||
gfx::Insets insets;
|
||||
insets.set_top(view->GetTranslucentTopAreaHeight());
|
||||
insets.set_top(layout->GetTranslucentTopAreaHeight());
|
||||
opaque_region_dip.Inset(insets);
|
||||
opaque_region.push_back(
|
||||
gfx::ScaleToEnclosingRect(opaque_region_dip, scale));
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
namespace electron {
|
||||
|
||||
class ClientFrameViewLinux;
|
||||
class NativeWindowViews;
|
||||
|
||||
class ElectronDesktopWindowTreeHostLinux
|
||||
|
||||
@@ -92,6 +92,13 @@ InspectableWebContentsView::InspectableWebContentsView(
|
||||
}
|
||||
|
||||
InspectableWebContentsView::~InspectableWebContentsView() {
|
||||
if (devtools_window_web_view_)
|
||||
devtools_window_web_view_->SetWebContents(nullptr);
|
||||
if (devtools_web_view_)
|
||||
devtools_web_view_->SetWebContents(nullptr);
|
||||
if (contents_web_view_)
|
||||
contents_web_view_->SetWebContents(nullptr);
|
||||
|
||||
if (devtools_window_)
|
||||
inspectable_web_contents()->SaveDevToolsBounds(
|
||||
devtools_window_->GetWindowBoundsInScreen());
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
#include "cc/paint/paint_filter.h"
|
||||
#include "cc/paint/paint_flags.h"
|
||||
#include "shell/browser/native_window_views.h"
|
||||
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
|
||||
#include "shell/browser/ui/views/frameless_view.h"
|
||||
#include "shell/browser/ui/views/linux_frame_layout.h"
|
||||
#include "ui/base/hit_test.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/base/metadata/metadata_impl_macros.h"
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "ui/gfx/font_list.h"
|
||||
#include "ui/gfx/geometry/insets.h"
|
||||
#include "ui/gfx/geometry/rect.h"
|
||||
#include "ui/gfx/geometry/skia_conversions.h"
|
||||
#include "ui/gfx/text_constants.h"
|
||||
#include "ui/gtk/gtk_compat.h" // nogncheck
|
||||
#include "ui/gtk/gtk_util.h" // nogncheck
|
||||
@@ -39,9 +38,6 @@ namespace electron {
|
||||
|
||||
namespace {
|
||||
|
||||
// These values should be the same as Chromium uses.
|
||||
constexpr int kResizeBorder = 10;
|
||||
|
||||
ui::NavButtonProvider::ButtonState ButtonStateToNavButtonProviderState(
|
||||
views::Button::ButtonState state) {
|
||||
switch (state) {
|
||||
@@ -116,6 +112,7 @@ ClientFrameViewLinux::~ClientFrameViewLinux() {
|
||||
void ClientFrameViewLinux::Init(NativeWindowViews* window,
|
||||
views::Widget* frame) {
|
||||
FramelessView::Init(window, frame);
|
||||
linux_frame_layout_ = std::make_unique<LinuxCSDFrameLayout>(window);
|
||||
|
||||
// Unretained() is safe because the subscription is saved into an instance
|
||||
// member and thus will be cancelled upon the instance's destruction.
|
||||
@@ -123,11 +120,6 @@ void ClientFrameViewLinux::Init(NativeWindowViews* window,
|
||||
frame_->RegisterPaintAsActiveChangedCallback(base::BindRepeating(
|
||||
&ClientFrameViewLinux::PaintAsActiveChanged, base::Unretained(this)));
|
||||
|
||||
auto* tree_host = static_cast<ElectronDesktopWindowTreeHostLinux*>(
|
||||
ElectronDesktopWindowTreeHostLinux::GetHostForWidget(
|
||||
window->GetAcceleratedWidget()));
|
||||
host_supports_client_frame_shadow_ = tree_host->SupportsClientFrameShadow();
|
||||
|
||||
UpdateWindowTitle();
|
||||
|
||||
for (auto& button : nav_buttons_) {
|
||||
@@ -143,50 +135,11 @@ void ClientFrameViewLinux::Init(NativeWindowViews* window,
|
||||
}
|
||||
|
||||
gfx::Insets ClientFrameViewLinux::RestoredFrameBorderInsets() const {
|
||||
gfx::Insets insets = GetFrameProvider()->GetFrameThicknessDip();
|
||||
const gfx::Insets input = GetInputInsets();
|
||||
|
||||
auto expand_if_visible = [](int side_thickness, int min_band) {
|
||||
return side_thickness > 0 ? std::max(side_thickness, min_band) : 0;
|
||||
};
|
||||
|
||||
gfx::Insets merged;
|
||||
merged.set_top(expand_if_visible(insets.top(), input.top()));
|
||||
merged.set_left(expand_if_visible(insets.left(), input.left()));
|
||||
merged.set_bottom(expand_if_visible(insets.bottom(), input.bottom()));
|
||||
merged.set_right(expand_if_visible(insets.right(), input.right()));
|
||||
|
||||
return base::i18n::IsRTL() ? gfx::Insets::TLBR(merged.top(), merged.right(),
|
||||
merged.bottom(), merged.left())
|
||||
: merged;
|
||||
return linux_frame_layout_->RestoredFrameBorderInsets();
|
||||
}
|
||||
|
||||
gfx::Insets ClientFrameViewLinux::GetInputInsets() const {
|
||||
bool showing_shadow = host_supports_client_frame_shadow_ &&
|
||||
!frame_->IsMaximized() && !frame_->IsFullscreen();
|
||||
return gfx::Insets(showing_shadow ? kResizeBorder : 0);
|
||||
}
|
||||
|
||||
gfx::Rect ClientFrameViewLinux::GetWindowContentBounds() const {
|
||||
gfx::Rect content_bounds = bounds();
|
||||
content_bounds.Inset(RestoredFrameBorderInsets());
|
||||
return content_bounds;
|
||||
}
|
||||
|
||||
SkRRect ClientFrameViewLinux::GetRoundedWindowContentBounds() const {
|
||||
SkRect rect = gfx::RectToSkRect(GetWindowContentBounds());
|
||||
SkRRect rrect;
|
||||
|
||||
if (!frame_->IsMaximized()) {
|
||||
SkPoint round_point{theme_values_.window_border_radius,
|
||||
theme_values_.window_border_radius};
|
||||
SkPoint radii[] = {round_point, round_point, {}, {}};
|
||||
rrect.setRectRadii(rect, radii);
|
||||
} else {
|
||||
rrect.setRect(rect);
|
||||
}
|
||||
|
||||
return rrect;
|
||||
LinuxFrameLayout* ClientFrameViewLinux::GetLinuxFrameLayout() const {
|
||||
return linux_frame_layout_.get();
|
||||
}
|
||||
|
||||
void ClientFrameViewLinux::OnNativeThemeUpdated(
|
||||
@@ -245,11 +198,6 @@ int ClientFrameViewLinux::NonClientHitTest(const gfx::Point& point) {
|
||||
return FramelessView::NonClientHitTest(point);
|
||||
}
|
||||
|
||||
ui::WindowFrameProvider* ClientFrameViewLinux::GetFrameProvider() const {
|
||||
return ui::LinuxUiTheme::GetForProfile(nullptr)->GetWindowFrameProvider(
|
||||
!host_supports_client_frame_shadow_, tiled(), frame_->IsMaximized());
|
||||
}
|
||||
|
||||
void ClientFrameViewLinux::GetWindowMask(const gfx::Size& size,
|
||||
SkPath* window_mask) {
|
||||
// Nothing to do here, as transparency is used for decorations, not masks.
|
||||
@@ -287,11 +235,8 @@ void ClientFrameViewLinux::Layout(PassKey) {
|
||||
}
|
||||
|
||||
void ClientFrameViewLinux::OnPaint(gfx::Canvas* canvas) {
|
||||
if (!frame_->IsFullscreen()) {
|
||||
GetFrameProvider()->PaintWindowFrame(
|
||||
canvas, GetLocalBounds(), GetTitlebarBounds().bottom(),
|
||||
ShouldPaintAsActive(), GetInputInsets());
|
||||
}
|
||||
linux_frame_layout_->PaintWindowFrame(
|
||||
canvas, GetLocalBounds(), GetTitlebarBounds(), ShouldPaintAsActive());
|
||||
}
|
||||
|
||||
void ClientFrameViewLinux::PaintAsActiveChanged() {
|
||||
@@ -322,7 +267,7 @@ void ClientFrameViewLinux::UpdateThemeValues() {
|
||||
}
|
||||
|
||||
theme_values_.window_border_radius =
|
||||
GetFrameProvider()->GetTopCornerRadiusDip();
|
||||
linux_frame_layout_->GetFrameProvider()->GetTopCornerRadiusDip();
|
||||
|
||||
gtk::GtkStyleContextGet(headerbar_context, "min-height",
|
||||
&theme_values_.titlebar_min_height, nullptr);
|
||||
@@ -479,10 +424,6 @@ views::View* ClientFrameViewLinux::TargetForRect(views::View* root,
|
||||
return views::FrameView::TargetForRect(root, rect);
|
||||
}
|
||||
|
||||
int ClientFrameViewLinux::GetTranslucentTopAreaHeight() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
BEGIN_METADATA(ClientFrameViewLinux) END_METADATA
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -13,13 +13,12 @@
|
||||
#include "base/memory/raw_ptr_exclusion.h"
|
||||
#include "base/scoped_observation.h"
|
||||
#include "shell/browser/ui/views/frameless_view.h"
|
||||
#include "third_party/skia/include/core/SkRRect.h"
|
||||
#include "shell/browser/ui/views/linux_frame_layout.h"
|
||||
#include "ui/base/metadata/metadata_header_macros.h"
|
||||
#include "ui/base/ui_base_types.h"
|
||||
#include "ui/linux/linux_ui.h"
|
||||
#include "ui/linux/nav_button_provider.h"
|
||||
#include "ui/linux/window_button_order_observer.h"
|
||||
#include "ui/linux/window_frame_provider.h"
|
||||
#include "ui/native_theme/native_theme.h"
|
||||
#include "ui/native_theme/native_theme_observer.h"
|
||||
#include "ui/views/controls/button/image_button.h"
|
||||
@@ -44,15 +43,7 @@ class ClientFrameViewLinux : public FramelessView,
|
||||
|
||||
// FramelessView:
|
||||
gfx::Insets RestoredFrameBorderInsets() const override;
|
||||
|
||||
gfx::Insets GetInputInsets() const;
|
||||
gfx::Rect GetWindowContentBounds() const;
|
||||
SkRRect GetRoundedWindowContentBounds() const;
|
||||
int GetTranslucentTopAreaHeight() const;
|
||||
|
||||
// Returns whether the frame is in a tiled state.
|
||||
bool tiled() const { return tiled_; }
|
||||
void set_tiled(bool tiled) { tiled_ = tiled; }
|
||||
LinuxFrameLayout* GetLinuxFrameLayout() const override;
|
||||
|
||||
protected:
|
||||
// ui::NativeThemeObserver:
|
||||
@@ -80,8 +71,6 @@ class ClientFrameViewLinux : public FramelessView,
|
||||
// Overridden from views::ViewTargeterDelegate
|
||||
views::View* TargetForRect(views::View* root, const gfx::Rect& rect) override;
|
||||
|
||||
ui::WindowFrameProvider* GetFrameProvider() const;
|
||||
|
||||
private:
|
||||
static constexpr int kNavButtonCount = 4;
|
||||
|
||||
@@ -123,6 +112,8 @@ class ClientFrameViewLinux : public FramelessView,
|
||||
gfx::Insets GetTitlebarContentInsets() const;
|
||||
gfx::Rect GetTitlebarContentBounds() const;
|
||||
|
||||
std::unique_ptr<LinuxFrameLayout> linux_frame_layout_;
|
||||
|
||||
raw_ptr<ui::NativeTheme> theme_;
|
||||
ThemeValues theme_values_;
|
||||
|
||||
@@ -134,16 +125,12 @@ class ClientFrameViewLinux : public FramelessView,
|
||||
std::vector<views::FrameButton> leading_frame_buttons_;
|
||||
std::vector<views::FrameButton> trailing_frame_buttons_;
|
||||
|
||||
bool host_supports_client_frame_shadow_ = false;
|
||||
|
||||
base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver>
|
||||
native_theme_observer_{this};
|
||||
base::ScopedObservation<ui::LinuxUi, ui::WindowButtonOrderObserver>
|
||||
window_button_order_observer_{this};
|
||||
|
||||
base::CallbackListSubscription paint_as_active_changed_subscription_;
|
||||
|
||||
bool tiled_ = false;
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -122,6 +122,13 @@ gfx::Size FramelessView::GetMaximumSize() const {
|
||||
return gfx::Size();
|
||||
return window_->GetMaximumSize();
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
LinuxFrameLayout* FramelessView::GetLinuxFrameLayout() const {
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
BEGIN_METADATA(FramelessView)
|
||||
END_METADATA
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#include "ui/gfx/geometry/insets.h"
|
||||
#include "ui/views/window/non_client_view.h"
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
#include "shell/browser/ui/views/linux_frame_layout.h"
|
||||
#endif
|
||||
|
||||
namespace views {
|
||||
class Widget;
|
||||
}
|
||||
@@ -42,6 +46,10 @@ class FramelessView : public views::FrameView {
|
||||
// bounds of the view, used for CSD and resize targets on some platforms.
|
||||
virtual gfx::Insets RestoredFrameBorderInsets() const;
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
virtual LinuxFrameLayout* GetLinuxFrameLayout() const;
|
||||
#endif
|
||||
|
||||
NativeWindowViews* window() const { return window_; }
|
||||
views::Widget* frame() const { return frame_; }
|
||||
|
||||
|
||||
171
shell/browser/ui/views/linux_frame_layout.cc
Normal file
171
shell/browser/ui/views/linux_frame_layout.cc
Normal file
@@ -0,0 +1,171 @@
|
||||
// Copyright (c) 2025 Mitchell Cohen.
|
||||
// Copyright (c) 2021 Ryan Gonzalez.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/ui/views/linux_frame_layout.h"
|
||||
#include "base/i18n/rtl.h"
|
||||
#include "shell/browser/native_window_views.h"
|
||||
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
|
||||
#include "ui/gfx/canvas.h"
|
||||
#include "ui/gfx/geometry/insets.h"
|
||||
#include "ui/gfx/geometry/skia_conversions.h"
|
||||
#include "ui/linux/linux_ui.h"
|
||||
#include "ui/native_theme/native_theme.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace {
|
||||
// This should match Chromium's value.
|
||||
constexpr int kResizeBorder = 10;
|
||||
// This should match FramelessView's inside resize band.
|
||||
constexpr int kResizeInsideBoundsSize = 5;
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
std::unique_ptr<LinuxFrameLayout> LinuxFrameLayout::Create(
|
||||
NativeWindowViews* window,
|
||||
bool wants_shadow) {
|
||||
if (x11_util::IsX11() || window->IsTranslucent() || !wants_shadow) {
|
||||
return std::make_unique<LinuxUndecoratedFrameLayout>(window);
|
||||
} else {
|
||||
return std::make_unique<LinuxCSDFrameLayout>(window);
|
||||
}
|
||||
}
|
||||
|
||||
LinuxCSDFrameLayout::LinuxCSDFrameLayout(NativeWindowViews* window)
|
||||
: window_(window) {
|
||||
host_supports_client_frame_shadow_ = SupportsClientFrameShadow();
|
||||
}
|
||||
|
||||
bool LinuxCSDFrameLayout::tiled() const {
|
||||
return tiled_;
|
||||
}
|
||||
|
||||
void LinuxCSDFrameLayout::set_tiled(bool tiled) {
|
||||
tiled_ = tiled;
|
||||
}
|
||||
|
||||
gfx::Insets LinuxCSDFrameLayout::RestoredFrameBorderInsets() const {
|
||||
gfx::Insets insets = GetFrameProvider()->GetFrameThicknessDip();
|
||||
const gfx::Insets input = GetInputInsets();
|
||||
|
||||
auto expand_if_visible = [](int side_thickness, int min_band) {
|
||||
return side_thickness > 0 ? std::max(side_thickness, min_band) : 0;
|
||||
};
|
||||
|
||||
gfx::Insets merged;
|
||||
merged.set_top(expand_if_visible(insets.top(), input.top()));
|
||||
merged.set_left(expand_if_visible(insets.left(), input.left()));
|
||||
merged.set_bottom(expand_if_visible(insets.bottom(), input.bottom()));
|
||||
merged.set_right(expand_if_visible(insets.right(), input.right()));
|
||||
|
||||
return base::i18n::IsRTL() ? gfx::Insets::TLBR(merged.top(), merged.right(),
|
||||
merged.bottom(), merged.left())
|
||||
: merged;
|
||||
}
|
||||
|
||||
gfx::Insets LinuxCSDFrameLayout::GetInputInsets() const {
|
||||
bool showing_shadow = host_supports_client_frame_shadow_ &&
|
||||
!window_->IsMaximized() && !window_->IsFullscreen();
|
||||
return gfx::Insets(showing_shadow ? kResizeBorder : 0);
|
||||
}
|
||||
|
||||
bool LinuxCSDFrameLayout::SupportsClientFrameShadow() const {
|
||||
auto* tree_host = static_cast<ElectronDesktopWindowTreeHostLinux*>(
|
||||
ElectronDesktopWindowTreeHostLinux::GetHostForWidget(
|
||||
window_->GetAcceleratedWidget()));
|
||||
return tree_host->SupportsClientFrameShadow();
|
||||
}
|
||||
|
||||
void LinuxCSDFrameLayout::PaintWindowFrame(gfx::Canvas* canvas,
|
||||
gfx::Rect local_bounds,
|
||||
gfx::Rect titlebar_bounds,
|
||||
bool active) {
|
||||
GetFrameProvider()->PaintWindowFrame(
|
||||
canvas, local_bounds, titlebar_bounds.bottom(), active, GetInputInsets());
|
||||
}
|
||||
|
||||
gfx::Rect LinuxCSDFrameLayout::GetWindowContentBounds() const {
|
||||
gfx::Rect content_bounds = window_->widget()->GetWindowBoundsInScreen();
|
||||
content_bounds.Inset(RestoredFrameBorderInsets());
|
||||
return content_bounds;
|
||||
}
|
||||
|
||||
SkRRect LinuxCSDFrameLayout::GetRoundedWindowContentBounds() const {
|
||||
SkRect rect = gfx::RectToSkRect(GetWindowContentBounds());
|
||||
SkRRect rrect;
|
||||
|
||||
if (!window_->IsMaximized()) {
|
||||
float radius = GetFrameProvider()->GetTopCornerRadiusDip();
|
||||
SkPoint round_point{radius, radius};
|
||||
SkPoint radii[] = {round_point, round_point, {}, {}};
|
||||
rrect.setRectRadii(rect, radii);
|
||||
} else {
|
||||
rrect.setRect(rect);
|
||||
}
|
||||
|
||||
return rrect;
|
||||
}
|
||||
|
||||
int LinuxCSDFrameLayout::GetTranslucentTopAreaHeight() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ui::WindowFrameProvider* LinuxCSDFrameLayout::GetFrameProvider() const {
|
||||
return ui::LinuxUiTheme::GetForProfile(nullptr)->GetWindowFrameProvider(
|
||||
!host_supports_client_frame_shadow_, tiled(), window_->IsMaximized());
|
||||
}
|
||||
|
||||
LinuxUndecoratedFrameLayout::LinuxUndecoratedFrameLayout(
|
||||
NativeWindowViews* window)
|
||||
: window_(window) {}
|
||||
|
||||
gfx::Insets LinuxUndecoratedFrameLayout::RestoredFrameBorderInsets() const {
|
||||
return gfx::Insets();
|
||||
}
|
||||
|
||||
gfx::Insets LinuxUndecoratedFrameLayout::GetInputInsets() const {
|
||||
return gfx::Insets(kResizeInsideBoundsSize);
|
||||
}
|
||||
|
||||
bool LinuxUndecoratedFrameLayout::SupportsClientFrameShadow() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxUndecoratedFrameLayout::tiled() const {
|
||||
return tiled_;
|
||||
}
|
||||
|
||||
void LinuxUndecoratedFrameLayout::set_tiled(bool tiled) {
|
||||
tiled_ = tiled;
|
||||
}
|
||||
|
||||
void LinuxUndecoratedFrameLayout::PaintWindowFrame(gfx::Canvas* canvas,
|
||||
gfx::Rect local_bounds,
|
||||
gfx::Rect titlebar_bounds,
|
||||
bool active) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
gfx::Rect LinuxUndecoratedFrameLayout::GetWindowContentBounds() const {
|
||||
// With no transparent insets, widget bounds and logical bounds match.
|
||||
return window_->widget()->GetWindowBoundsInScreen();
|
||||
}
|
||||
|
||||
SkRRect LinuxUndecoratedFrameLayout::GetRoundedWindowContentBounds() const {
|
||||
SkRRect rrect;
|
||||
rrect.setRect(gfx::RectToSkRect(GetWindowContentBounds()));
|
||||
return rrect;
|
||||
}
|
||||
|
||||
int LinuxUndecoratedFrameLayout::GetTranslucentTopAreaHeight() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ui::WindowFrameProvider* LinuxUndecoratedFrameLayout::GetFrameProvider() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
117
shell/browser/ui/views/linux_frame_layout.h
Normal file
117
shell/browser/ui/views/linux_frame_layout.h
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2025 Mitchell Cohen.
|
||||
// Copyright (c) 2021 Ryan Gonzalez.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_BROWSER_UI_VIEWS_LINUX_FRAME_LAYOUT_H_
|
||||
#define ELECTRON_SHELL_BROWSER_UI_VIEWS_LINUX_FRAME_LAYOUT_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/i18n/rtl.h"
|
||||
#include "shell/browser/linux/x11_util.h"
|
||||
#include "shell/browser/native_window_views.h"
|
||||
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
|
||||
#include "third_party/skia/include/core/SkRRect.h"
|
||||
#include "ui/base/ozone_buildflags.h"
|
||||
#include "ui/gfx/canvas.h"
|
||||
#include "ui/gfx/geometry/insets.h"
|
||||
#include "ui/linux/linux_ui.h"
|
||||
#include "ui/linux/window_frame_provider.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
class NativeWindowViews;
|
||||
|
||||
// Shared helper for CSD layout and frame painting on Linux (shadows, resize
|
||||
// regions, titlebars, etc.). Also helps views determine insets and perform
|
||||
// bounds conversions between widget and logical coordinates.
|
||||
class LinuxFrameLayout {
|
||||
public:
|
||||
virtual ~LinuxFrameLayout() = default;
|
||||
|
||||
static std::unique_ptr<LinuxFrameLayout> Create(NativeWindowViews* window,
|
||||
bool wants_shadow);
|
||||
|
||||
// Insets from the transparent widget border to the opaque part of the window
|
||||
virtual gfx::Insets RestoredFrameBorderInsets() const = 0;
|
||||
// Insets for parts of the surface that should be counted for user input
|
||||
virtual gfx::Insets GetInputInsets() const = 0;
|
||||
|
||||
virtual bool SupportsClientFrameShadow() const = 0;
|
||||
|
||||
virtual bool tiled() const = 0;
|
||||
virtual void set_tiled(bool tiled) = 0;
|
||||
|
||||
virtual void PaintWindowFrame(gfx::Canvas* canvas,
|
||||
gfx::Rect local_bounds,
|
||||
gfx::Rect titlebar_bounds,
|
||||
bool active) = 0;
|
||||
|
||||
// The logical bounds of the window
|
||||
virtual gfx::Rect GetWindowContentBounds() const = 0;
|
||||
// The logical bounds as a rounded rect with corner radii applied
|
||||
virtual SkRRect GetRoundedWindowContentBounds() const = 0;
|
||||
|
||||
virtual int GetTranslucentTopAreaHeight() const = 0;
|
||||
|
||||
virtual ui::WindowFrameProvider* GetFrameProvider() const = 0;
|
||||
};
|
||||
|
||||
// Client-side decoration (CSD) Linux frame layout implementation.
|
||||
class LinuxCSDFrameLayout : public LinuxFrameLayout {
|
||||
public:
|
||||
explicit LinuxCSDFrameLayout(NativeWindowViews* window);
|
||||
~LinuxCSDFrameLayout() override = default;
|
||||
|
||||
gfx::Insets RestoredFrameBorderInsets() const override;
|
||||
gfx::Insets GetInputInsets() const override;
|
||||
bool SupportsClientFrameShadow() const override;
|
||||
bool tiled() const override;
|
||||
void set_tiled(bool tiled) override;
|
||||
void PaintWindowFrame(gfx::Canvas* canvas,
|
||||
gfx::Rect local_bounds,
|
||||
gfx::Rect titlebar_bounds,
|
||||
bool active) override;
|
||||
gfx::Rect GetWindowContentBounds() const override;
|
||||
SkRRect GetRoundedWindowContentBounds() const override;
|
||||
int GetTranslucentTopAreaHeight() const override;
|
||||
ui::WindowFrameProvider* GetFrameProvider() const override;
|
||||
|
||||
private:
|
||||
raw_ptr<NativeWindowViews> window_;
|
||||
bool tiled_ = false;
|
||||
bool host_supports_client_frame_shadow_ = false;
|
||||
};
|
||||
|
||||
// No-decoration Linux frame layout implementation.
|
||||
//
|
||||
// Intended for cases where we do not allocate a transparent inset area around
|
||||
// the window (e.g. X11 / server-side decorations, or when insets are disabled).
|
||||
// All inset math returns 0 and frame painting is skipped.
|
||||
class LinuxUndecoratedFrameLayout : public LinuxFrameLayout {
|
||||
public:
|
||||
explicit LinuxUndecoratedFrameLayout(NativeWindowViews* window);
|
||||
~LinuxUndecoratedFrameLayout() override = default;
|
||||
|
||||
gfx::Insets RestoredFrameBorderInsets() const override;
|
||||
gfx::Insets GetInputInsets() const override;
|
||||
bool SupportsClientFrameShadow() const override;
|
||||
bool tiled() const override;
|
||||
void set_tiled(bool tiled) override;
|
||||
void PaintWindowFrame(gfx::Canvas* canvas,
|
||||
gfx::Rect local_bounds,
|
||||
gfx::Rect titlebar_bounds,
|
||||
bool active) override;
|
||||
gfx::Rect GetWindowContentBounds() const override;
|
||||
SkRRect GetRoundedWindowContentBounds() const override;
|
||||
int GetTranslucentTopAreaHeight() const override;
|
||||
ui::WindowFrameProvider* GetFrameProvider() const override;
|
||||
|
||||
private:
|
||||
raw_ptr<NativeWindowViews> window_;
|
||||
bool tiled_ = false;
|
||||
};
|
||||
} // namespace electron
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_UI_VIEWS_LINUX_FRAME_LAYOUT_H_
|
||||
@@ -60,6 +60,13 @@ OpaqueFrameView::~OpaqueFrameView() = default;
|
||||
|
||||
void OpaqueFrameView::Init(NativeWindowViews* window, views::Widget* frame) {
|
||||
FramelessView::Init(window, frame);
|
||||
linux_frame_layout_ = LinuxFrameLayout::Create(window, window->HasShadow());
|
||||
|
||||
// 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)));
|
||||
|
||||
if (!window->IsWindowControlsOverlayEnabled())
|
||||
return;
|
||||
@@ -88,16 +95,12 @@ void OpaqueFrameView::Init(NativeWindowViews* window, views::Widget* frame) {
|
||||
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);
|
||||
auto insets = RestoredFrameBorderInsets();
|
||||
return ResizingBorderHitTestImpl(
|
||||
point, insets.IsEmpty() ? linux_frame_layout_->GetInputInsets() : insets);
|
||||
}
|
||||
|
||||
void OpaqueFrameView::InvalidateCaptionButtons() {
|
||||
@@ -108,40 +111,28 @@ void OpaqueFrameView::InvalidateCaptionButtons() {
|
||||
}
|
||||
|
||||
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()));
|
||||
gfx::Rect client_bounds = bounds();
|
||||
if (!frame_->IsFullscreen()) {
|
||||
client_bounds.Inset(FrameBorderInsets(false));
|
||||
}
|
||||
|
||||
return FramelessView::GetBoundsForClientView();
|
||||
return client_bounds;
|
||||
}
|
||||
|
||||
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);
|
||||
gfx::Insets insets = bounds().InsetsFrom(GetBoundsForClientView());
|
||||
return gfx::Rect(std::max(0, client_bounds.x() - insets.left()),
|
||||
std::max(0, client_bounds.y() - insets.top()),
|
||||
client_bounds.width() + insets.width(),
|
||||
client_bounds.height() + insets.height());
|
||||
}
|
||||
|
||||
int OpaqueFrameView::NonClientHitTest(const gfx::Point& point) {
|
||||
if (window()->IsWindowControlsOverlayEnabled()) {
|
||||
// Ensure support for resizing frameless window with border drag.
|
||||
int frame_component = ResizingBorderHitTest(point);
|
||||
if (frame_component != HTNOWHERE)
|
||||
return frame_component;
|
||||
int frame_component = ResizingBorderHitTest(point);
|
||||
if (frame_component != HTNOWHERE)
|
||||
return frame_component;
|
||||
|
||||
if (window()->IsWindowControlsOverlayEnabled()) {
|
||||
if (HitTestCaptionButton(close_button_, point))
|
||||
return HTCLOSE;
|
||||
if (HitTestCaptionButton(restore_button_, point))
|
||||
@@ -185,41 +176,53 @@ void OpaqueFrameView::Layout(PassKey) {
|
||||
|
||||
// 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;
|
||||
gfx::Rect client_bounds = GetBoundsForClientView();
|
||||
available_space_leading_x_ = client_bounds.x() + top_area_padding.leading;
|
||||
available_space_trailing_x_ =
|
||||
client_bounds.right() - top_area_padding.trailing;
|
||||
minimum_size_for_buttons_ =
|
||||
available_space_leading_x_ + width() - available_space_trailing_x_;
|
||||
(available_space_leading_x_ - client_bounds.x()) +
|
||||
(client_bounds.right() - 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;
|
||||
int container_x =
|
||||
placed_trailing_button_ ? available_space_trailing_x_ : client_bounds.x();
|
||||
caption_button_placeholder_container_->SetBounds(
|
||||
container_x, insets.top(), minimum_size_for_buttons_ - insets.width(),
|
||||
height - insets.top());
|
||||
|
||||
container_x, client_bounds.y(), minimum_size_for_buttons_, height);
|
||||
LayoutWindowControlsOverlay();
|
||||
}
|
||||
|
||||
void OpaqueFrameView::OnPaint(gfx::Canvas* canvas) {
|
||||
if (!window()->IsWindowControlsOverlayEnabled())
|
||||
if (frame()->IsFullscreen())
|
||||
return;
|
||||
|
||||
if (frame()->IsFullscreen())
|
||||
// Titlebar height must be at least the frame border insets to avoid
|
||||
// a negative height calculation in the GTK frame provider. We add 1 to
|
||||
// ensure it's always positive even when insets are 0.
|
||||
int top_area_height = RestoredFrameBorderInsets().top() + 1;
|
||||
|
||||
linux_frame_layout_->PaintWindowFrame(
|
||||
canvas, GetLocalBounds(), gfx::Rect(0, 0, width(), top_area_height),
|
||||
ShouldPaintAsActive());
|
||||
|
||||
if (!window()->IsWindowControlsOverlayEnabled())
|
||||
return;
|
||||
|
||||
UpdateFrameCaptionButtons();
|
||||
}
|
||||
|
||||
void OpaqueFrameView::PaintAsActiveChanged() {
|
||||
if (!window()->IsWindowControlsOverlayEnabled())
|
||||
return;
|
||||
if (window()->IsWindowControlsOverlayEnabled()) {
|
||||
UpdateCaptionButtonPlaceholderContainerBackground();
|
||||
UpdateFrameCaptionButtons();
|
||||
}
|
||||
|
||||
UpdateCaptionButtonPlaceholderContainerBackground();
|
||||
UpdateFrameCaptionButtons();
|
||||
InvalidateLayout();
|
||||
SchedulePaint();
|
||||
}
|
||||
|
||||
void OpaqueFrameView::UpdateFrameCaptionButtons() {
|
||||
@@ -284,7 +287,8 @@ void OpaqueFrameView::LayoutWindowControlsOverlay() {
|
||||
: caption_button_placeholder_container_->size().height() + 1;
|
||||
}
|
||||
int overlay_width = caption_button_placeholder_container_->size().width();
|
||||
int bounding_rect_width = width() - overlay_width;
|
||||
gfx::Rect client_bounds = GetBoundsForClientView();
|
||||
int bounding_rect_width = client_bounds.width() - overlay_width;
|
||||
auto bounding_rect =
|
||||
GetMirroredRect(gfx::Rect(0, 0, bounding_rect_width, overlay_height));
|
||||
|
||||
@@ -317,8 +321,9 @@ views::Button* OpaqueFrameView::CreateButton(
|
||||
}
|
||||
|
||||
gfx::Insets OpaqueFrameView::FrameBorderInsets(bool restored) const {
|
||||
return !restored && IsFrameCondensed() ? gfx::Insets()
|
||||
: RestoredFrameBorderInsets();
|
||||
return !restored && IsFrameCondensed()
|
||||
? gfx::Insets()
|
||||
: linux_frame_layout_->RestoredFrameBorderInsets();
|
||||
}
|
||||
|
||||
int OpaqueFrameView::FrameTopBorderThickness(bool restored) const {
|
||||
@@ -331,8 +336,7 @@ int OpaqueFrameView::FrameTopBorderThickness(bool restored) const {
|
||||
OpaqueFrameView::TopAreaPadding OpaqueFrameView::GetTopAreaPadding(
|
||||
bool has_leading_buttons,
|
||||
bool has_trailing_buttons) const {
|
||||
const auto padding = FrameBorderInsets(false);
|
||||
return TopAreaPadding{padding.left(), padding.right()};
|
||||
return {};
|
||||
}
|
||||
|
||||
bool OpaqueFrameView::IsFrameCondensed() const {
|
||||
@@ -340,7 +344,11 @@ bool OpaqueFrameView::IsFrameCondensed() const {
|
||||
}
|
||||
|
||||
gfx::Insets OpaqueFrameView::RestoredFrameBorderInsets() const {
|
||||
return {};
|
||||
return linux_frame_layout_->RestoredFrameBorderInsets();
|
||||
}
|
||||
|
||||
LinuxFrameLayout* OpaqueFrameView::GetLinuxFrameLayout() const {
|
||||
return linux_frame_layout_.get();
|
||||
}
|
||||
|
||||
gfx::Insets OpaqueFrameView::RestoredFrameEdgeInsets() const {
|
||||
@@ -378,7 +386,7 @@ int OpaqueFrameView::DefaultCaptionButtonY(bool restored) const {
|
||||
// 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()
|
||||
? 0
|
||||
: OpaqueBrowserFrameViewLayout::kFrameShadowThickness;
|
||||
}
|
||||
|
||||
@@ -462,7 +470,8 @@ void OpaqueFrameView::HideButton(views::FrameButton button_id) {
|
||||
void OpaqueFrameView::SetBoundsForButton(views::FrameButton button_id,
|
||||
views::Button* button,
|
||||
ButtonAlignment alignment) {
|
||||
const int caption_y = CaptionButtonY(button_id, false);
|
||||
gfx::Rect client_bounds = GetBoundsForClientView();
|
||||
const int caption_y = CaptionButtonY(button_id, false) + client_bounds.y();
|
||||
|
||||
// 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
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "chrome/browser/ui/view_ids.h"
|
||||
#include "shell/browser/ui/views/frameless_view.h"
|
||||
#include "shell/browser/ui/views/linux_frame_layout.h"
|
||||
#include "ui/base/metadata/metadata_header_macros.h"
|
||||
#include "ui/linux/window_button_order_observer.h"
|
||||
#include "ui/views/controls/button/button.h"
|
||||
@@ -40,6 +41,7 @@ class OpaqueFrameView : public FramelessView {
|
||||
int ResizingBorderHitTest(const gfx::Point& point) override;
|
||||
void InvalidateCaptionButtons() override;
|
||||
gfx::Insets RestoredFrameBorderInsets() const override;
|
||||
LinuxFrameLayout* GetLinuxFrameLayout() const override;
|
||||
|
||||
// views::FrameView:
|
||||
gfx::Rect GetBoundsForClientView() const override;
|
||||
@@ -163,6 +165,8 @@ class OpaqueFrameView : public FramelessView {
|
||||
bool leading_spacing,
|
||||
bool is_leading_button) const;
|
||||
|
||||
std::unique_ptr<LinuxFrameLayout> linux_frame_layout_;
|
||||
|
||||
// Window controls.
|
||||
raw_ptr<views::Button> minimize_button_;
|
||||
raw_ptr<views::Button> maximize_button_;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "components/input/native_web_keyboard_event.h"
|
||||
#include "shell/browser/native_window.h"
|
||||
#include "shell/browser/ui/views/menu_bar.h"
|
||||
#include "ui/events/keycodes/dom/keycode_converter.h"
|
||||
#include "ui/views/layout/box_layout.h"
|
||||
|
||||
namespace electron {
|
||||
@@ -21,9 +22,21 @@ bool IsAltKey(const input::NativeWebKeyboardEvent& event) {
|
||||
|
||||
bool IsAltModifier(const input::NativeWebKeyboardEvent& event) {
|
||||
using Mods = input::NativeWebKeyboardEvent::Modifiers;
|
||||
|
||||
// AltGraph (AltGr) should not be treated as a single Alt keypress for
|
||||
// menu-bar toggling.
|
||||
if (event.windows_key_code == ui::VKEY_ALTGR ||
|
||||
ui::KeycodeConverter::DomKeyToKeyString(event.dom_key) == "AltGraph") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (event.GetModifiers() & Mods::kKeyModifiers) == Mods::kAltKey;
|
||||
}
|
||||
|
||||
bool IsSingleAltKey(const input::NativeWebKeyboardEvent& event) {
|
||||
return IsAltKey(event) && IsAltModifier(event);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RootView::RootView(NativeWindow* window)
|
||||
@@ -98,7 +111,7 @@ void RootView::HandleKeyEvent(const input::NativeWebKeyboardEvent& event) {
|
||||
return;
|
||||
|
||||
// Show accelerator when "Alt" is pressed.
|
||||
if (menu_bar_visible_ && IsAltKey(event))
|
||||
if (menu_bar_visible_ && IsSingleAltKey(event))
|
||||
menu_bar_->SetAcceleratorVisibility(
|
||||
event.GetType() == blink::WebInputEvent::Type::kRawKeyDown);
|
||||
|
||||
@@ -121,11 +134,11 @@ void RootView::HandleKeyEvent(const input::NativeWebKeyboardEvent& event) {
|
||||
|
||||
// Toggle the menu bar only when a single Alt is released.
|
||||
if (event.GetType() == blink::WebInputEvent::Type::kRawKeyDown &&
|
||||
IsAltKey(event)) {
|
||||
IsSingleAltKey(event)) {
|
||||
// When a single Alt is pressed:
|
||||
menu_bar_alt_pressed_ = true;
|
||||
} else if (event.GetType() == blink::WebInputEvent::Type::kKeyUp &&
|
||||
IsAltKey(event) && menu_bar_alt_pressed_) {
|
||||
IsSingleAltKey(event) && menu_bar_alt_pressed_) {
|
||||
// When a single Alt is released right after a Alt is pressed:
|
||||
menu_bar_alt_pressed_ = false;
|
||||
if (menu_bar_autohide_)
|
||||
|
||||
@@ -275,12 +275,16 @@ bool WinFrameView::GetShouldPaintAsActive() {
|
||||
}
|
||||
|
||||
gfx::Size WinFrameView::GetMinimumSize() const {
|
||||
if (!window_)
|
||||
return gfx::Size();
|
||||
// Chromium expects minimum size to be in content dimensions on Windows
|
||||
// because it adds the frame border automatically in OnGetMinMaxInfo.
|
||||
return window_->GetContentMinimumSize();
|
||||
}
|
||||
|
||||
gfx::Size WinFrameView::GetMaximumSize() const {
|
||||
if (!window_)
|
||||
return gfx::Size();
|
||||
// Chromium expects minimum size to be in content dimensions on Windows
|
||||
// because it adds the frame border automatically in OnGetMinMaxInfo.
|
||||
gfx::Size size = window_->GetContentMaximumSize();
|
||||
|
||||
@@ -413,6 +413,7 @@ bool IsAllowedOption(const std::string_view option) {
|
||||
"--inspect-port",
|
||||
"--inspect-publish-uid",
|
||||
"--experimental-network-inspection",
|
||||
"--experimental-transform-types",
|
||||
});
|
||||
|
||||
// This should be aligned with what's possible to set via the process object.
|
||||
|
||||
@@ -3977,6 +3977,28 @@ describe('BrowserWindow module', () => {
|
||||
expect(webPreferences!.contextIsolation).to.equal(false);
|
||||
});
|
||||
|
||||
it('should apply zoomFactor from setWindowOpenHandler overrideBrowserWindowOptions', async () => {
|
||||
const w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
sandbox: true
|
||||
}
|
||||
});
|
||||
|
||||
w.webContents.setWindowOpenHandler(() => ({
|
||||
action: 'allow',
|
||||
overrideBrowserWindowOptions: {
|
||||
webPreferences: {
|
||||
zoomFactor: 2.0
|
||||
}
|
||||
}
|
||||
}));
|
||||
w.loadFile(path.join(fixtures, 'api', 'new-window.html'));
|
||||
const [childWindow] = await once(w.webContents, 'did-create-window') as [BrowserWindow, any];
|
||||
await once(childWindow.webContents, 'did-finish-load');
|
||||
expect(childWindow.webContents.getZoomFactor()).to.be.closeTo(2.0, 0.1);
|
||||
});
|
||||
|
||||
it('should set ipc event sender correctly', async () => {
|
||||
const w = new BrowserWindow({
|
||||
show: false,
|
||||
@@ -5903,6 +5925,23 @@ describe('BrowserWindow module', () => {
|
||||
});
|
||||
});
|
||||
|
||||
ifdescribe(process.platform === 'linux')('menu bar AltGr behavior', () => {
|
||||
it('does not toggle auto-hide menu bar visibility', async () => {
|
||||
const w = new BrowserWindow({ show: false, autoHideMenuBar: true });
|
||||
w.setMenuBarVisibility(false);
|
||||
expect(w.isMenuBarVisible()).to.be.false('isMenuBarVisible');
|
||||
|
||||
w.show();
|
||||
await once(w, 'show');
|
||||
w.webContents.focus();
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'AltGr' });
|
||||
w.webContents.sendInputEvent({ type: 'keyUp', keyCode: 'AltGr' });
|
||||
await setTimeout();
|
||||
|
||||
expect(w.isMenuBarVisible()).to.be.false('isMenuBarVisible');
|
||||
});
|
||||
});
|
||||
|
||||
ifdescribe(process.platform !== 'darwin')('when fullscreen state is changed', () => {
|
||||
it('correctly remembers state prior to fullscreen change', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
|
||||
@@ -59,10 +59,11 @@ describe('WebContentsView', () => {
|
||||
const browserWindow = new BrowserWindow();
|
||||
|
||||
const webContentsView = new WebContentsView();
|
||||
webContentsView.webContents.loadURL('about:blank');
|
||||
webContentsView.webContents.destroy();
|
||||
const wc = webContentsView.webContents;
|
||||
wc.loadURL('about:blank');
|
||||
wc.destroy();
|
||||
|
||||
const destroyed = once(webContentsView.webContents, 'destroyed');
|
||||
const destroyed = once(wc, 'destroyed');
|
||||
await destroyed;
|
||||
expect(() => browserWindow.contentView.addChildView(webContentsView)).to.throw(
|
||||
'Can\'t add a destroyed child view to a parent view'
|
||||
@@ -90,13 +91,14 @@ describe('WebContentsView', () => {
|
||||
const w = new BaseWindow({ show: false });
|
||||
const v = new View();
|
||||
const wcv = new WebContentsView();
|
||||
const wc = wcv.webContents;
|
||||
w.setContentView(v);
|
||||
v.addChildView(wcv);
|
||||
await wcv.webContents.loadURL('about:blank');
|
||||
const destroyed = once(wcv.webContents, 'destroyed');
|
||||
wcv.webContents.executeJavaScript('window.close()');
|
||||
await wc.loadURL('about:blank');
|
||||
const destroyed = once(wc, 'destroyed');
|
||||
wc.executeJavaScript('window.close()');
|
||||
await destroyed;
|
||||
expect(wcv.webContents.isDestroyed()).to.be.true();
|
||||
expect(wc.isDestroyed()).to.be.true();
|
||||
v.removeChildView(wcv);
|
||||
});
|
||||
|
||||
@@ -170,18 +172,19 @@ describe('WebContentsView', () => {
|
||||
it('does not crash when closed via window.close()', async () => {
|
||||
const bw = new BrowserWindow();
|
||||
const wcv = new WebContentsView();
|
||||
const wc = wcv.webContents;
|
||||
|
||||
await bw.loadURL('data:text/html,<h1>Main Window</h1>');
|
||||
bw.contentView.addChildView(wcv);
|
||||
|
||||
const dto = new Promise<boolean>((resolve) => {
|
||||
wcv.webContents.on('blur', () => {
|
||||
const devToolsOpen = wcv.webContents.isDevToolsOpened();
|
||||
wc.on('blur', () => {
|
||||
const devToolsOpen = !wc.isDestroyed() && wc.isDevToolsOpened();
|
||||
resolve(devToolsOpen);
|
||||
});
|
||||
});
|
||||
|
||||
wcv.webContents.loadURL('data:text/html,<script>window.close()</script>');
|
||||
wc.loadURL('data:text/html,<script>window.close()</script>');
|
||||
|
||||
const open = await dto;
|
||||
expect(open).to.be.false();
|
||||
|
||||
7
spec/fixtures/type-stripping/basic.ts
vendored
Normal file
7
spec/fixtures/type-stripping/basic.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { app } from 'electron/main';
|
||||
|
||||
const logMessage = (message: string): void => console.log(message);
|
||||
|
||||
logMessage('running');
|
||||
|
||||
app.exit(0);
|
||||
9
spec/fixtures/type-stripping/transform-types-node.ts
vendored
Normal file
9
spec/fixtures/type-stripping/transform-types-node.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
enum Test {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
}
|
||||
|
||||
console.log(Test.A);
|
||||
|
||||
process.exit(0);
|
||||
11
spec/fixtures/type-stripping/transform-types.ts
vendored
Normal file
11
spec/fixtures/type-stripping/transform-types.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import { app } from 'electron/main';
|
||||
|
||||
enum Test {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
}
|
||||
|
||||
console.log(Test.A);
|
||||
|
||||
app.exit(0);
|
||||
@@ -1030,4 +1030,26 @@ describe('node feature', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('type stripping', () => {
|
||||
it('strips TypeScript types automatically in the main process', async () => {
|
||||
const child = childProcess.spawn(process.execPath, [path.join(fixtures, 'type-stripping', 'basic.ts')]);
|
||||
const [code] = await once(child, 'exit');
|
||||
expect(code).to.equal(0);
|
||||
});
|
||||
|
||||
it('will not transform TypeScript types without --experimental-transform-types', async () => {
|
||||
const child = childProcess.spawn(process.execPath, [path.join(fixtures, 'type-stripping', 'transform-types-node.ts')], {
|
||||
env: { ELECTRON_RUN_AS_NODE: 'true' }
|
||||
});
|
||||
const [code] = await once(child, 'exit');
|
||||
expect(code).to.not.equal(0);
|
||||
});
|
||||
|
||||
it('transforms TypeScript types with --experimental-transform-types', async () => {
|
||||
const child = childProcess.spawn(process.execPath, ['--experimental-transform-types', path.join(fixtures, 'type-stripping', 'transform-types.ts')]);
|
||||
const [code] = await once(child, 'exit');
|
||||
expect(code).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user