Commit 0e850c3f authored by Michael Giuffrida's avatar Michael Giuffrida Committed by Commit Bot

AppShell: Move app windows between displays

When an AppWindow's bounds would place it on another display, reparent
it to that display's root window.

Bug: 827703
Test: ShellDesktopControllerAuraTest.MultipleDisplays
Change-Id: I0506dbe13ef7cdb0cf88c71128e6a1b56e470780
Reviewed-on: https://chromium-review.googlesource.com/989020Reviewed-by: default avatarJames Cook <jamescook@chromium.org>
Commit-Queue: Michael Giuffrida <michaelpg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#547370}
parent c2f8749b
......@@ -7,6 +7,7 @@
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/native_app_window.h"
#include "extensions/shell/browser/shell_app_delegate.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/layout_manager.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tracker.h"
......@@ -14,6 +15,7 @@
#include "ui/display/display.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/wm/core/default_screen_position_client.h"
namespace extensions {
......@@ -61,6 +63,34 @@ class FillLayout : public aura::LayoutManager {
DISALLOW_COPY_AND_ASSIGN(FillLayout);
};
// A simple screen positioning client that translates bounds to screen
// coordinates using the offset of the root window in screen coordinates.
class ScreenPositionClient : public wm::DefaultScreenPositionClient {
public:
ScreenPositionClient() = default;
~ScreenPositionClient() override = default;
// wm::DefaultScreenPositionClient:
void SetBounds(aura::Window* window,
const gfx::Rect& bounds,
const display::Display& display) override {
aura::Window* root_window = window->GetRootWindow();
DCHECK(window);
// Convert the window's origin to its root window's coordinates.
gfx::Point origin = bounds.origin();
aura::Window::ConvertPointToTarget(window->parent(), root_window, &origin);
// Translate the origin by the root window's offset in screen coordinates.
gfx::Point host_origin = GetOriginInScreen(root_window);
origin.Offset(-host_origin.x(), -host_origin.y());
window->SetBounds(gfx::Rect(origin, bounds.size()));
}
private:
DISALLOW_COPY_AND_ASSIGN(ScreenPositionClient);
};
} // namespace
RootWindowController::RootWindowController(
......@@ -68,7 +98,8 @@ RootWindowController::RootWindowController(
const gfx::Rect& bounds,
content::BrowserContext* browser_context)
: desktop_delegate_(desktop_delegate),
browser_context_(browser_context) {
browser_context_(browser_context),
screen_position_client_(std::make_unique<ScreenPositionClient>()) {
DCHECK(desktop_delegate_);
DCHECK(browser_context_);
host_.reset(aura::WindowTreeHost::Create(bounds));
......@@ -77,6 +108,8 @@ RootWindowController::RootWindowController(
host_->window()->Show();
aura::client::SetWindowParentingClient(host_->window(), this);
aura::client::SetScreenPositionClient(host_->window(),
screen_position_client_.get());
// Ensure the window fills the display.
host_->window()->SetLayoutManager(new FillLayout(host_->window()));
......@@ -104,6 +137,13 @@ void RootWindowController::AddAppWindow(AppWindow* app_window,
root_window->AddChild(window);
}
void RootWindowController::RemoveAppWindow(AppWindow* app_window) {
host_->window()->RemoveChild(app_window->GetNativeWindow());
app_windows_.remove(app_window);
if (app_windows_.empty())
AppWindowRegistry::Get(browser_context_)->RemoveObserver(this);
}
void RootWindowController::CloseAppWindows() {
if (app_windows_.empty())
return;
......
......@@ -16,6 +16,9 @@
namespace aura {
class WindowTreeHost;
namespace client {
class ScreenPositionClient;
} // namespace client
} // namespace aura
namespace content {
......@@ -61,6 +64,10 @@ class RootWindowController : public aura::client::WindowParentingClient,
// Attaches a NativeAppWindow's window to our root window.
void AddAppWindow(AppWindow* app_window, gfx::NativeWindow window);
// Unparents the AppWindow's window from our root window so it can be added to
// a different RootWindowController.
void RemoveAppWindow(AppWindow* app_window);
// Closes the root window's AppWindows, resulting in their destruction.
void CloseAppWindows();
......@@ -88,6 +95,8 @@ class RootWindowController : public aura::client::WindowParentingClient,
// The BrowserContext used to create AppWindows.
content::BrowserContext* const browser_context_;
std::unique_ptr<aura::client::ScreenPositionClient> screen_position_client_;
// The host we create.
std::unique_ptr<aura::WindowTreeHost> host_;
......
......@@ -11,6 +11,8 @@
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "components/keep_alive_registry/keep_alive_registry.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/native_app_window.h"
#include "extensions/shell/browser/shell_app_window_client.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/window.h"
......@@ -233,8 +235,21 @@ void ShellDesktopControllerAura::OnDisplayModeChanged(
ui::EventDispatchDetails ShellDesktopControllerAura::DispatchKeyEventPostIME(
ui::KeyEvent* key_event) {
// TODO(michaelpg): With multiple windows, determine which root window
// triggered the event. See ash::WindowTreeHostManager for example.
if (key_event->target()) {
aura::WindowTreeHost* host = static_cast<aura::Window*>(key_event->target())
->GetRootWindow()
->GetHost();
return host->DispatchKeyEventPostIME(key_event);
}
// Send the key event to the focused window.
aura::Window* active_window =
const_cast<aura::Window*>(focus_controller_->GetActiveWindow());
if (active_window) {
return active_window->GetRootWindow()->GetHost()->DispatchKeyEventPostIME(
key_event);
}
return GetPrimaryHost()->DispatchKeyEventPostIME(key_event);
}
......@@ -267,6 +282,36 @@ aura::Window::Windows ShellDesktopControllerAura::GetAllRootWindows() {
return windows;
}
void ShellDesktopControllerAura::SetWindowBoundsInScreen(
AppWindow* app_window,
const gfx::Rect& bounds) {
display::Display display =
display::Screen::GetScreen()->GetDisplayMatching(bounds);
// Create a RootWindowController for the display if necessary.
if (root_window_controllers_.count(display.id()) == 0) {
root_window_controllers_[display.id()] =
CreateRootWindowControllerForDisplay(display);
}
// Check if the window is parented to a different RootWindowController.
if (app_window->GetNativeWindow()->GetRootWindow() !=
root_window_controllers_[display.id()]->host()->window()) {
// Move the window to the appropriate RootWindowController for the display.
for (const auto& it : root_window_controllers_) {
if (it.second->host()->window() ==
app_window->GetNativeWindow()->GetRootWindow()) {
it.second->RemoveAppWindow(app_window);
break;
}
}
root_window_controllers_[display.id()]->AddAppWindow(
app_window, app_window->GetNativeWindow());
}
app_window->GetNativeWindow()->SetBoundsInScreen(bounds, display);
}
void ShellDesktopControllerAura::InitWindowManager() {
root_window_event_filter_ = std::make_unique<wm::CompoundEventFilter>();
......
......@@ -109,6 +109,10 @@ class ShellDesktopControllerAura
// Returns all root windows managed by RootWindowControllers.
aura::Window::Windows GetAllRootWindows();
// Updates the bounds of |app_window|. This may involve reparenting the window
// to a different root window if the new bounds are in a different display.
void SetWindowBoundsInScreen(AppWindow* app_window, const gfx::Rect& bounds);
protected:
// Creates and sets the aura clients and window manager stuff. Subclass may
// initialize different sets of the clients.
......
......@@ -13,6 +13,7 @@
#include "content/public/browser/web_contents.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_client.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/app_window/native_app_window.h"
#include "extensions/browser/app_window/test_app_window_contents.h"
#include "extensions/common/extension_builder.h"
......@@ -85,10 +86,12 @@ class ShellDesktopControllerAuraTest : public ShellTestBaseAura {
}
protected:
void CreateAppWindow(const Extension* extension, gfx::Rect bounds = {}) {
AppWindow* CreateAppWindow(const Extension* extension,
gfx::Rect bounds = {}) {
AppWindow* app_window =
AppWindowClient::Get()->CreateAppWindow(browser_context(), extension);
InitAppWindow(app_window, bounds);
return app_window;
}
std::unique_ptr<display::ScreenBase> screen_;
......@@ -180,18 +183,45 @@ TEST_F(ShellDesktopControllerAuraTest, MultipleDisplays) {
AppWindowRegistry::Get(browser_context());
scoped_refptr<Extension> extension = ExtensionBuilder("Test").Build();
// Create two app window on the primary display. Both should be hosted in the
// Create two apps window on the primary display. Both should be hosted in the
// same RootWindowController.
CreateAppWindow(extension.get());
CreateAppWindow(extension.get());
AppWindow* primary_app_window = CreateAppWindow(extension.get());
AppWindow* primary_app_window_2 = CreateAppWindow(extension.get());
EXPECT_EQ(2u, app_window_registry->app_windows().size());
EXPECT_EQ(1u, controller_->GetAllRootWindows().size());
// Create an app window near the secondary display, which should result in
// creating a RootWindowController for that display.
CreateAppWindow(extension.get(), gfx::Rect(1900, 1000, 600, 400));
AppWindow* secondary_app_window =
CreateAppWindow(extension.get(), gfx::Rect(1900, 1000, 600, 400));
EXPECT_EQ(3u, app_window_registry->app_windows().size());
EXPECT_EQ(2u, controller_->GetAllRootWindows().size());
aura::Window* primary_root = controller_->GetPrimaryHost()->window();
// Move an app window from the second display to the primary display.
EXPECT_NE(secondary_app_window->GetNativeWindow()->GetRootWindow(),
primary_root);
controller_->SetWindowBoundsInScreen(secondary_app_window,
gfx::Rect(0, 0, 100, 100));
EXPECT_EQ(secondary_app_window->GetNativeWindow()->GetRootWindow(),
primary_root);
// Move an app window from the primary display to the secondary display.
EXPECT_EQ(primary_app_window->GetNativeWindow()->GetRootWindow(),
primary_root);
controller_->SetWindowBoundsInScreen(primary_app_window,
gfx::Rect(1920, 1080, 800, 600));
EXPECT_NE(primary_app_window->GetNativeWindow()->GetRootWindow(),
primary_root);
// Test moving an app window within its display.
EXPECT_EQ(primary_app_window_2->GetNativeWindow()->GetRootWindow(),
primary_root);
controller_->SetWindowBoundsInScreen(primary_app_window_2,
gfx::Rect(100, 100, 500, 600));
EXPECT_EQ(primary_app_window_2->GetNativeWindow()->GetRootWindow(),
primary_root);
}
} // namespace extensions
......@@ -5,6 +5,7 @@
#include "extensions/shell/browser/shell_native_app_window_aura.h"
#include "content/public/browser/web_contents.h"
#include "extensions/shell/browser/shell_desktop_controller_aura.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/gfx/geometry/rect.h"
......@@ -69,7 +70,16 @@ void ShellNativeAppWindowAura::Deactivate() {
}
void ShellNativeAppWindowAura::SetBounds(const gfx::Rect& bounds) {
GetNativeWindow()->SetBounds(bounds);
// When this is called during window creation, set the bounds naively.
if (!GetNativeWindow()->GetRootWindow()) {
GetNativeWindow()->SetBounds(bounds);
return;
}
// ShellDesktopControllerAura will take care of moving the AppWindow to
// another display if necessary.
static_cast<ShellDesktopControllerAura*>(DesktopController::instance())
->SetWindowBoundsInScreen(app_window(), bounds);
}
gfx::Size ShellNativeAppWindowAura::GetContentMinimumSize() const {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment