feat: allow restoring macOS BrowserWindow state across relaunches

This commit is contained in:
Shelley Vohr 2024-02-13 14:17:23 +01:00
Родитель b684a98267
Коммит ec539d6f27
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: F13993A75599653C
8 изменённых файлов: 100 добавлений и 8 удалений

Просмотреть файл

@ -41,6 +41,7 @@
* `skipTaskbar` boolean (optional) _macOS_ _Windows_ - Whether to show the window in taskbar.
Default is `false`.
* `hiddenInMissionControl` boolean (optional) _macOS_ - Whether window should be hidden when the user toggles into mission control.
* `restoreOnRelaunch` boolean (optional) _macOS_ - Whether the window should launch with full fidelity across app restarts.
* `kiosk` boolean (optional) - Whether the window is in kiosk mode. Default is `false`.
* `title` string (optional) - Default window title. Default is `"Electron"`. If the HTML tag `<title>` is defined in the HTML file loaded by `loadURL()`, this property will be ignored.
* `icon` ([NativeImage](../native-image.md) | string) (optional) - The window icon. On Windows it is

Просмотреть файл

@ -28,11 +28,12 @@ void ElectronBrowserMainParts::PreCreateMainMessageLoop() {
PreCreateMainMessageLoopCommon();
// Prevent Cocoa from turning command-line arguments into
// |-application:openFiles:|, since we already handle them directly.
[[NSUserDefaults standardUserDefaults]
setObject:@"NO"
forKey:@"NSTreatUnknownArgumentsAsOpen"];
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
// Prevent Cocoa from turning command-line arguments into -[NSApplication
// application:openFile:], because they are handled directly. @"NO" looks
// like a mistake, but the value really is supposed to be a string.
[defaults setObject:@"NO" forKey:@"NSTreatUnknownArgumentsAsOpen"];
[defaults setBool:NO forKey:@"NSWindowRestoresWorkspaceAtLaunch"];
if (!device::GeolocationSystemPermissionManager::GetInstance()) {
device::GeolocationSystemPermissionManager::SetInstance(

Просмотреть файл

@ -164,6 +164,8 @@ class NativeWindowMac : public NativeWindow,
// Detach window from parent without destroying it.
void DetachChildren() override;
void OnWindowStateRestorationDataChanged(const std::vector<uint8_t>& data);
void NotifyWindowWillEnterFullScreen();
void NotifyWindowWillLeaveFullScreen();
@ -286,6 +288,8 @@ class NativeWindowMac : public NativeWindow,
bool user_set_bounds_maximized_ = false;
std::vector<uint8_t> state_restoration_data_;
// Simple (pre-Lion) Fullscreen Settings
bool always_simple_fullscreen_ = false;
bool is_simple_fullscreen_ = false;

Просмотреть файл

@ -47,7 +47,6 @@
#include "ui/gfx/skia_util.h"
#include "ui/gl/gpu_switching_manager.h"
#include "ui/views/background.h"
#include "ui/views/cocoa/native_widget_mac_ns_window_host.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/native_frame_view_mac.h"
@ -261,6 +260,18 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
window_delegate_ = [[ElectronNSWindowDelegate alloc] initWithShell:this];
[window_ setDelegate:window_delegate_];
if (options.Get(options::kRestoreOnRestart, &restore) && !restore) {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSData* restore_ns_data = [defaults objectForKey:@"state_restoration_data"];
if (restore_ns_data != nil) {
NSKeyedUnarchiver* decoder =
[[NSKeyedUnarchiver alloc] initForReadingFromData:restore_ns_data
error:nil];
[window_ restoreStateWithCoder:decoder];
state_restoration_data_.clear();
}
}
// Only use native parent window for non-modal windows.
if (parent && !is_modal()) {
SetParentWindow(parent);
@ -355,7 +366,14 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
original_level_ = [window_ level];
}
NativeWindowMac::~NativeWindowMac() = default;
NativeWindowMac::~NativeWindowMac() {
if (!state_restoration_data_.empty()) {
NSData* data = [NSData dataWithBytes:state_restoration_data_.data()
length:state_restoration_data_.size()];
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:data forKey:@"state_restoration_data"];
}
}
void NativeWindowMac::SetContentView(views::View* view) {
views::View* root_view = GetContentsView();
@ -1668,6 +1686,12 @@ void NativeWindowMac::NotifyWindowLeaveFullScreen() {
[window_ setTitlebarAppearsTransparent:YES];
}
void NativeWindowMac::OnWindowStateRestorationDataChanged(
const std::vector<uint8_t>& data) {
LOG(INFO) << "OnWindowStateRestorationDataChanged";
state_restoration_data_ = data;
}
void NativeWindowMac::NotifyWindowWillEnterFullScreen() {
UpdateVibrancyRadii(true);
}

Просмотреть файл

@ -29,9 +29,11 @@ class ScopedDisableResize {
} // namespace electron
@interface ElectronNSWindow : NativeWidgetMacNSWindow {
@interface ElectronNSWindow
: NativeWidgetMacNSWindow <NSKeyedArchiverDelegate> {
@private
raw_ptr<electron::NativeWindowMac> shell_;
BOOL _willUpdateRestorableState;
}
@property BOOL acceptsFirstMouse;
@property BOOL enableLargerThanScreen;

Просмотреть файл

@ -4,6 +4,7 @@
#include "shell/browser/ui/cocoa/electron_ns_window.h"
#include "base/mac/mac_util.h"
#include "base/strings/sys_string_conversions.h"
#include "shell/browser/native_window_mac.h"
#include "shell/browser/ui/cocoa/electron_preview_item.h"
@ -23,6 +24,7 @@ int ScopedDisableResize::disable_resize_ = 0;
@interface NSWindow (PrivateAPI)
- (NSImage*)_cornerMask;
- (int64_t)_resizeDirectionForMouseLocation:(CGPoint)location;
- (BOOL)_isConsideredOpenForPersistentState;
@end
// See components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm
@ -186,8 +188,64 @@ void SwizzleSwipeWithEvent(NSView* view, SEL swiz_selector) {
return nil;
}
// Called when the window is the delegate of the archiver passed to
// |-encodeRestorableStateWithCoder:|, below. It prevents the archiver from
// trying to encode the window or an NSView, say, to represent the first
// responder. When AppKit calls |-encodeRestorableStateWithCoder:|, it
// accomplishes the same thing by passing a custom coder.
- (id)archiver:(NSKeyedArchiver*)archiver willEncodeObject:(id)object {
if (object == self)
return nil;
if ([object isKindOfClass:[NSView class]])
return nil;
return object;
}
- (void)saveRestorableState {
if (![self _isConsideredOpenForPersistentState])
return;
// On macOS 12+, create restorable state archives with secure encoding.
NSKeyedArchiver* encoder = [[NSKeyedArchiver alloc]
initRequiringSecureCoding:base::mac::MacOSMajorVersion() >= 12];
encoder.delegate = self;
[self encodeRestorableStateWithCoder:encoder];
[encoder finishEncoding];
NSData* restorableStateData = encoder.encodedData;
auto* bytes = static_cast<uint8_t const*>(restorableStateData.bytes);
shell_->OnWindowStateRestorationDataChanged(
std::vector<uint8_t>(bytes, bytes + restorableStateData.length));
_willUpdateRestorableState = NO;
}
// AppKit calls -invalidateRestorableState when a property of the window which
// affects its restorable state changes.
- (void)invalidateRestorableState {
[super invalidateRestorableState];
if ([self _isConsideredOpenForPersistentState]) {
if (_willUpdateRestorableState)
return;
_willUpdateRestorableState = YES;
[self performSelectorOnMainThread:@selector(saveRestorableState)
withObject:nil
waitUntilDone:NO
modes:@[ NSDefaultRunLoopMode ]];
} else if (_willUpdateRestorableState) {
_willUpdateRestorableState = NO;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
}
// NSWindow overrides.
- (void)dealloc {
_willUpdateRestorableState = YES;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
- (void)rotateWithEvent:(NSEvent*)event {
shell_->NotifyWindowRotateGesture(event.rotation);
}

Просмотреть файл

@ -26,6 +26,7 @@ const char kMovable[] = "movable";
const char kMinimizable[] = "minimizable";
const char kMaximizable[] = "maximizable";
const char kFullScreenable[] = "fullscreenable";
const char kRestoreOnRestart[] = "restoreOnRestart";
const char kClosable[] = "closable";
const char kFullscreen[] = "fullscreen";
const char kTrafficLightPosition[] = "trafficLightPosition";

Просмотреть файл

@ -29,6 +29,7 @@ extern const char kMovable[];
extern const char kMinimizable[];
extern const char kMaximizable[];
extern const char kFullScreenable[];
extern const char kRestoreOnRestart[];
extern const char kClosable[];
extern const char kHiddenInMissionControl[];
extern const char kFullscreen[];