Merge pull request #6140 from electron/parent
Add support for child windows
This commit is contained in:
commit
3428874907
16 changed files with 532 additions and 28 deletions
|
@ -95,9 +95,16 @@ Window::Window(v8::Isolate* isolate, const mate::Dictionary& options) {
|
|||
mate::Dictionary(isolate, web_contents->GetWrapper()).Set(
|
||||
"browserWindowOptions", options);
|
||||
|
||||
// The parent window.
|
||||
mate::Handle<Window> parent;
|
||||
if (options.Get("parent", &parent))
|
||||
parent_window_.Reset(isolate, parent.ToV8());
|
||||
|
||||
// Creates BrowserWindow.
|
||||
window_.reset(NativeWindow::Create(web_contents->managed_web_contents(),
|
||||
options));
|
||||
window_.reset(NativeWindow::Create(
|
||||
web_contents->managed_web_contents(),
|
||||
options,
|
||||
parent.IsEmpty() ? nullptr : parent->window_.get()));
|
||||
web_contents->SetOwnerWindow(window_.get());
|
||||
window_->InitFromOptions(options);
|
||||
window_->AddObserver(this);
|
||||
|
@ -120,10 +127,32 @@ Window::~Window() {
|
|||
base::MessageLoop::current()->DeleteSoon(FROM_HERE, window_.release());
|
||||
}
|
||||
|
||||
void Window::AfterInit(v8::Isolate* isolate) {
|
||||
mate::TrackableObject<Window>::AfterInit(isolate);
|
||||
|
||||
// We can only append this window to parent window's child windows after this
|
||||
// window's JS wrapper gets initialized.
|
||||
mate::Handle<Window> parent;
|
||||
if (!parent_window_.IsEmpty() &&
|
||||
mate::ConvertFromV8(isolate, GetParentWindow(), &parent))
|
||||
parent->child_windows_.Set(isolate, ID(), GetWrapper());
|
||||
}
|
||||
|
||||
void Window::WillCloseWindow(bool* prevent_default) {
|
||||
*prevent_default = Emit("close");
|
||||
}
|
||||
|
||||
void Window::WillDestoryNativeObject() {
|
||||
// Close all child windows before closing current window.
|
||||
v8::Locker locker(isolate());
|
||||
v8::HandleScope handle_scope(isolate());
|
||||
for (v8::Local<v8::Value> value : child_windows_.Values(isolate())) {
|
||||
mate::Handle<Window> child;
|
||||
if (mate::ConvertFromV8(isolate(), value, &child))
|
||||
child->window_->CloseImmediately();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::OnWindowClosed() {
|
||||
api_web_contents_->DestroyWebContents();
|
||||
|
||||
|
@ -136,6 +165,8 @@ void Window::OnWindowClosed() {
|
|||
|
||||
Emit("closed");
|
||||
|
||||
RemoveFromParentChildWindows();
|
||||
|
||||
// Destroy the native class when window is closed.
|
||||
base::MessageLoop::current()->PostTask(FROM_HERE, GetDestroyClosure());
|
||||
}
|
||||
|
@ -280,6 +311,10 @@ void Window::Show() {
|
|||
}
|
||||
|
||||
void Window::ShowInactive() {
|
||||
// This method doesn't make sense for modal window..
|
||||
if (IsModal())
|
||||
return;
|
||||
|
||||
window_->ShowInactive();
|
||||
}
|
||||
|
||||
|
@ -291,6 +326,10 @@ bool Window::IsVisible() {
|
|||
return window_->IsVisible();
|
||||
}
|
||||
|
||||
bool Window::IsEnabled() {
|
||||
return window_->IsEnabled();
|
||||
}
|
||||
|
||||
void Window::Maximize() {
|
||||
window_->Maximize();
|
||||
}
|
||||
|
@ -650,6 +689,42 @@ void Window::SetAspectRatio(double aspect_ratio, mate::Arguments* args) {
|
|||
window_->SetAspectRatio(aspect_ratio, extra_size);
|
||||
}
|
||||
|
||||
void Window::SetParentWindow(v8::Local<v8::Value> value,
|
||||
mate::Arguments* args) {
|
||||
if (IsModal()) {
|
||||
args->ThrowError("Can not be called for modal window");
|
||||
return;
|
||||
}
|
||||
|
||||
mate::Handle<Window> parent;
|
||||
if (value->IsNull()) {
|
||||
RemoveFromParentChildWindows();
|
||||
parent_window_.Reset();
|
||||
window_->SetParentWindow(nullptr);
|
||||
} else if (mate::ConvertFromV8(isolate(), value, &parent)) {
|
||||
parent_window_.Reset(isolate(), value);
|
||||
window_->SetParentWindow(parent->window_.get());
|
||||
parent->child_windows_.Set(isolate(), ID(), GetWrapper());
|
||||
} else {
|
||||
args->ThrowError("Must pass BrowserWindow instance or null");
|
||||
}
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> Window::GetParentWindow() const {
|
||||
if (parent_window_.IsEmpty())
|
||||
return v8::Null(isolate());
|
||||
else
|
||||
return v8::Local<v8::Value>::New(isolate(), parent_window_);
|
||||
}
|
||||
|
||||
std::vector<v8::Local<v8::Object>> Window::GetChildWindows() const {
|
||||
return child_windows_.Values(isolate());
|
||||
}
|
||||
|
||||
bool Window::IsModal() const {
|
||||
return window_->is_modal();
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> Window::GetNativeWindowHandle() {
|
||||
gfx::AcceleratedWidget handle = window_->GetAcceleratedWidget();
|
||||
return ToBuffer(
|
||||
|
@ -675,6 +750,17 @@ v8::Local<v8::Value> Window::WebContents(v8::Isolate* isolate) {
|
|||
return v8::Local<v8::Value>::New(isolate, web_contents_);
|
||||
}
|
||||
|
||||
void Window::RemoveFromParentChildWindows() {
|
||||
if (parent_window_.IsEmpty())
|
||||
return;
|
||||
|
||||
mate::Handle<Window> parent;
|
||||
if (!mate::ConvertFromV8(isolate(), GetParentWindow(), &parent))
|
||||
return;
|
||||
|
||||
parent->child_windows_.Remove(ID());
|
||||
}
|
||||
|
||||
// static
|
||||
void Window::BuildPrototype(v8::Isolate* isolate,
|
||||
v8::Local<v8::ObjectTemplate> prototype) {
|
||||
|
@ -688,6 +774,7 @@ void Window::BuildPrototype(v8::Isolate* isolate,
|
|||
.SetMethod("showInactive", &Window::ShowInactive)
|
||||
.SetMethod("hide", &Window::Hide)
|
||||
.SetMethod("isVisible", &Window::IsVisible)
|
||||
.SetMethod("isEnabled", &Window::IsEnabled)
|
||||
.SetMethod("maximize", &Window::Maximize)
|
||||
.SetMethod("unmaximize", &Window::Unmaximize)
|
||||
.SetMethod("isMaximized", &Window::IsMaximized)
|
||||
|
@ -697,6 +784,12 @@ void Window::BuildPrototype(v8::Isolate* isolate,
|
|||
.SetMethod("setFullScreen", &Window::SetFullScreen)
|
||||
.SetMethod("isFullScreen", &Window::IsFullscreen)
|
||||
.SetMethod("setAspectRatio", &Window::SetAspectRatio)
|
||||
#if !defined(OS_WIN)
|
||||
.SetMethod("setParentWindow", &Window::SetParentWindow)
|
||||
#endif
|
||||
.SetMethod("getParentWindow", &Window::GetParentWindow)
|
||||
.SetMethod("getChildWindows", &Window::GetChildWindows)
|
||||
.SetMethod("isModal", &Window::IsModal)
|
||||
.SetMethod("getNativeWindowHandle", &Window::GetNativeWindowHandle)
|
||||
.SetMethod("getBounds", &Window::GetBounds)
|
||||
.SetMethod("setBounds", &Window::SetBounds)
|
||||
|
|
|
@ -6,15 +6,16 @@
|
|||
#define ATOM_BROWSER_API_ATOM_API_WINDOW_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
#include "atom/browser/api/trackable_object.h"
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "atom/browser/native_window_observer.h"
|
||||
#include "atom/common/api/atom_api_native_image.h"
|
||||
#include "atom/common/key_weak_map.h"
|
||||
#include "native_mate/handle.h"
|
||||
|
||||
class GURL;
|
||||
|
@ -54,8 +55,12 @@ class Window : public mate::TrackableObject<Window>,
|
|||
Window(v8::Isolate* isolate, const mate::Dictionary& options);
|
||||
~Window() override;
|
||||
|
||||
// TrackableObject:
|
||||
void AfterInit(v8::Isolate* isolate) override;
|
||||
|
||||
// NativeWindowObserver:
|
||||
void WillCloseWindow(bool* prevent_default) override;
|
||||
void WillDestoryNativeObject() override;
|
||||
void OnWindowClosed() override;
|
||||
void OnWindowBlur() override;
|
||||
void OnWindowFocus() override;
|
||||
|
@ -94,6 +99,7 @@ class Window : public mate::TrackableObject<Window>,
|
|||
void ShowInactive();
|
||||
void Hide();
|
||||
bool IsVisible();
|
||||
bool IsEnabled();
|
||||
void Maximize();
|
||||
void Unmaximize();
|
||||
bool IsMaximized();
|
||||
|
@ -159,6 +165,10 @@ class Window : public mate::TrackableObject<Window>,
|
|||
void SetMenuBarVisibility(bool visible);
|
||||
bool IsMenuBarVisible();
|
||||
void SetAspectRatio(double aspect_ratio, mate::Arguments* args);
|
||||
void SetParentWindow(v8::Local<v8::Value> value, mate::Arguments* args);
|
||||
v8::Local<v8::Value> GetParentWindow() const;
|
||||
std::vector<v8::Local<v8::Object>> GetChildWindows() const;
|
||||
bool IsModal() const;
|
||||
v8::Local<v8::Value> GetNativeWindowHandle();
|
||||
|
||||
#if defined(OS_WIN)
|
||||
|
@ -181,6 +191,9 @@ class Window : public mate::TrackableObject<Window>,
|
|||
int32_t ID() const;
|
||||
v8::Local<v8::Value> WebContents(v8::Isolate* isolate);
|
||||
|
||||
// Remove this window from parent window's |child_windows_|.
|
||||
void RemoveFromParentChildWindows();
|
||||
|
||||
#if defined(OS_WIN)
|
||||
typedef std::map<UINT, MessageCallback> MessageCallbackMap;
|
||||
MessageCallbackMap messages_callback_map_;
|
||||
|
@ -188,6 +201,8 @@ class Window : public mate::TrackableObject<Window>,
|
|||
|
||||
v8::Global<v8::Value> web_contents_;
|
||||
v8::Global<v8::Value> menu_;
|
||||
v8::Global<v8::Value> parent_window_;
|
||||
KeyWeakMap<int> child_windows_;
|
||||
|
||||
api::WebContents* api_web_contents_;
|
||||
|
||||
|
|
|
@ -46,7 +46,8 @@ namespace atom {
|
|||
|
||||
NativeWindow::NativeWindow(
|
||||
brightray::InspectableWebContents* inspectable_web_contents,
|
||||
const mate::Dictionary& options)
|
||||
const mate::Dictionary& options,
|
||||
NativeWindow* parent)
|
||||
: content::WebContentsObserver(inspectable_web_contents->GetWebContents()),
|
||||
has_frame_(true),
|
||||
transparent_(false),
|
||||
|
@ -56,12 +57,17 @@ NativeWindow::NativeWindow(
|
|||
sheet_offset_x_(0.0),
|
||||
sheet_offset_y_(0.0),
|
||||
aspect_ratio_(0.0),
|
||||
parent_(parent),
|
||||
is_modal_(false),
|
||||
inspectable_web_contents_(inspectable_web_contents),
|
||||
weak_factory_(this) {
|
||||
options.Get(options::kFrame, &has_frame_);
|
||||
options.Get(options::kTransparent, &transparent_);
|
||||
options.Get(options::kEnableLargerThanScreen, &enable_larger_than_screen_);
|
||||
|
||||
if (parent)
|
||||
options.Get("modal", &is_modal_);
|
||||
|
||||
// Tell the content module to initialize renderer widget with transparent
|
||||
// mode.
|
||||
ui::GpuSwitchingManager::SetTransparent(transparent_);
|
||||
|
@ -292,6 +298,10 @@ bool NativeWindow::HasModalDialog() {
|
|||
return has_dialog_attached_;
|
||||
}
|
||||
|
||||
void NativeWindow::SetParentWindow(NativeWindow* parent) {
|
||||
parent_ = parent;
|
||||
}
|
||||
|
||||
void NativeWindow::FocusOnWebView() {
|
||||
web_contents()->GetRenderViewHost()->GetWidget()->Focus();
|
||||
}
|
||||
|
@ -397,6 +407,9 @@ void NativeWindow::CloseContents(content::WebContents* source) {
|
|||
inspectable_web_contents_ = nullptr;
|
||||
Observe(nullptr);
|
||||
|
||||
FOR_EACH_OBSERVER(NativeWindowObserver, observers_,
|
||||
WillDestoryNativeObject());
|
||||
|
||||
// When the web contents is gone, close the window immediately, but the
|
||||
// memory will not be freed until you call delete.
|
||||
// In this way, it would be safe to manage windows via smart pointers. If you
|
||||
|
@ -611,7 +624,7 @@ void NativeWindow::ScheduleUnresponsiveEvent(int ms) {
|
|||
void NativeWindow::NotifyWindowUnresponsive() {
|
||||
window_unresposive_closure_.Cancel();
|
||||
|
||||
if (!is_closed_ && !HasModalDialog())
|
||||
if (!is_closed_ && !HasModalDialog() && IsEnabled())
|
||||
FOR_EACH_OBSERVER(NativeWindowObserver,
|
||||
observers_,
|
||||
OnRendererUnresponsive());
|
||||
|
|
|
@ -81,7 +81,8 @@ class NativeWindow : public base::SupportsUserData,
|
|||
// managing the window's live.
|
||||
static NativeWindow* Create(
|
||||
brightray::InspectableWebContents* inspectable_web_contents,
|
||||
const mate::Dictionary& options);
|
||||
const mate::Dictionary& options,
|
||||
NativeWindow* parent = nullptr);
|
||||
|
||||
// Find a window from its WebContents
|
||||
static NativeWindow* FromWebContents(content::WebContents* web_contents);
|
||||
|
@ -97,6 +98,7 @@ class NativeWindow : public base::SupportsUserData,
|
|||
virtual void ShowInactive() = 0;
|
||||
virtual void Hide() = 0;
|
||||
virtual bool IsVisible() = 0;
|
||||
virtual bool IsEnabled() = 0;
|
||||
virtual void Maximize() = 0;
|
||||
virtual void Unmaximize() = 0;
|
||||
virtual bool IsMaximized() = 0;
|
||||
|
@ -158,6 +160,7 @@ class NativeWindow : public base::SupportsUserData,
|
|||
virtual void SetFocusable(bool focusable);
|
||||
virtual void SetMenu(ui::MenuModel* menu);
|
||||
virtual bool HasModalDialog();
|
||||
virtual void SetParentWindow(NativeWindow* parent);
|
||||
virtual gfx::NativeWindow GetNativeWindow() = 0;
|
||||
virtual gfx::AcceleratedWidget GetAcceleratedWidget() = 0;
|
||||
|
||||
|
@ -255,9 +258,13 @@ class NativeWindow : public base::SupportsUserData,
|
|||
has_dialog_attached_ = has_dialog_attached;
|
||||
}
|
||||
|
||||
NativeWindow* parent() const { return parent_; }
|
||||
bool is_modal() const { return is_modal_; }
|
||||
|
||||
protected:
|
||||
NativeWindow(brightray::InspectableWebContents* inspectable_web_contents,
|
||||
const mate::Dictionary& options);
|
||||
const mate::Dictionary& options,
|
||||
NativeWindow* parent);
|
||||
|
||||
// Convert draggable regions in raw format to SkRegion format. Caller is
|
||||
// responsible for deleting the returned SkRegion instance.
|
||||
|
@ -329,6 +336,12 @@ class NativeWindow : public base::SupportsUserData,
|
|||
double aspect_ratio_;
|
||||
gfx::Size aspect_ratio_extraSize_;
|
||||
|
||||
// The parent window, it is guaranteed to be valid during this window's life.
|
||||
NativeWindow* parent_;
|
||||
|
||||
// Is this a modal window.
|
||||
bool is_modal_;
|
||||
|
||||
// The page this window is viewing.
|
||||
brightray::InspectableWebContents* inspectable_web_contents_;
|
||||
|
||||
|
|
|
@ -22,7 +22,8 @@ namespace atom {
|
|||
class NativeWindowMac : public NativeWindow {
|
||||
public:
|
||||
NativeWindowMac(brightray::InspectableWebContents* inspectable_web_contents,
|
||||
const mate::Dictionary& options);
|
||||
const mate::Dictionary& options,
|
||||
NativeWindow* parent);
|
||||
~NativeWindowMac() override;
|
||||
|
||||
// NativeWindow:
|
||||
|
@ -34,6 +35,7 @@ class NativeWindowMac : public NativeWindow {
|
|||
void ShowInactive() override;
|
||||
void Hide() override;
|
||||
bool IsVisible() override;
|
||||
bool IsEnabled() override;
|
||||
void Maximize() override;
|
||||
void Unmaximize() override;
|
||||
bool IsMaximized() override;
|
||||
|
@ -78,6 +80,7 @@ class NativeWindowMac : public NativeWindow {
|
|||
bool IsDocumentEdited() override;
|
||||
void SetIgnoreMouseEvents(bool ignore) override;
|
||||
bool HasModalDialog() override;
|
||||
void SetParentWindow(NativeWindow* parent) override;
|
||||
gfx::NativeWindow GetNativeWindow() override;
|
||||
gfx::AcceleratedWidget GetAcceleratedWidget() override;
|
||||
void SetProgressBar(double progress) override;
|
||||
|
@ -144,9 +147,6 @@ class NativeWindowMac : public NativeWindow {
|
|||
// The "titleBarStyle" option.
|
||||
TitleBarStyle title_bar_style_;
|
||||
|
||||
// Whether to hide the native toolbar under fullscreen mode.
|
||||
bool should_hide_native_toolbar_in_fullscreen_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(NativeWindowMac);
|
||||
};
|
||||
|
||||
|
|
|
@ -429,8 +429,9 @@ namespace atom {
|
|||
|
||||
NativeWindowMac::NativeWindowMac(
|
||||
brightray::InspectableWebContents* web_contents,
|
||||
const mate::Dictionary& options)
|
||||
: NativeWindow(web_contents, options),
|
||||
const mate::Dictionary& options,
|
||||
NativeWindow* parent)
|
||||
: NativeWindow(web_contents, options, parent),
|
||||
is_kiosk_(false),
|
||||
attention_request_id_(0),
|
||||
title_bar_style_(NORMAL) {
|
||||
|
@ -501,6 +502,11 @@ NativeWindowMac::NativeWindowMac(
|
|||
window_delegate_.reset([[AtomNSWindowDelegate alloc] initWithShell:this]);
|
||||
[window_ setDelegate:window_delegate_];
|
||||
|
||||
// Only use native parent window for non-modal windows.
|
||||
if (parent && !is_modal()) {
|
||||
SetParentWindow(parent);
|
||||
}
|
||||
|
||||
if (transparent()) {
|
||||
// Setting the background color to clear will also hide the shadow.
|
||||
[window_ setBackgroundColor:[NSColor clearColor]];
|
||||
|
@ -595,6 +601,12 @@ NativeWindowMac::~NativeWindowMac() {
|
|||
}
|
||||
|
||||
void NativeWindowMac::Close() {
|
||||
// When this is a sheet showing, performClose won't work.
|
||||
if (is_modal() && parent() && IsVisible()) {
|
||||
CloseImmediately();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsClosable()) {
|
||||
WindowList::WindowCloseCancelled(this);
|
||||
return;
|
||||
|
@ -624,6 +636,12 @@ bool NativeWindowMac::IsFocused() {
|
|||
}
|
||||
|
||||
void NativeWindowMac::Show() {
|
||||
if (is_modal() && parent()) {
|
||||
[parent()->GetNativeWindow() beginSheet:window_
|
||||
completionHandler:^(NSModalResponse) {}];
|
||||
return;
|
||||
}
|
||||
|
||||
// This method is supposed to put focus on window, however if the app does not
|
||||
// have focus then "makeKeyAndOrderFront" will only show the window.
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
|
@ -636,6 +654,12 @@ void NativeWindowMac::ShowInactive() {
|
|||
}
|
||||
|
||||
void NativeWindowMac::Hide() {
|
||||
if (is_modal() && parent()) {
|
||||
[window_ orderOut:nil];
|
||||
[parent()->GetNativeWindow() endSheet:window_];
|
||||
return;
|
||||
}
|
||||
|
||||
[window_ orderOut:nil];
|
||||
}
|
||||
|
||||
|
@ -643,6 +667,10 @@ bool NativeWindowMac::IsVisible() {
|
|||
return [window_ isVisible];
|
||||
}
|
||||
|
||||
bool NativeWindowMac::IsEnabled() {
|
||||
return [window_ attachedSheet] == nil;
|
||||
}
|
||||
|
||||
void NativeWindowMac::Maximize() {
|
||||
if (IsMaximized())
|
||||
return;
|
||||
|
@ -911,6 +939,21 @@ bool NativeWindowMac::HasModalDialog() {
|
|||
return [window_ attachedSheet] != nil;
|
||||
}
|
||||
|
||||
void NativeWindowMac::SetParentWindow(NativeWindow* parent) {
|
||||
if (is_modal())
|
||||
return;
|
||||
|
||||
NativeWindow::SetParentWindow(parent);
|
||||
|
||||
// Remove current parent window.
|
||||
if ([window_ parentWindow])
|
||||
[[window_ parentWindow] removeChildWindow:window_];
|
||||
|
||||
// Set new current window.
|
||||
if (parent)
|
||||
[parent->GetNativeWindow() addChildWindow:window_ ordered:NSWindowAbove];
|
||||
}
|
||||
|
||||
gfx::NativeWindow NativeWindowMac::GetNativeWindow() {
|
||||
return window_;
|
||||
}
|
||||
|
@ -1132,8 +1175,9 @@ void NativeWindowMac::SetCollectionBehavior(bool on, NSUInteger flag) {
|
|||
// static
|
||||
NativeWindow* NativeWindow::Create(
|
||||
brightray::InspectableWebContents* inspectable_web_contents,
|
||||
const mate::Dictionary& options) {
|
||||
return new NativeWindowMac(inspectable_web_contents, options);
|
||||
const mate::Dictionary& options,
|
||||
NativeWindow* parent) {
|
||||
return new NativeWindowMac(inspectable_web_contents, options, parent);
|
||||
}
|
||||
|
||||
} // namespace atom
|
||||
|
|
|
@ -33,6 +33,9 @@ class NativeWindowObserver {
|
|||
// Called when the window is gonna closed.
|
||||
virtual void WillCloseWindow(bool* prevent_default) {}
|
||||
|
||||
// Called before the native window object is going to be destroyed.
|
||||
virtual void WillDestoryNativeObject() {}
|
||||
|
||||
// Called when the window is closed.
|
||||
virtual void OnWindowClosed() {}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "ui/views/window/client_view.h"
|
||||
#include "ui/views/widget/native_widget_private.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
#include "ui/wm/core/window_util.h"
|
||||
#include "ui/wm/core/shadow_types.h"
|
||||
|
||||
#if defined(USE_X11)
|
||||
|
@ -36,6 +37,7 @@
|
|||
#include "atom/browser/ui/views/global_menu_bar_x11.h"
|
||||
#include "atom/browser/ui/views/frameless_view.h"
|
||||
#include "atom/browser/ui/views/native_frame_view.h"
|
||||
#include "atom/browser/ui/x/event_disabler.h"
|
||||
#include "atom/browser/ui/x/window_state_watcher.h"
|
||||
#include "atom/browser/ui/x/x_window_utils.h"
|
||||
#include "base/strings/string_util.h"
|
||||
|
@ -125,14 +127,16 @@ class NativeWindowClientView : public views::ClientView {
|
|||
|
||||
NativeWindowViews::NativeWindowViews(
|
||||
brightray::InspectableWebContents* web_contents,
|
||||
const mate::Dictionary& options)
|
||||
: NativeWindow(web_contents, options),
|
||||
const mate::Dictionary& options,
|
||||
NativeWindow* parent)
|
||||
: NativeWindow(web_contents, options, parent),
|
||||
window_(new views::Widget),
|
||||
web_view_(inspectable_web_contents()->GetView()->GetView()),
|
||||
menu_bar_autohide_(false),
|
||||
menu_bar_visible_(false),
|
||||
menu_bar_alt_pressed_(false),
|
||||
keyboard_event_handler_(new views::UnhandledKeyboardEventHandler),
|
||||
disable_count_(0),
|
||||
use_content_size_(false),
|
||||
movable_(true),
|
||||
resizable_(true),
|
||||
|
@ -186,6 +190,9 @@ NativeWindowViews::NativeWindowViews(
|
|||
params.activatable = views::Widget::InitParams::ACTIVATABLE_NO;
|
||||
|
||||
#if defined(OS_WIN)
|
||||
if (parent)
|
||||
params.parent = parent->GetNativeWindow();
|
||||
|
||||
params.native_widget =
|
||||
new views::DesktopNativeWidgetAura(window_.get());
|
||||
atom_desktop_window_tree_host_win_ = new AtomDesktopWindowTreeHostWin(
|
||||
|
@ -236,12 +243,23 @@ NativeWindowViews::NativeWindowViews(
|
|||
state_atom_list.push_back(GetAtom("_NET_WM_STATE_FULLSCREEN"));
|
||||
}
|
||||
|
||||
std::string window_type;
|
||||
options.Get(options::kType, &window_type);
|
||||
|
||||
if (parent) {
|
||||
SetParentWindow(parent);
|
||||
// Force using dialog type for child window.
|
||||
window_type = "dialog";
|
||||
// Modal window needs the _NET_WM_STATE_MODAL hint.
|
||||
if (is_modal())
|
||||
state_atom_list.push_back(GetAtom("_NET_WM_STATE_MODAL"));
|
||||
}
|
||||
|
||||
ui::SetAtomArrayProperty(GetAcceleratedWidget(), "_NET_WM_STATE", "ATOM",
|
||||
state_atom_list);
|
||||
|
||||
// Set the _NET_WM_WINDOW_TYPE.
|
||||
std::string window_type;
|
||||
if (options.Get(options::kType, &window_type))
|
||||
if (!window_type.empty())
|
||||
SetWindowType(GetAcceleratedWidget(), window_type);
|
||||
#endif
|
||||
|
||||
|
@ -337,6 +355,9 @@ bool NativeWindowViews::IsFocused() {
|
|||
}
|
||||
|
||||
void NativeWindowViews::Show() {
|
||||
if (is_modal() && NativeWindow::parent())
|
||||
static_cast<NativeWindowViews*>(NativeWindow::parent())->SetEnabled(false);
|
||||
|
||||
window_->native_widget_private()->ShowWithWindowState(GetRestoredState());
|
||||
|
||||
NotifyWindowShow();
|
||||
|
@ -359,6 +380,9 @@ void NativeWindowViews::ShowInactive() {
|
|||
}
|
||||
|
||||
void NativeWindowViews::Hide() {
|
||||
if (is_modal() && NativeWindow::parent())
|
||||
static_cast<NativeWindowViews*>(NativeWindow::parent())->SetEnabled(true);
|
||||
|
||||
window_->Hide();
|
||||
|
||||
NotifyWindowHide();
|
||||
|
@ -373,6 +397,14 @@ bool NativeWindowViews::IsVisible() {
|
|||
return window_->IsVisible();
|
||||
}
|
||||
|
||||
bool NativeWindowViews::IsEnabled() {
|
||||
#if defined(OS_WIN)
|
||||
return ::IsWindowEnabled(GetAcceleratedWidget());
|
||||
#elif defined(USE_X11)
|
||||
return !event_disabler_.get();
|
||||
#endif
|
||||
}
|
||||
|
||||
void NativeWindowViews::Maximize() {
|
||||
if (IsVisible())
|
||||
window_->Maximize();
|
||||
|
@ -772,6 +804,32 @@ void NativeWindowViews::SetMenu(ui::MenuModel* menu_model) {
|
|||
Layout();
|
||||
}
|
||||
|
||||
void NativeWindowViews::SetParentWindow(NativeWindow* parent) {
|
||||
NativeWindow::SetParentWindow(parent);
|
||||
|
||||
#if defined(USE_X11)
|
||||
XDisplay* xdisplay = gfx::GetXDisplay();
|
||||
XSetTransientForHint(
|
||||
xdisplay, GetAcceleratedWidget(),
|
||||
parent? parent->GetAcceleratedWidget() : DefaultRootWindow(xdisplay));
|
||||
#elif defined(OS_WIN) && defined(DEBUG)
|
||||
// Should work, but does not, it seems that the views toolkit doesn't support
|
||||
// reparenting on desktop.
|
||||
if (parent) {
|
||||
::SetParent(GetAcceleratedWidget(), parent->GetAcceleratedWidget());
|
||||
views::Widget::ReparentNativeView(GetNativeWindow(),
|
||||
parent->GetNativeWindow());
|
||||
wm::AddTransientChild(parent->GetNativeWindow(), GetNativeWindow());
|
||||
} else {
|
||||
if (!GetNativeWindow()->parent())
|
||||
return;
|
||||
::SetParent(GetAcceleratedWidget(), NULL);
|
||||
views::Widget::ReparentNativeView(GetNativeWindow(), nullptr);
|
||||
wm::RemoveTransientChild(GetNativeWindow()->parent(), GetNativeWindow());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
gfx::NativeWindow NativeWindowViews::GetNativeWindow() {
|
||||
return window_->GetNativeWindow();
|
||||
}
|
||||
|
@ -867,6 +925,33 @@ void NativeWindowViews::SetIcon(const gfx::ImageSkia& icon) {
|
|||
}
|
||||
#endif
|
||||
|
||||
void NativeWindowViews::SetEnabled(bool enable) {
|
||||
// Handle multiple calls of SetEnabled correctly.
|
||||
if (enable) {
|
||||
--disable_count_;
|
||||
if (disable_count_ != 0)
|
||||
return;
|
||||
} else {
|
||||
++disable_count_;
|
||||
if (disable_count_ != 1)
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(OS_WIN)
|
||||
::EnableWindow(GetAcceleratedWidget(), enable);
|
||||
#elif defined(USE_X11)
|
||||
views::DesktopWindowTreeHostX11* tree_host =
|
||||
views::DesktopWindowTreeHostX11::GetHostForXID(GetAcceleratedWidget());
|
||||
if (enable) {
|
||||
tree_host->RemoveEventRewriter(event_disabler_.get());
|
||||
event_disabler_.reset();
|
||||
} else {
|
||||
event_disabler_.reset(new EventDisabler);
|
||||
tree_host->AddEventRewriter(event_disabler_.get());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void NativeWindowViews::OnWidgetActivationChanged(
|
||||
views::Widget* widget, bool active) {
|
||||
if (widget != window_.get())
|
||||
|
@ -900,6 +985,15 @@ void NativeWindowViews::OnWidgetBoundsChanged(
|
|||
}
|
||||
|
||||
void NativeWindowViews::DeleteDelegate() {
|
||||
if (is_modal() && NativeWindow::parent()) {
|
||||
NativeWindowViews* parent =
|
||||
static_cast<NativeWindowViews*>(NativeWindow::parent());
|
||||
// Enable parent window after current window gets closed.
|
||||
parent->SetEnabled(true);
|
||||
// Focus on parent window.
|
||||
parent->Focus(true);
|
||||
}
|
||||
|
||||
NotifyWindowClosed();
|
||||
}
|
||||
|
||||
|
@ -1110,8 +1204,9 @@ ui::WindowShowState NativeWindowViews::GetRestoredState() {
|
|||
// static
|
||||
NativeWindow* NativeWindow::Create(
|
||||
brightray::InspectableWebContents* inspectable_web_contents,
|
||||
const mate::Dictionary& options) {
|
||||
return new NativeWindowViews(inspectable_web_contents, options);
|
||||
const mate::Dictionary& options,
|
||||
NativeWindow* parent) {
|
||||
return new NativeWindowViews(inspectable_web_contents, options, parent);
|
||||
}
|
||||
|
||||
} // namespace atom
|
||||
|
|
|
@ -32,6 +32,8 @@ class WindowStateWatcher;
|
|||
|
||||
#if defined(OS_WIN)
|
||||
class AtomDesktopWindowTreeHostWin;
|
||||
#elif defined(USE_X11)
|
||||
class EventDisabler;
|
||||
#endif
|
||||
|
||||
class NativeWindowViews : public NativeWindow,
|
||||
|
@ -42,7 +44,8 @@ class NativeWindowViews : public NativeWindow,
|
|||
public views::WidgetObserver {
|
||||
public:
|
||||
NativeWindowViews(brightray::InspectableWebContents* inspectable_web_contents,
|
||||
const mate::Dictionary& options);
|
||||
const mate::Dictionary& options,
|
||||
NativeWindow* parent);
|
||||
~NativeWindowViews() override;
|
||||
|
||||
// NativeWindow:
|
||||
|
@ -54,6 +57,7 @@ class NativeWindowViews : public NativeWindow,
|
|||
void ShowInactive() override;
|
||||
void Hide() override;
|
||||
bool IsVisible() override;
|
||||
bool IsEnabled() override;
|
||||
void Maximize() override;
|
||||
void Unmaximize() override;
|
||||
bool IsMaximized() override;
|
||||
|
@ -94,6 +98,7 @@ class NativeWindowViews : public NativeWindow,
|
|||
void SetIgnoreMouseEvents(bool ignore) override;
|
||||
void SetFocusable(bool focusable) override;
|
||||
void SetMenu(ui::MenuModel* menu_model) override;
|
||||
void SetParentWindow(NativeWindow* parent) override;
|
||||
gfx::NativeWindow GetNativeWindow() override;
|
||||
void SetOverlayIcon(const gfx::Image& overlay,
|
||||
const std::string& description) override;
|
||||
|
@ -113,6 +118,8 @@ class NativeWindowViews : public NativeWindow,
|
|||
void SetIcon(const gfx::ImageSkia& icon);
|
||||
#endif
|
||||
|
||||
void SetEnabled(bool enable);
|
||||
|
||||
views::Widget* widget() const { return window_.get(); }
|
||||
|
||||
#if defined(OS_WIN)
|
||||
|
@ -187,6 +194,9 @@ class NativeWindowViews : public NativeWindow,
|
|||
// Handles window state events.
|
||||
std::unique_ptr<WindowStateWatcher> window_state_watcher_;
|
||||
|
||||
// To disable the mouse events.
|
||||
std::unique_ptr<EventDisabler> event_disabler_;
|
||||
|
||||
// The "resizable" flag on Linux is implemented by setting size constraints,
|
||||
// we need to make sure size constraints are restored when window becomes
|
||||
// resizable again.
|
||||
|
@ -220,6 +230,9 @@ class NativeWindowViews : public NativeWindow,
|
|||
// Map from accelerator to menu item's command id.
|
||||
accelerator_util::AcceleratorTable accelerator_table_;
|
||||
|
||||
// How many times the Disable has been called.
|
||||
int disable_count_;
|
||||
|
||||
bool use_content_size_;
|
||||
bool movable_;
|
||||
bool resizable_;
|
||||
|
|
27
atom/browser/ui/x/event_disabler.cc
Normal file
27
atom/browser/ui/x/event_disabler.cc
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2016 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/x/event_disabler.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
EventDisabler::EventDisabler() {
|
||||
}
|
||||
|
||||
EventDisabler::~EventDisabler() {
|
||||
}
|
||||
|
||||
ui::EventRewriteStatus EventDisabler::RewriteEvent(
|
||||
const ui::Event& event,
|
||||
std::unique_ptr<ui::Event>* rewritten_event) {
|
||||
return ui::EVENT_REWRITE_DISCARD;
|
||||
}
|
||||
|
||||
ui::EventRewriteStatus EventDisabler::NextDispatchEvent(
|
||||
const ui::Event& last_event,
|
||||
std::unique_ptr<ui::Event>* new_event) {
|
||||
return ui::EVENT_REWRITE_CONTINUE;
|
||||
}
|
||||
|
||||
} // namespace atom
|
32
atom/browser/ui/x/event_disabler.h
Normal file
32
atom/browser/ui/x/event_disabler.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) 2016 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_X_EVENT_DISABLER_H_
|
||||
#define ATOM_BROWSER_UI_X_EVENT_DISABLER_H_
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "ui/events/event_rewriter.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class EventDisabler : public ui::EventRewriter {
|
||||
public:
|
||||
EventDisabler();
|
||||
~EventDisabler() override;
|
||||
|
||||
// ui::EventRewriter:
|
||||
ui::EventRewriteStatus RewriteEvent(
|
||||
const ui::Event& event,
|
||||
std::unique_ptr<ui::Event>* rewritten_event) override;
|
||||
ui::EventRewriteStatus NextDispatchEvent(
|
||||
const ui::Event& last_event,
|
||||
std::unique_ptr<ui::Event>* new_event) override;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(EventDisabler);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_X_EVENT_DISABLER_H_
|
|
@ -14,10 +14,6 @@
|
|||
|
||||
namespace atom {
|
||||
|
||||
namespace internal {
|
||||
|
||||
} // namespace internal
|
||||
|
||||
// Like ES6's WeakMap, but the key is Integer and the value is Weak Pointer.
|
||||
template<typename K>
|
||||
class KeyWeakMap {
|
||||
|
@ -57,7 +53,7 @@ class KeyWeakMap {
|
|||
}
|
||||
|
||||
// Returns all objects.
|
||||
std::vector<v8::Local<v8::Object>> Values(v8::Isolate* isolate) {
|
||||
std::vector<v8::Local<v8::Object>> Values(v8::Isolate* isolate) const {
|
||||
std::vector<v8::Local<v8::Object>> keys;
|
||||
keys.reserve(map_.size());
|
||||
for (const auto& iter : map_) {
|
||||
|
|
|
@ -59,6 +59,39 @@ win.loadURL('https://github.com')
|
|||
Note that even for apps that use `ready-to-show` event, it is still recommended
|
||||
to set `backgroundColor` to make app feel more native.
|
||||
|
||||
## Parent and child windows
|
||||
|
||||
By using `parent` option, you can create child windows:
|
||||
|
||||
```javascript
|
||||
let top = new BrowserWindow()
|
||||
let child = new BrowserWindow({parent: top})
|
||||
```
|
||||
|
||||
The `child` window will always show on top of the `top` window.
|
||||
|
||||
### Modal windows
|
||||
|
||||
A modal window is a child window that disables parent window, to create a modal
|
||||
window, you have to set both `parent` and `modal` options:
|
||||
|
||||
```javascript
|
||||
let child = new BrowserWindow({parent: top, modal: true, show: false})
|
||||
child.loadURL('https://github.com')
|
||||
child.once('ready-to-show', () => {
|
||||
child.show()
|
||||
})
|
||||
```
|
||||
|
||||
### Platform notices
|
||||
|
||||
* On macOS the child windows will keep the relative position to parent window
|
||||
when parent window moves, while on Windows and Linux child windows will not
|
||||
move.
|
||||
* On Windows it is not supported to change parent window dynamically.
|
||||
* On Linux the type of modal windows will be changed to `dialog`.
|
||||
* On Linux many desktop environments do not support hiding a modal window.
|
||||
|
||||
## Class: BrowserWindow
|
||||
|
||||
`BrowserWindow` is an
|
||||
|
@ -116,6 +149,9 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
|
|||
`true`.
|
||||
* `frame` Boolean - Specify `false` to create a
|
||||
[Frameless Window](frameless-window.md). Default is `true`.
|
||||
* `parent` BrowserWindow - Specify parent window. Default is `null`.
|
||||
* `modal` Boolean - Whether this is a modal window. This only works when the
|
||||
window is a child window. Default is `false`.
|
||||
* `acceptFirstMouse` Boolean - Whether the web view accepts a single
|
||||
mouse-down event that simultaneously activates the window. Default is
|
||||
`false`.
|
||||
|
@ -532,6 +568,10 @@ Hides the window.
|
|||
|
||||
Returns a boolean, whether the window is visible to the user.
|
||||
|
||||
### `win.isModal()`
|
||||
|
||||
Returns whether current window is a modal window.
|
||||
|
||||
### `win.maximize()`
|
||||
|
||||
Maximizes the window.
|
||||
|
@ -1017,3 +1057,18 @@ events.
|
|||
Changes whether the window can be focused.
|
||||
|
||||
[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in
|
||||
|
||||
### `win.setParentWindow(parent)` _Linux_ _macOS_
|
||||
|
||||
* `parent` BrowserWindow
|
||||
|
||||
Sets `parent` as current window's parent window, passing `null` will turn
|
||||
current window into a top-level window.
|
||||
|
||||
### `win.getParentWindow()`
|
||||
|
||||
Returns the parent window.
|
||||
|
||||
### `win.getChildWindows()`
|
||||
|
||||
Returns all child windows.
|
||||
|
|
|
@ -286,6 +286,8 @@
|
|||
'atom/browser/ui/win/notify_icon.h',
|
||||
'atom/browser/ui/win/taskbar_host.cc',
|
||||
'atom/browser/ui/win/taskbar_host.h',
|
||||
'atom/browser/ui/x/event_disabler.cc',
|
||||
'atom/browser/ui/x/event_disabler.h',
|
||||
'atom/browser/ui/x/window_state_watcher.cc',
|
||||
'atom/browser/ui/x/window_state_watcher.h',
|
||||
'atom/browser/ui/x/x_window_utils.cc',
|
||||
|
|
|
@ -117,7 +117,6 @@ BrowserWindow.fromDevToolsWebContents = (webContents) => {
|
|||
}
|
||||
|
||||
// Helpers.
|
||||
|
||||
Object.assign(BrowserWindow.prototype, {
|
||||
loadURL (...args) {
|
||||
return this.webContents.loadURL.apply(this.webContents, args)
|
||||
|
|
|
@ -836,6 +836,110 @@ describe('browser-window module', function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe('parent window', function () {
|
||||
let c = null
|
||||
|
||||
beforeEach(function () {
|
||||
if (c != null) c.destroy()
|
||||
c = new BrowserWindow({show: false, parent: w})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
if (c != null) c.destroy()
|
||||
c = null
|
||||
})
|
||||
|
||||
describe('parent option', function () {
|
||||
it('sets parent window', function () {
|
||||
assert.equal(c.getParentWindow(), w)
|
||||
})
|
||||
|
||||
it('adds window to child windows of parent', function () {
|
||||
assert.deepEqual(w.getChildWindows(), [c])
|
||||
})
|
||||
|
||||
it('removes from child windows of parent when window is closed', function (done) {
|
||||
c.once('closed', () => {
|
||||
assert.deepEqual(w.getChildWindows(), [])
|
||||
done()
|
||||
})
|
||||
c.close()
|
||||
})
|
||||
})
|
||||
|
||||
describe('win.setParentWindow(parent)', function () {
|
||||
if (process.platform === 'win32') return
|
||||
|
||||
beforeEach(function () {
|
||||
if (c != null) c.destroy()
|
||||
c = new BrowserWindow({show: false})
|
||||
})
|
||||
|
||||
it('sets parent window', function () {
|
||||
assert.equal(w.getParentWindow(), null)
|
||||
assert.equal(c.getParentWindow(), null)
|
||||
c.setParentWindow(w)
|
||||
assert.equal(c.getParentWindow(), w)
|
||||
c.setParentWindow(null)
|
||||
assert.equal(c.getParentWindow(), null)
|
||||
})
|
||||
|
||||
it('adds window to child windows of parent', function () {
|
||||
assert.deepEqual(w.getChildWindows(), [])
|
||||
c.setParentWindow(w)
|
||||
assert.deepEqual(w.getChildWindows(), [c])
|
||||
c.setParentWindow(null)
|
||||
assert.deepEqual(w.getChildWindows(), [])
|
||||
})
|
||||
|
||||
it('removes from child windows of parent when window is closed', function (done) {
|
||||
c.once('closed', () => {
|
||||
assert.deepEqual(w.getChildWindows(), [])
|
||||
done()
|
||||
})
|
||||
c.setParentWindow(w)
|
||||
c.close()
|
||||
})
|
||||
})
|
||||
|
||||
describe('modal option', function () {
|
||||
// The isEnabled API is not reliable on macOS.
|
||||
if (process.platform === 'darwin') return
|
||||
|
||||
beforeEach(function () {
|
||||
if (c != null) c.destroy()
|
||||
c = new BrowserWindow({show: false, parent: w, modal: true})
|
||||
})
|
||||
|
||||
it('disables parent window', function () {
|
||||
assert.equal(w.isEnabled(), true)
|
||||
c.show()
|
||||
assert.equal(w.isEnabled(), false)
|
||||
})
|
||||
|
||||
it('enables parent window when closed', function (done) {
|
||||
c.once('closed', () => {
|
||||
assert.equal(w.isEnabled(), true)
|
||||
done()
|
||||
})
|
||||
c.show()
|
||||
c.close()
|
||||
})
|
||||
|
||||
it('disables parent window recursively', function () {
|
||||
let c2 = new BrowserWindow({show: false, parent: w, modal: true})
|
||||
c.show()
|
||||
assert.equal(w.isEnabled(), false)
|
||||
c2.show()
|
||||
assert.equal(w.isEnabled(), false)
|
||||
c.destroy()
|
||||
assert.equal(w.isEnabled(), false)
|
||||
c2.destroy()
|
||||
assert.equal(w.isEnabled(), true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('window.webContents.send(channel, args...)', function () {
|
||||
it('throws an error when the channel is missing', function () {
|
||||
assert.throws(function () {
|
||||
|
|
Loading…
Reference in a new issue