blob: 11c547d4f15d0cd005a011e6b9caaef0b0457628 [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/view_manager/connection_manager.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "components/view_manager/client_connection.h"
#include "components/view_manager/connection_manager_delegate.h"
#include "components/view_manager/display_manager.h"
#include "components/view_manager/focus_controller.h"
#include "components/view_manager/server_view.h"
#include "components/view_manager/view_coordinate_conversions.h"
#include "components/view_manager/view_manager_service_impl.h"
#include "mojo/application/public/interfaces/service_provider.mojom.h"
#include "mojo/converters/geometry/geometry_type_converters.h"
#include "mojo/converters/input_events/input_events_type_converters.h"
using mojo::ConnectionSpecificId;
namespace view_manager {
namespace {
// Creates a copy of |view|. The copied view has |delegate| as its delegate.
// This does not recurse.
ServerView* CloneView(const ServerView* view, ServerViewDelegate* delegate) {
ServerView* clone = new ServerView(delegate, ClonedViewId());
clone->SetBounds(view->bounds());
clone->SetSurfaceId(view->surface_id());
clone->SetOpacity(view->opacity());
return clone;
}
// Creates copies of all the visible children of |parent|. Newly cloned views
// are added to |cloned_parent| and have |delegate| as their delegate. The
// stacking order of the cloned views is preseved.
void CloneViewTree(const ServerView* parent,
ServerView* cloned_parent,
ServerViewDelegate* delegate) {
DCHECK(parent->visible());
for (const ServerView* to_clone : parent->GetChildren()) {
if (to_clone->visible()) {
ServerView* cloned = CloneView(to_clone, delegate);
cloned_parent->Add(cloned);
CloneViewTree(to_clone, cloned, delegate);
}
}
}
// Recurses through all the children of |view| moving any cloned views to
// |new_parent| stacked above |stack_above|. |stack_above| is updated as views
// are moved.
void ReparentClonedViews(ServerView* new_parent,
ServerView** stack_above,
ServerView* view) {
if (view->id() == ClonedViewId()) {
const gfx::Rect new_bounds(ConvertRectBetweenViews(
view, new_parent, gfx::Rect(view->bounds().size())));
new_parent->Add(view);
new_parent->Reorder(view, *stack_above, mojo::ORDER_DIRECTION_ABOVE);
view->SetBounds(new_bounds);
*stack_above = view;
return;
}
for (ServerView* child : view->GetChildren())
ReparentClonedViews(new_parent, stack_above, child);
}
// Deletes |view| and all its descendants.
void DeleteViewTree(ServerView* view) {
for (ServerView* child : view->GetChildren())
DeleteViewTree(child);
delete view;
}
// TODO(sky): nuke, proof of concept.
bool DecrementAnimatingViewsOpacity(ServerView* view) {
if (view->id() == ClonedViewId()) {
const float new_opacity = view->opacity() - .05f;
if (new_opacity <= 0)
DeleteViewTree(view);
else
view->SetOpacity(new_opacity);
return true;
}
bool ret_value = false;
for (ServerView* child : view->GetChildren()) {
if (DecrementAnimatingViewsOpacity(child))
ret_value = true;
}
return ret_value;
}
} // namespace
ConnectionManager::ScopedChange::ScopedChange(
ViewManagerServiceImpl* connection,
ConnectionManager* connection_manager,
bool is_delete_view)
: connection_manager_(connection_manager),
connection_id_(connection->id()),
is_delete_view_(is_delete_view) {
connection_manager_->PrepareForChange(this);
}
ConnectionManager::ScopedChange::~ScopedChange() {
connection_manager_->FinishChange();
}
ConnectionManager::ConnectionManager(ConnectionManagerDelegate* delegate,
scoped_ptr<DisplayManager> display_manager)
: delegate_(delegate),
window_manager_client_connection_(nullptr),
next_connection_id_(1),
event_dispatcher_(this),
display_manager_(display_manager.Pass()),
root_(CreateServerView(RootViewId())),
current_change_(nullptr),
in_destructor_(false),
animation_runner_(base::TimeTicks::Now()),
focus_controller_(new FocusController(this, root_.get())) {
root_->SetBounds(gfx::Rect(800, 600));
root_->SetVisible(true);
display_manager_->Init(this, &event_dispatcher_);
}
ConnectionManager::~ConnectionManager() {
in_destructor_ = true;
// Deleting views will attempt to advance focus. When we're being destroyed
// that is not necessary. Additionally |focus_controller_| needs to be
// destroyed before |root_|.
focus_controller_.reset();
STLDeleteValues(&connection_map_);
// All the connections should have been destroyed.
DCHECK(connection_map_.empty());
root_.reset();
}
ServerView* ConnectionManager::CreateServerView(const ViewId& id) {
ServerView* view = new ServerView(this, id);
view->AddObserver(this);
return view;
}
ConnectionSpecificId ConnectionManager::GetAndAdvanceNextConnectionId() {
const ConnectionSpecificId id = next_connection_id_++;
DCHECK_LT(id, next_connection_id_);
return id;
}
void ConnectionManager::OnConnectionError(ClientConnection* connection) {
if (connection == window_manager_client_connection_) {
window_manager_client_connection_ = nullptr;
delegate_->OnLostConnectionToWindowManager();
// Assume we've been destroyed.
return;
}
scoped_ptr<ClientConnection> connection_owner(connection);
connection_map_.erase(connection->service()->id());
// TODO(sky): I may want to advance focus differently if focus is in
// |connection|.
// Notify remaining connections so that they can cleanup.
for (auto& pair : connection_map_) {
pair.second->service()->OnWillDestroyViewManagerServiceImpl(
connection->service());
}
}
void ConnectionManager::EmbedAtView(
ConnectionSpecificId creator_id,
mojo::URLRequestPtr request,
const ViewId& view_id,
mojo::InterfaceRequest<mojo::ServiceProvider> services,
mojo::ServiceProviderPtr exposed_services) {
std::string creator_url;
ConnectionMap::const_iterator it = connection_map_.find(creator_id);
if (it != connection_map_.end())
creator_url = it->second->service()->url();
mojo::ViewManagerServicePtr service_ptr;
ClientConnection* client_connection =
delegate_->CreateClientConnectionForEmbedAtView(
this, GetProxy(&service_ptr), creator_id, creator_url, request.Pass(),
view_id);
AddConnection(client_connection);
client_connection->service()->Init(client_connection->client(),
service_ptr.Pass(), services.Pass(),
exposed_services.Pass());
OnConnectionMessagedClient(client_connection->service()->id());
}
void ConnectionManager::EmbedAtView(mojo::ConnectionSpecificId creator_id,
const ViewId& view_id,
mojo::ViewManagerClientPtr client) {
std::string creator_url;
ConnectionMap::const_iterator it = connection_map_.find(creator_id);
if (it != connection_map_.end())
creator_url = it->second->service()->url();
mojo::ViewManagerServicePtr service_ptr;
ClientConnection* client_connection =
delegate_->CreateClientConnectionForEmbedAtView(
this, GetProxy(&service_ptr), creator_id, creator_url, view_id,
client.Pass());
AddConnection(client_connection);
client_connection->service()->Init(client_connection->client(),
service_ptr.Pass(), nullptr, nullptr);
OnConnectionMessagedClient(client_connection->service()->id());
}
ViewManagerServiceImpl* ConnectionManager::GetConnection(
ConnectionSpecificId connection_id) {
ConnectionMap::iterator i = connection_map_.find(connection_id);
return i == connection_map_.end() ? nullptr : i->second->service();
}
ServerView* ConnectionManager::GetView(const ViewId& id) {
if (id == root_->id())
return root_.get();
ViewManagerServiceImpl* service = GetConnection(id.connection_id);
return service ? service->GetView(id) : nullptr;
}
void ConnectionManager::SetFocusedView(ServerView* view) {
ServerView* old_focused = GetFocusedView();
if (old_focused == view)
return;
focus_controller_->SetFocusedView(view);
OnFocusChanged(old_focused, view);
}
ServerView* ConnectionManager::GetFocusedView() {
return focus_controller_->GetFocusedView();
}
void ConnectionManager::OnConnectionMessagedClient(ConnectionSpecificId id) {
if (current_change_)
current_change_->MarkConnectionAsMessaged(id);
}
bool ConnectionManager::DidConnectionMessageClient(
ConnectionSpecificId id) const {
return current_change_ && current_change_->DidMessageConnection(id);
}
const ViewManagerServiceImpl* ConnectionManager::GetConnectionWithRoot(
const ViewId& id) const {
for (auto& pair : connection_map_) {
if (pair.second->service()->IsRoot(id))
return pair.second->service();
}
return nullptr;
}
ViewManagerServiceImpl* ConnectionManager::GetEmbedRoot(
ViewManagerServiceImpl* service) {
while (service) {
const ViewId* root_id = service->root();
if (!root_id || root_id->connection_id == service->id())
return nullptr;
ViewManagerServiceImpl* parent_service =
GetConnection(root_id->connection_id);
service = parent_service;
if (service && service->is_embed_root())
return service;
}
return nullptr;
}
void ConnectionManager::SetWindowManagerClientConnection(
scoped_ptr<ClientConnection> connection) {
CHECK(!window_manager_client_connection_);
window_manager_client_connection_ = connection.release();
AddConnection(window_manager_client_connection_);
window_manager_client_connection_->service()->Init(
window_manager_client_connection_->client(), nullptr, nullptr, nullptr);
}
mojo::ViewManagerClient*
ConnectionManager::GetWindowManagerViewManagerClient() {
CHECK(window_manager_client_connection_);
return window_manager_client_connection_->client();
}
bool ConnectionManager::CloneAndAnimate(const ViewId& view_id) {
ServerView* view = GetView(view_id);
if (!view || !view->IsDrawn(root_.get()) || view == root_.get())
return false;
if (!animation_timer_.IsRunning()) {
animation_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(100),
this, &ConnectionManager::DoAnimation);
}
ServerView* clone = CloneView(view, this);
CloneViewTree(view, clone, this);
view->parent()->Add(clone);
view->parent()->Reorder(clone, view, mojo::ORDER_DIRECTION_ABOVE);
return true;
}
void ConnectionManager::ProcessEvent(mojo::EventPtr event) {
event_dispatcher_.OnEvent(event.Pass());
}
void ConnectionManager::DispatchInputEventToView(const ServerView* view,
mojo::EventPtr event) {
// It's possible for events to flow through here from the platform_window
// before any connections are established with the view_manager.
if (!has_window_manager_client_connection())
return;
// If the view is an embed root, forward to the embedded view, not the owner.
ViewManagerServiceImpl* connection = GetConnectionWithRoot(view->id());
if (!connection)
connection = GetConnection(view->id().connection_id);
CHECK(connection);
connection->client()->OnViewInputEvent(ViewIdToTransportId(view->id()),
event.Pass(),
base::Bind(&base::DoNothing));
}
void ConnectionManager::ProcessViewBoundsChanged(const ServerView* view,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds) {
for (auto& pair : connection_map_) {
pair.second->service()->ProcessViewBoundsChanged(
view, old_bounds, new_bounds, IsChangeSource(pair.first));
}
}
void ConnectionManager::ProcessViewportMetricsChanged(
const mojo::ViewportMetrics& old_metrics,
const mojo::ViewportMetrics& new_metrics) {
for (auto& pair : connection_map_) {
pair.second->service()->ProcessViewportMetricsChanged(
old_metrics, new_metrics, IsChangeSource(pair.first));
}
}
void ConnectionManager::ProcessWillChangeViewHierarchy(
const ServerView* view,
const ServerView* new_parent,
const ServerView* old_parent) {
for (auto& pair : connection_map_) {
pair.second->service()->ProcessWillChangeViewHierarchy(
view, new_parent, old_parent, IsChangeSource(pair.first));
}
}
void ConnectionManager::ProcessViewHierarchyChanged(
const ServerView* view,
const ServerView* new_parent,
const ServerView* old_parent) {
for (auto& pair : connection_map_) {
pair.second->service()->ProcessViewHierarchyChanged(
view, new_parent, old_parent, IsChangeSource(pair.first));
}
}
void ConnectionManager::ProcessViewReorder(
const ServerView* view,
const ServerView* relative_view,
const mojo::OrderDirection direction) {
for (auto& pair : connection_map_) {
pair.second->service()->ProcessViewReorder(view, relative_view, direction,
IsChangeSource(pair.first));
}
}
void ConnectionManager::ProcessViewDeleted(const ViewId& view) {
for (auto& pair : connection_map_) {
pair.second->service()->ProcessViewDeleted(view,
IsChangeSource(pair.first));
}
}
void ConnectionManager::PrepareForChange(ScopedChange* change) {
// Should only ever have one change in flight.
CHECK(!current_change_);
current_change_ = change;
}
void ConnectionManager::FinishChange() {
// PrepareForChange/FinishChange should be balanced.
CHECK(current_change_);
current_change_ = NULL;
}
void ConnectionManager::DoAnimation() {
if (!DecrementAnimatingViewsOpacity(root()))
animation_timer_.Stop();
}
void ConnectionManager::AddConnection(ClientConnection* connection) {
DCHECK_EQ(0u, connection_map_.count(connection->service()->id()));
connection_map_[connection->service()->id()] = connection;
}
void ConnectionManager::PrepareToDestroyView(ServerView* view) {
if (!in_destructor_ && root_->Contains(view) && view != root_.get() &&
view->id() != ClonedViewId()) {
// We're about to destroy a view. Any cloned views need to be reparented
// else the animation would no longer be visible. By moving to a visible
// view, view->parent(), we ensure the animation is still visible.
ServerView* parent_above = view;
ReparentClonedViews(view->parent(), &parent_above, view);
}
animation_runner_.CancelAnimationForView(view);
}
void ConnectionManager::PrepareToChangeViewHierarchy(ServerView* view,
ServerView* new_parent,
ServerView* old_parent) {
if (view->id() == ClonedViewId() || in_destructor_)
return;
if (root_->Contains(view) && view != root_.get()) {
// We're about to reparent a view. Any cloned views need to be reparented
// else the animation may be effected in unusual ways. For example, the view
// could move to a new location such that the animation is entirely clipped.
// By moving to view->parent() we ensure the animation is still visible.
ServerView* parent_above = view;
ReparentClonedViews(view->parent(), &parent_above, view);
}
animation_runner_.CancelAnimationForView(view);
}
void ConnectionManager::PrepareToChangeViewVisibility(ServerView* view) {
if (in_destructor_)
return;
if (view != root_.get() && view->id() != ClonedViewId() &&
root_->Contains(view) && view->IsDrawn(root_.get())) {
// We're about to hide |view|, this would implicitly make any cloned views
// hide too. Reparent so that animations are still visible.
ServerView* parent_above = view;
ReparentClonedViews(view->parent(), &parent_above, view);
}
const bool is_parent_drawn =
view->parent() && view->parent()->IsDrawn(root_.get());
if (!is_parent_drawn || !view->visible())
animation_runner_.CancelAnimationForView(view);
}
void ConnectionManager::OnScheduleViewPaint(const ServerView* view) {
if (!in_destructor_)
display_manager_->SchedulePaint(view, gfx::Rect(view->bounds().size()));
}
void ConnectionManager::OnViewDestroyed(ServerView* view) {
if (!in_destructor_)
ProcessViewDeleted(view->id());
}
void ConnectionManager::OnWillChangeViewHierarchy(ServerView* view,
ServerView* new_parent,
ServerView* old_parent) {
if (view->id() == ClonedViewId() || in_destructor_)
return;
ProcessWillChangeViewHierarchy(view, new_parent, old_parent);
}
void ConnectionManager::OnViewHierarchyChanged(ServerView* view,
ServerView* new_parent,
ServerView* old_parent) {
if (in_destructor_)
return;
ProcessViewHierarchyChanged(view, new_parent, old_parent);
// TODO(beng): optimize.
if (old_parent) {
display_manager_->SchedulePaint(old_parent,
gfx::Rect(old_parent->bounds().size()));
}
if (new_parent) {
display_manager_->SchedulePaint(new_parent,
gfx::Rect(new_parent->bounds().size()));
}
}
void ConnectionManager::OnViewBoundsChanged(ServerView* view,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds) {
if (in_destructor_)
return;
ProcessViewBoundsChanged(view, old_bounds, new_bounds);
if (!view->parent())
return;
// TODO(sky): optimize this.
display_manager_->SchedulePaint(view->parent(), old_bounds);
display_manager_->SchedulePaint(view->parent(), new_bounds);
}
void ConnectionManager::OnViewReordered(ServerView* view,
ServerView* relative,
mojo::OrderDirection direction) {
if (!in_destructor_)
display_manager_->SchedulePaint(view, gfx::Rect(view->bounds().size()));
}
void ConnectionManager::OnWillChangeViewVisibility(ServerView* view) {
if (in_destructor_)
return;
// Need to repaint if the view was drawn (which means it's in the process of
// hiding) or the view is transitioning to drawn.
if (view->IsDrawn(root_.get()) || (!view->visible() && view->parent() &&
view->parent()->IsDrawn(root_.get()))) {
display_manager_->SchedulePaint(view->parent(), view->bounds());
}
for (auto& pair : connection_map_) {
pair.second->service()->ProcessWillChangeViewVisibility(
view, IsChangeSource(pair.first));
}
}
void ConnectionManager::OnViewSharedPropertyChanged(
ServerView* view,
const std::string& name,
const std::vector<uint8_t>* new_data) {
for (auto& pair : connection_map_) {
pair.second->service()->ProcessViewPropertyChanged(
view, name, new_data, IsChangeSource(pair.first));
}
}
void ConnectionManager::SetViewManagerRootClient(
mojo::ViewManagerRootClientPtr client) {
view_manager_root_client_ = client.Pass();
}
void ConnectionManager::SetViewportSize(mojo::SizePtr size) {
display_manager_->SetViewportSize(size.To<gfx::Size>());
}
void ConnectionManager::CloneAndAnimate(mojo::Id transport_view_id) {
CloneAndAnimate(ViewIdFromTransportId(transport_view_id));
}
void ConnectionManager::AddAccelerator(mojo::KeyboardCode keyboard_code,
mojo::EventFlags flags) {
event_dispatcher_.AddAccelerator(keyboard_code, flags);
}
void ConnectionManager::RemoveAccelerator(mojo::KeyboardCode keyboard_code,
mojo::EventFlags flags) {
event_dispatcher_.RemoveAccelerator(keyboard_code, flags);
}
void ConnectionManager::OnFocusChanged(ServerView* old_focused_view,
ServerView* new_focused_view) {
// There are up to four connections that need to be notified:
// . the connection containing |old_focused_view|.
// . the connection with |old_focused_view| as its root.
// . the connection containing |new_focused_view|.
// . the connection with |new_focused_view| as its root.
// Some of these connections may be the same. The following takes care to
// notify each only once.
ViewManagerServiceImpl* owning_connection_old = nullptr;
ViewManagerServiceImpl* embedded_connection_old = nullptr;
if (old_focused_view) {
owning_connection_old = GetConnection(old_focused_view->id().connection_id);
if (owning_connection_old) {
owning_connection_old->ProcessFocusChanged(old_focused_view,
new_focused_view);
}
embedded_connection_old = GetConnectionWithRoot(old_focused_view->id());
if (embedded_connection_old) {
DCHECK_NE(owning_connection_old, embedded_connection_old);
embedded_connection_old->ProcessFocusChanged(old_focused_view,
new_focused_view);
}
}
ViewManagerServiceImpl* owning_connection_new = nullptr;
ViewManagerServiceImpl* embedded_connection_new = nullptr;
if (new_focused_view) {
owning_connection_new = GetConnection(new_focused_view->id().connection_id);
if (owning_connection_new &&
owning_connection_new != owning_connection_old &&
owning_connection_new != embedded_connection_old) {
owning_connection_new->ProcessFocusChanged(old_focused_view,
new_focused_view);
}
embedded_connection_new = GetConnectionWithRoot(new_focused_view->id());
if (embedded_connection_new &&
embedded_connection_new != owning_connection_old &&
embedded_connection_new != embedded_connection_old) {
DCHECK_NE(owning_connection_new, embedded_connection_new);
embedded_connection_new->ProcessFocusChanged(old_focused_view,
new_focused_view);
}
}
if (has_window_manager_client_connection()) {
// Window manager should always be notified of focus change.
ViewManagerServiceImpl* wm_connection =
window_manager_client_connection_->service();
if (wm_connection != owning_connection_old &&
wm_connection != embedded_connection_old &&
wm_connection != owning_connection_new &&
wm_connection != embedded_connection_new) {
wm_connection->ProcessFocusChanged(old_focused_view, new_focused_view);
}
}
}
} // namespace view_manager