blob: 3c5e329c8f3f314c361717d0b81d4fac0eee8ddc [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/mus/public/cpp/lib/window_tree_client_impl.h"
#include "base/bind.h"
#include "components/mus/public/cpp/lib/in_flight_change.h"
#include "components/mus/public/cpp/lib/window_private.h"
#include "components/mus/public/cpp/util.h"
#include "components/mus/public/cpp/window_manager_delegate.h"
#include "components/mus/public/cpp/window_observer.h"
#include "components/mus/public/cpp/window_tree_connection.h"
#include "components/mus/public/cpp/window_tree_delegate.h"
#include "mojo/application/public/cpp/application_impl.h"
#include "mojo/application/public/cpp/connect.h"
#include "mojo/application/public/cpp/service_provider_impl.h"
#include "mojo/application/public/interfaces/service_provider.mojom.h"
#include "mojo/converters/geometry/geometry_type_converters.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/size.h"
namespace mus {
namespace {
void WindowManagerCallback(mojom::WindowManagerErrorCode error_code) {}
} // namespace
Id MakeTransportId(ConnectionSpecificId connection_id,
ConnectionSpecificId local_id) {
return (connection_id << 16) | local_id;
}
// Helper called to construct a local window object from transport data.
Window* AddWindowToConnection(WindowTreeClientImpl* client,
Window* parent,
const mojom::WindowDataPtr& window_data) {
// We don't use the cto that takes a WindowTreeConnection here, since it will
// call back to the service and attempt to create a new window.
Window* window = WindowPrivate::LocalCreate();
WindowPrivate private_window(window);
private_window.set_connection(client);
private_window.set_id(window_data->window_id);
private_window.set_visible(window_data->visible);
private_window.set_drawn(window_data->drawn);
private_window.LocalSetViewportMetrics(mojom::ViewportMetrics(),
*window_data->viewport_metrics);
private_window.set_properties(
window_data->properties
.To<std::map<std::string, std::vector<uint8_t>>>());
client->AddWindow(window);
private_window.LocalSetBounds(gfx::Rect(),
window_data->bounds.To<gfx::Rect>());
if (parent)
WindowPrivate(parent).LocalAddChild(window);
return window;
}
Window* BuildWindowTree(WindowTreeClientImpl* client,
const mojo::Array<mojom::WindowDataPtr>& windows,
Window* initial_parent) {
std::vector<Window*> parents;
Window* root = NULL;
Window* last_window = NULL;
if (initial_parent)
parents.push_back(initial_parent);
for (size_t i = 0; i < windows.size(); ++i) {
if (last_window && windows[i]->parent_id == last_window->id()) {
parents.push_back(last_window);
} else if (!parents.empty()) {
while (parents.back()->id() != windows[i]->parent_id)
parents.pop_back();
}
Window* window = AddWindowToConnection(
client, !parents.empty() ? parents.back() : NULL, windows[i]);
if (!last_window)
root = window;
last_window = window;
}
return root;
}
WindowTreeConnection* WindowTreeConnection::Create(
WindowTreeDelegate* delegate,
mojo::InterfaceRequest<mojom::WindowTreeClient> request,
CreateType create_type) {
WindowTreeClientImpl* client =
new WindowTreeClientImpl(delegate, nullptr, request.Pass());
if (create_type == CreateType::WAIT_FOR_EMBED)
client->WaitForEmbed();
return client;
}
WindowTreeConnection* WindowTreeConnection::CreateForWindowManager(
WindowTreeDelegate* delegate,
mojo::InterfaceRequest<mojom::WindowTreeClient> request,
CreateType create_type,
WindowManagerDelegate* window_manager_delegate) {
WindowTreeClientImpl* client = new WindowTreeClientImpl(
delegate, window_manager_delegate, request.Pass());
if (create_type == CreateType::WAIT_FOR_EMBED)
client->WaitForEmbed();
return client;
}
WindowTreeClientImpl::WindowTreeClientImpl(
WindowTreeDelegate* delegate,
WindowManagerDelegate* window_manager_delegate,
mojo::InterfaceRequest<mojom::WindowTreeClient> request)
: connection_id_(0),
next_window_id_(1),
next_change_id_(1),
delegate_(delegate),
window_manager_delegate_(window_manager_delegate),
root_(nullptr),
focused_window_(nullptr),
binding_(this),
tree_(nullptr),
is_embed_root_(false),
in_destructor_(false) {
// Allow for a null request in tests.
if (request.is_pending())
binding_.Bind(request.Pass());
}
WindowTreeClientImpl::~WindowTreeClientImpl() {
in_destructor_ = true;
std::vector<Window*> non_owned;
while (!windows_.empty()) {
IdToWindowMap::iterator it = windows_.begin();
if (OwnsWindow(it->second->id())) {
it->second->Destroy();
} else {
non_owned.push_back(it->second);
windows_.erase(it);
}
}
// Delete the non-owned windows last. In the typical case these are roots. The
// exception is the window manager and embed roots, which may know about
// other random windows that it doesn't own.
// NOTE: we manually delete as we're a friend.
for (size_t i = 0; i < non_owned.size(); ++i)
delete non_owned[i];
delegate_->OnConnectionLost(this);
}
void WindowTreeClientImpl::WaitForEmbed() {
DCHECK(!root_);
// OnEmbed() is the first function called.
binding_.WaitForIncomingMethodCall();
// TODO(sky): deal with pipe being closed before we get OnEmbed().
}
void WindowTreeClientImpl::DestroyWindow(Id window_id) {
DCHECK(tree_);
tree_->DeleteWindow(window_id, ActionCompletedCallback());
}
void WindowTreeClientImpl::AddChild(Id child_id, Id parent_id) {
DCHECK(tree_);
tree_->AddWindow(parent_id, child_id, ActionCompletedCallback());
}
void WindowTreeClientImpl::RemoveChild(Id child_id, Id parent_id) {
DCHECK(tree_);
tree_->RemoveWindowFromParent(child_id, ActionCompletedCallback());
}
void WindowTreeClientImpl::Reorder(Id window_id,
Id relative_window_id,
mojom::OrderDirection direction) {
DCHECK(tree_);
tree_->ReorderWindow(window_id, relative_window_id, direction,
ActionCompletedCallback());
}
bool WindowTreeClientImpl::OwnsWindow(Id id) const {
return HiWord(id) == connection_id_;
}
void WindowTreeClientImpl::SetBounds(Id window_id,
const gfx::Rect& old_bounds,
const gfx::Rect& bounds) {
DCHECK(tree_);
const uint32_t change_id = ScheduleInFlightChange(
make_scoped_ptr(new InFlightBoundsChange(this, window_id, old_bounds)));
tree_->SetWindowBounds(change_id, window_id, mojo::Rect::From(bounds));
}
void WindowTreeClientImpl::SetClientArea(Id window_id,
const gfx::Insets& client_area) {
DCHECK(tree_);
tree_->SetClientArea(window_id, mojo::Insets::From(client_area));
}
void WindowTreeClientImpl::SetFocus(Id window_id) {
// In order for us to get here we had to have exposed a window, which implies
// we got a connection.
DCHECK(tree_);
tree_->SetFocus(window_id);
}
void WindowTreeClientImpl::SetVisible(Id window_id, bool visible) {
DCHECK(tree_);
tree_->SetWindowVisibility(window_id, visible, ActionCompletedCallback());
}
void WindowTreeClientImpl::SetProperty(Id window_id,
const std::string& name,
const std::vector<uint8_t>& data) {
DCHECK(tree_);
tree_->SetWindowProperty(window_id, mojo::String(name),
mojo::Array<uint8_t>::From(data),
ActionCompletedCallback());
}
void WindowTreeClientImpl::SetWindowTextInputState(
Id window_id,
mojo::TextInputStatePtr state) {
DCHECK(tree_);
tree_->SetWindowTextInputState(window_id, state.Pass());
}
void WindowTreeClientImpl::SetImeVisibility(Id window_id,
bool visible,
mojo::TextInputStatePtr state) {
DCHECK(tree_);
tree_->SetImeVisibility(window_id, visible, state.Pass());
}
void WindowTreeClientImpl::Embed(
Id window_id,
mojom::WindowTreeClientPtr client,
uint32_t policy_bitmask,
const mojom::WindowTree::EmbedCallback& callback) {
DCHECK(tree_);
tree_->Embed(window_id, client.Pass(), policy_bitmask, callback);
}
void WindowTreeClientImpl::RequestSurface(
Id window_id,
mojom::SurfaceType type,
mojo::InterfaceRequest<mojom::Surface> surface,
mojom::SurfaceClientPtr client) {
DCHECK(tree_);
tree_->RequestSurface(window_id, type, surface.Pass(), client.Pass());
}
void WindowTreeClientImpl::AddWindow(Window* window) {
DCHECK(windows_.find(window->id()) == windows_.end());
windows_[window->id()] = window;
}
void WindowTreeClientImpl::RemoveWindow(Id window_id) {
if (focused_window_ && focused_window_->id() == window_id)
OnWindowFocused(0);
IdToWindowMap::iterator it = windows_.find(window_id);
if (it != windows_.end())
windows_.erase(it);
// Remove any InFlightChanges associated with the window.
std::set<uint32_t> in_flight_change_ids;
for (const auto& pair : in_flight_map_) {
if (pair.second->window_id() == window_id)
in_flight_change_ids.insert(pair.first);
}
for (auto change_id : in_flight_change_ids)
in_flight_map_.erase(change_id);
}
void WindowTreeClientImpl::OnRootDestroyed(Window* root) {
DCHECK_EQ(root, root_);
root_ = nullptr;
// When the root is gone we can't do anything useful.
if (!in_destructor_)
delete this;
}
void WindowTreeClientImpl::SetPreferredSize(Id window_id,
const gfx::Size& size) {
tree_->SetPreferredSize(window_id, mojo::Size::From(size),
base::Bind(&WindowManagerCallback));
}
void WindowTreeClientImpl::SetShowState(Id window_id,
mojom::ShowState show_state) {
tree_->SetShowState(window_id, show_state,
base::Bind(&WindowManagerCallback));
}
void WindowTreeClientImpl::SetResizeBehavior(
Id window_id,
mojom::ResizeBehavior resize_behavior) {
tree_->SetResizeBehavior(window_id, resize_behavior);
}
Id WindowTreeClientImpl::CreateWindowOnServer() {
DCHECK(tree_);
const Id window_id = MakeTransportId(connection_id_, next_window_id_++);
const uint32_t change_id = ScheduleInFlightChange(make_scoped_ptr(
new CrashInFlightChange(window_id, ChangeType::NEW_WINDOW)));
tree_->NewWindow(change_id, window_id);
return window_id;
}
InFlightChange* WindowTreeClientImpl::GetOldestInFlightChangeMatching(
Id window_id,
ChangeType change_type) {
for (auto& pair : in_flight_map_) {
if (pair.second->window_id() == window_id &&
pair.second->change_type() == change_type) {
return pair.second;
}
}
return nullptr;
}
uint32_t WindowTreeClientImpl::ScheduleInFlightChange(
scoped_ptr<InFlightChange> change) {
const uint32_t change_id = next_change_id_++;
in_flight_map_.set(change_id, change.Pass());
return change_id;
}
void WindowTreeClientImpl::OnEmbedImpl(mojom::WindowTree* window_tree,
ConnectionSpecificId connection_id,
mojom::WindowDataPtr root_data,
Id focused_window_id,
uint32 access_policy) {
tree_ = window_tree;
connection_id_ = connection_id;
is_embed_root_ =
(access_policy & mojom::WindowTree::ACCESS_POLICY_EMBED_ROOT) != 0;
DCHECK(!root_);
root_ = AddWindowToConnection(this, nullptr, root_data);
focused_window_ = GetWindowById(focused_window_id);
delegate_->OnEmbed(root_);
}
////////////////////////////////////////////////////////////////////////////////
// WindowTreeClientImpl, WindowTreeConnection implementation:
Window* WindowTreeClientImpl::GetRoot() {
return root_;
}
Window* WindowTreeClientImpl::GetWindowById(Id id) {
IdToWindowMap::const_iterator it = windows_.find(id);
return it != windows_.end() ? it->second : NULL;
}
Window* WindowTreeClientImpl::GetFocusedWindow() {
return focused_window_;
}
Window* WindowTreeClientImpl::NewWindow() {
Window* window = new Window(this, CreateWindowOnServer());
AddWindow(window);
return window;
}
bool WindowTreeClientImpl::IsEmbedRoot() {
return is_embed_root_;
}
ConnectionSpecificId WindowTreeClientImpl::GetConnectionId() {
return connection_id_;
}
////////////////////////////////////////////////////////////////////////////////
// WindowTreeClientImpl, WindowTreeClient implementation:
void WindowTreeClientImpl::OnEmbed(ConnectionSpecificId connection_id,
mojom::WindowDataPtr root_data,
mojom::WindowTreePtr tree,
Id focused_window_id,
uint32 access_policy) {
DCHECK(!tree_ptr_);
tree_ptr_ = tree.Pass();
tree_ptr_.set_connection_error_handler([this]() { delete this; });
OnEmbedImpl(tree_ptr_.get(), connection_id, root_data.Pass(),
focused_window_id, access_policy);
}
void WindowTreeClientImpl::OnEmbeddedAppDisconnected(Id window_id) {
Window* window = GetWindowById(window_id);
if (window) {
FOR_EACH_OBSERVER(WindowObserver, *WindowPrivate(window).observers(),
OnWindowEmbeddedAppDisconnected(window));
}
}
void WindowTreeClientImpl::OnUnembed() {
delegate_->OnUnembed();
// This will send out the various notifications.
delete this;
}
void WindowTreeClientImpl::OnWindowBoundsChanged(Id window_id,
mojo::RectPtr old_bounds,
mojo::RectPtr new_bounds) {
InFlightChange* change =
GetOldestInFlightChangeMatching(window_id, ChangeType::BOUNDS);
if (change) {
static_cast<InFlightBoundsChange*>(change)
->set_revert_bounds(new_bounds.To<gfx::Rect>());
// Wait for the change we initiated on the server to complete before
// deciding if |new_bounds| should be applied.
return;
}
Window* window = GetWindowById(window_id);
WindowPrivate(window)
.LocalSetBounds(old_bounds.To<gfx::Rect>(), new_bounds.To<gfx::Rect>());
}
void WindowTreeClientImpl::OnClientAreaChanged(
uint32_t window_id,
mojo::InsetsPtr old_client_area,
mojo::InsetsPtr new_client_area) {
Window* window = GetWindowById(window_id);
if (window)
WindowPrivate(window).LocalSetClientArea(new_client_area.To<gfx::Insets>());
}
namespace {
void SetViewportMetricsOnDecendants(Window* root,
const mojom::ViewportMetrics& old_metrics,
const mojom::ViewportMetrics& new_metrics) {
WindowPrivate(root).LocalSetViewportMetrics(old_metrics, new_metrics);
const Window::Children& children = root->children();
for (size_t i = 0; i < children.size(); ++i)
SetViewportMetricsOnDecendants(children[i], old_metrics, new_metrics);
}
}
void WindowTreeClientImpl::OnWindowViewportMetricsChanged(
mojom::ViewportMetricsPtr old_metrics,
mojom::ViewportMetricsPtr new_metrics) {
Window* window = GetRoot();
if (window)
SetViewportMetricsOnDecendants(window, *old_metrics, *new_metrics);
}
void WindowTreeClientImpl::OnWindowHierarchyChanged(
Id window_id,
Id new_parent_id,
Id old_parent_id,
mojo::Array<mojom::WindowDataPtr> windows) {
Window* initial_parent =
windows.size() ? GetWindowById(windows[0]->parent_id) : NULL;
const bool was_window_known = GetWindowById(window_id) != nullptr;
BuildWindowTree(this, windows, initial_parent);
// If the window was not known, then BuildWindowTree() will have created it
// and parented the window.
if (!was_window_known)
return;
Window* new_parent = GetWindowById(new_parent_id);
Window* old_parent = GetWindowById(old_parent_id);
Window* window = GetWindowById(window_id);
if (new_parent)
WindowPrivate(new_parent).LocalAddChild(window);
else
WindowPrivate(old_parent).LocalRemoveChild(window);
}
void WindowTreeClientImpl::OnWindowReordered(Id window_id,
Id relative_window_id,
mojom::OrderDirection direction) {
Window* window = GetWindowById(window_id);
Window* relative_window = GetWindowById(relative_window_id);
if (window && relative_window)
WindowPrivate(window).LocalReorder(relative_window, direction);
}
void WindowTreeClientImpl::OnWindowDeleted(Id window_id) {
Window* window = GetWindowById(window_id);
if (window)
WindowPrivate(window).LocalDestroy();
}
void WindowTreeClientImpl::OnWindowVisibilityChanged(Id window_id,
bool visible) {
// TODO(sky): there is a race condition here. If this client and another
// client change the visibility at the same time the wrong value may be set.
// Deal with this some how.
Window* window = GetWindowById(window_id);
if (window)
WindowPrivate(window).LocalSetVisible(visible);
}
void WindowTreeClientImpl::OnWindowDrawnStateChanged(Id window_id, bool drawn) {
Window* window = GetWindowById(window_id);
if (window)
WindowPrivate(window).LocalSetDrawn(drawn);
}
void WindowTreeClientImpl::OnWindowSharedPropertyChanged(
Id window_id,
const mojo::String& name,
mojo::Array<uint8_t> new_data) {
Window* window = GetWindowById(window_id);
if (window) {
std::vector<uint8_t> data;
std::vector<uint8_t>* data_ptr = NULL;
if (!new_data.is_null()) {
data = new_data.To<std::vector<uint8_t>>();
data_ptr = &data;
}
WindowPrivate(window).LocalSetSharedProperty(name, data_ptr);
}
}
void WindowTreeClientImpl::OnWindowInputEvent(
Id window_id,
mojom::EventPtr event,
const mojo::Callback<void()>& ack_callback) {
Window* window = GetWindowById(window_id);
if (window) {
FOR_EACH_OBSERVER(WindowObserver, *WindowPrivate(window).observers(),
OnWindowInputEvent(window, event));
}
ack_callback.Run();
}
void WindowTreeClientImpl::OnWindowFocused(Id focused_window_id) {
Window* focused = GetWindowById(focused_window_id);
Window* blurred = focused_window_;
// Update |focused_window_| before calling any of the observers, so that the
// observers get the correct result from calling |Window::HasFocus()|,
// |WindowTreeConnection::GetFocusedWindow()| etc.
focused_window_ = focused;
if (blurred) {
FOR_EACH_OBSERVER(WindowObserver, *WindowPrivate(blurred).observers(),
OnWindowFocusChanged(focused, blurred));
}
if (focused) {
FOR_EACH_OBSERVER(WindowObserver, *WindowPrivate(focused).observers(),
OnWindowFocusChanged(focused, blurred));
}
}
void WindowTreeClientImpl::OnChangeCompleted(uint32 change_id, bool success) {
scoped_ptr<InFlightChange> change(in_flight_map_.take_and_erase(change_id));
if (!change)
return;
InFlightChange* next_change = GetOldestInFlightChangeMatching(
change->window_id(), change->change_type());
if (next_change)
next_change->PreviousChangeCompleted(change.get(), success);
else if (!success)
change->Revert();
}
void WindowTreeClientImpl::WmSetBounds(uint32 change_id,
uint32 window_id,
mojo::RectPtr transit_bounds) {
Window* window = GetWindowById(window_id);
bool result = false;
if (window) {
DCHECK(window_manager_delegate_);
gfx::Rect bounds = transit_bounds.To<gfx::Rect>();
result = window_manager_delegate_->OnWmSetBounds(window, &bounds);
if (result) {
// If the resulting bounds differ return false. Returning false ensures
// the client applies the bounds we set below.
result = bounds == transit_bounds.To<gfx::Rect>();
window->SetBounds(bounds);
}
}
tree_->WmResponse(change_id, result);
}
////////////////////////////////////////////////////////////////////////////////
// WindowTreeClientImpl, private:
void WindowTreeClientImpl::OnActionCompleted(bool success) {
if (!change_acked_callback_.is_null())
change_acked_callback_.Run();
}
mojo::Callback<void(bool)> WindowTreeClientImpl::ActionCompletedCallback() {
return [this](bool success) { OnActionCompleted(success); };
}
} // namespace mus