diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 0d72a74f7d..cc3749bd01 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -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 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::AfterInit(isolate); + + // We can only append this window to parent window's child windows after this + // window's JS wrapper gets initialized. + mate::Handle 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 value : child_windows_.Values(isolate())) { + mate::Handle 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 value, + mate::Arguments* args) { + if (IsModal()) { + args->ThrowError("Can not be called for modal window"); + return; + } + + mate::Handle 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 Window::GetParentWindow() const { + if (parent_window_.IsEmpty()) + return v8::Null(isolate()); + else + return v8::Local::New(isolate(), parent_window_); +} + +std::vector> Window::GetChildWindows() const { + return child_windows_.Values(isolate()); +} + +bool Window::IsModal() const { + return window_->is_modal(); +} + v8::Local Window::GetNativeWindowHandle() { gfx::AcceleratedWidget handle = window_->GetAcceleratedWidget(); return ToBuffer( @@ -675,6 +750,17 @@ v8::Local Window::WebContents(v8::Isolate* isolate) { return v8::Local::New(isolate, web_contents_); } +void Window::RemoveFromParentChildWindows() { + if (parent_window_.IsEmpty()) + return; + + mate::Handle parent; + if (!mate::ConvertFromV8(isolate(), GetParentWindow(), &parent)) + return; + + parent->child_windows_.Remove(ID()); +} + // static void Window::BuildPrototype(v8::Isolate* isolate, v8::Local 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) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 9d3f7ef240..21ecca4c72 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -6,15 +6,16 @@ #define ATOM_BROWSER_API_ATOM_API_WINDOW_H_ #include +#include #include #include -#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(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, void ShowInactive(); void Hide(); bool IsVisible(); + bool IsEnabled(); void Maximize(); void Unmaximize(); bool IsMaximized(); @@ -159,6 +165,10 @@ class Window : public mate::TrackableObject, void SetMenuBarVisibility(bool visible); bool IsMenuBarVisible(); void SetAspectRatio(double aspect_ratio, mate::Arguments* args); + void SetParentWindow(v8::Local value, mate::Arguments* args); + v8::Local GetParentWindow() const; + std::vector> GetChildWindows() const; + bool IsModal() const; v8::Local GetNativeWindowHandle(); #if defined(OS_WIN) @@ -181,6 +191,9 @@ class Window : public mate::TrackableObject, int32_t ID() const; v8::Local WebContents(v8::Isolate* isolate); + // Remove this window from parent window's |child_windows_|. + void RemoveFromParentChildWindows(); + #if defined(OS_WIN) typedef std::map MessageCallbackMap; MessageCallbackMap messages_callback_map_; @@ -188,6 +201,8 @@ class Window : public mate::TrackableObject, v8::Global web_contents_; v8::Global menu_; + v8::Global parent_window_; + KeyWeakMap child_windows_; api::WebContents* api_web_contents_; diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 844bbefa9e..4eb72dfe08 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -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()); diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index a644682e34..d3b6f8a2b0 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -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_; diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 899043fd6a..af99b3912e 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -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); }; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index e15ff0c6ce..5f997ee9b6 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -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 diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 73bf4362f5..16541d0909 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -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() {} diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 021ceba005..1feb9618ac 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -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(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(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(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 diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 5377420374..69d3d27a35 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -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 window_state_watcher_; + // To disable the mouse events. + std::unique_ptr 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_; diff --git a/atom/browser/ui/x/event_disabler.cc b/atom/browser/ui/x/event_disabler.cc new file mode 100644 index 0000000000..6d0e4cfeb0 --- /dev/null +++ b/atom/browser/ui/x/event_disabler.cc @@ -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* rewritten_event) { + return ui::EVENT_REWRITE_DISCARD; +} + +ui::EventRewriteStatus EventDisabler::NextDispatchEvent( + const ui::Event& last_event, + std::unique_ptr* new_event) { + return ui::EVENT_REWRITE_CONTINUE; +} + +} // namespace atom diff --git a/atom/browser/ui/x/event_disabler.h b/atom/browser/ui/x/event_disabler.h new file mode 100644 index 0000000000..9a6645bcdc --- /dev/null +++ b/atom/browser/ui/x/event_disabler.h @@ -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* rewritten_event) override; + ui::EventRewriteStatus NextDispatchEvent( + const ui::Event& last_event, + std::unique_ptr* new_event) override; + + private: + DISALLOW_COPY_AND_ASSIGN(EventDisabler); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_X_EVENT_DISABLER_H_ diff --git a/atom/common/key_weak_map.h b/atom/common/key_weak_map.h index bce34bfe0f..009ba099c9 100644 --- a/atom/common/key_weak_map.h +++ b/atom/common/key_weak_map.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 class KeyWeakMap { @@ -57,7 +53,7 @@ class KeyWeakMap { } // Returns all objects. - std::vector> Values(v8::Isolate* isolate) { + std::vector> Values(v8::Isolate* isolate) const { std::vector> keys; keys.reserve(map_.size()); for (const auto& iter : map_) { diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 6aae7b0080..e95570450b 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -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. diff --git a/filenames.gypi b/filenames.gypi index 87ff5dc981..af7080821b 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -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', diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 0847649956..388e396844 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -117,7 +117,6 @@ BrowserWindow.fromDevToolsWebContents = (webContents) => { } // Helpers. - Object.assign(BrowserWindow.prototype, { loadURL (...args) { return this.webContents.loadURL.apply(this.webContents, args) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 598a3470f7..7310cd6a90 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -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 () {