blob: 76052207973e0b1f92a6c889291ddf66075e645a [file] [log] [blame]
// Copyright 2018 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/ui_devtools/viz/dom_agent_viz.h"
#include "base/stl_util.h"
#include "components/ui_devtools/root_element.h"
#include "components/ui_devtools/ui_element.h"
#include "components/ui_devtools/viz/frame_sink_element.h"
#include "components/ui_devtools/viz/surface_element.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "components/viz/common/surfaces/surface_id.h"
#include "components/viz/common/surfaces/surface_info.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "components/viz/service/surfaces/surface_manager.h"
namespace ui_devtools {
// Updating logic for FrameSinks:
//
// 1. Creating. We register a FrameSinkId, create a CompositorFrameSink and if a
// CompositorFrameSink is not a root we register hierarchy from the parent of
// this FrameSink to this CompositorFrameSink. When we register a FrameSinkId,
// we check if its corresponding element is already in the tree. If not, we
// attach it to the RootElement which serves as the root of the
// CompositorFrameSink tree. In this state the CompositorFrameSink is considered
// unembedded and it is a sibling of RootCompositorFrameSinks. If it is present
// in a tree we just change the properties (|has_created_frame_sink_|).
// These events don't know anything about the hierarchy
// so we don't change it. When we get OnRegisteredHierarchy from parent to child
// the corresponding elements must already be present in a tree. The usual state
// is: child is attached to RootElement and now we will detach it from the
// RootElement and attach to the real parent. During handling this event we
// actually delete the subtree of RootElement rooted from child and create a new
// subtree of parent. This potentially could be more efficient, but if we just
// switch necessary pointers we must send a notification to the frontend so it
// can update the UI. Unfortunately, this action involves deleting a node from
// the backend list of UI Elements (even when it is still alive) and trying to
// delete it once again (for instance, when we close a corresponding tab) causes
// crash.
//
// 2. Deleting. We unregister hierarchy, destroy a CompositorFrameSink and
// invalidate a FrameSinkId. When we invalidate an FrameSinkId or destroy a
// FrameSink we check if it's the last action that has to happen with the
// corresponding element. For example, if the element has
// |has_created_frame_sink_| = true and we get a |OnDestroyedFrameSink| event we
// just set |has_created_frame_sink_| = false, but don't remove it from a tree,
// because its FrameSinkId is still registered, so it's not completely dead. But
// when after that we get |OnInvalidatedFrameSinkId| we can remove the
// node from the tree. When we get OnUnregisteredHierarchy we assume the nodes
// are still present in a tree, so we do the same work as we did in registering
// case. Only here we move a subtree of parent rooted from child to the
// RootElement. Obviously, now the child will be in detached state.
//
// Updating logic for Surfaces:
// 1. Creating. We create a surface and then add reference to it.
// SurfaceManager::root_surface_id_ is treated like a regular surface id, so
// every time we add a reference from SurfaceManager::root_surface_id_ we add
// the child to the associated SurfaceElement.
//
// 2. Deleting. We remove the reference and destroy a Surface.
//
// Although this overview is relatively shorter than for FrameSinks and we have
// a smaller set of actions handling updates in the Surface tree is actually
// more difficult. Because of no particular order of the events we may face the
// cases when more than one surface references some surface or a surface doesn't
// remove its reference and just gets destroyed instead. The intermediate state
// between state A when surface hierarchy is a tree and state B when surface
// hierarchy is a tree again can be ambiguous and may not follow the tree
// structure. Current approach of handling these states needs revisiting.
using namespace ui_devtools::protocol;
DOMAgentViz::DOMAgentViz(viz::FrameSinkManagerImpl* frame_sink_manager)
: frame_sink_manager_(frame_sink_manager),
surface_manager_(frame_sink_manager->surface_manager()) {}
DOMAgentViz::~DOMAgentViz() {
Clear();
}
void DOMAgentViz::OnFirstSurfaceActivation(
const viz::SurfaceInfo& surface_info) {
// The surface was just created. No one embedded it yet, so just attach
// it to RootElement. Sometimes OnAddedSurfaceReference is called first, so
// don't create the element if it already exists.
const viz::SurfaceId& surface_id = surface_info.id();
if (!base::ContainsKey(surface_elements_, surface_id)) {
UIElement* surface_root = GetRootSurfaceElement();
surface_root->AddChild(CreateSurfaceElement(surface_id, surface_root));
}
}
bool DOMAgentViz::OnSurfaceDamaged(const viz::SurfaceId& surface_id,
const viz::BeginFrameAck& ack) {
return false;
}
void DOMAgentViz::OnSurfaceDiscarded(const viz::SurfaceId& surface_id) {
// We may come across the case where we delete element, but its children
// are still alive. Therefore we should attach children to the RootElement
// and then delete this element.
auto it = surface_elements_.find(surface_id);
DCHECK(it != surface_elements_.end());
DestroyElementAndRemoveSubtree(it->second.get());
}
// TODO(sgilhuly): Add support for elements to have multiple parents. Currently,
// when a reference is added to a surface, the SurfaceElement is moved to be a
// child of only its most recent referrer. When a reference is removed from a
// surface, this is ignored unless the reference is to the SurfaceElement's
// current parent.
void DOMAgentViz::OnAddedSurfaceReference(const viz::SurfaceId& parent_id,
const viz::SurfaceId& child_id) {
// Detach child element from its current parent and attach to the new parent.
// OnAddedSurfaceReference is often called before OnFirstSurfaceActivation, so
// create the element if it does not exist.
auto it_parent = surface_elements_.find(parent_id);
if (it_parent == surface_elements_.end()) {
UIElement* surface_root = GetRootSurfaceElement();
surface_root->AddChild(CreateSurfaceElement(parent_id, surface_root));
// The subtree is populated in AddChild, so we don't need to do anything
// else here.
return;
}
UIElement* parent = it_parent->second.get();
// It's possible that OnFirstSurfaceActivation hasn't been called yet, so
// create a new element as a child of |parent| if it doesn't already exist.
auto it_child = surface_elements_.find(child_id);
if (it_child == surface_elements_.end()) {
parent->AddChild(CreateSurfaceElement(child_id, parent));
} else {
UIElement* child = it_child->second.get();
Reparent(parent, child);
}
}
void DOMAgentViz::OnRemovedSurfaceReference(const viz::SurfaceId& parent_id,
const viz::SurfaceId& child_id) {
// Detach child element from its current parent and attach to the root
// surface.
auto it_child = surface_elements_.find(child_id);
DCHECK(it_child != surface_elements_.end());
UIElement* child = it_child->second.get();
// Do nothing if parent is not a parent of this child anymore. This can
// happen when we have Surface A referencing Surface B, then we create
// Surface C and ask it to reference Surface B. When A asks to remove
// the reference to B, do nothing because B is already referenced by C.
// TODO(sgilhuly): Add support for elements to have multiple parents so this
// case can be correctly handled.
UIElement* old_parent = child->parent();
if (SurfaceElement::From(old_parent) != parent_id)
return;
Reparent(GetRootSurfaceElement(), child);
}
void DOMAgentViz::OnRegisteredFrameSinkId(
const viz::FrameSinkId& frame_sink_id) {
// If a FrameSink was just registered we don't know anything about
// hierarchy. So we should attach it to the RootElement.
element_root()->AddChild(
CreateFrameSinkElement(frame_sink_id, element_root(), /*is_root=*/false,
/*has_created_frame_sink=*/false));
}
void DOMAgentViz::OnInvalidatedFrameSinkId(
const viz::FrameSinkId& frame_sink_id) {
auto it = frame_sink_elements_.find(frame_sink_id);
DCHECK(it != frame_sink_elements_.end());
// A FrameSinkElement with |frame_sink_id| can only be invalidated after
// being destroyed.
DCHECK(!it->second->has_created_frame_sink());
DestroyElementAndRemoveSubtree(it->second.get());
}
void DOMAgentViz::OnCreatedCompositorFrameSink(
const viz::FrameSinkId& frame_sink_id,
bool is_root) {
auto it = frame_sink_elements_.find(frame_sink_id);
DCHECK(it != frame_sink_elements_.end());
// The corresponding element is already present in a tree, so we
// should update its |has_created_frame_sink_| and |is_root_| properties.
it->second->SetHasCreatedFrameSink(true);
it->second->SetRoot(is_root);
}
void DOMAgentViz::OnDestroyedCompositorFrameSink(
const viz::FrameSinkId& frame_sink_id) {
auto it = frame_sink_elements_.find(frame_sink_id);
DCHECK(it != frame_sink_elements_.end());
// Set FrameSinkElement to not connected to mark it as destroyed.
it->second->SetHasCreatedFrameSink(false);
}
void DOMAgentViz::OnRegisteredFrameSinkHierarchy(
const viz::FrameSinkId& parent_frame_sink_id,
const viz::FrameSinkId& child_frame_sink_id) {
// At this point these elements must be present in a tree.
// We should detach a child from its current parent and attach to the new
// parent.
auto it_parent = frame_sink_elements_.find(parent_frame_sink_id);
auto it_child = frame_sink_elements_.find(child_frame_sink_id);
DCHECK(it_parent != frame_sink_elements_.end());
DCHECK(it_child != frame_sink_elements_.end());
FrameSinkElement* child = it_child->second.get();
FrameSinkElement* new_parent = it_parent->second.get();
// TODO: Add support for |child| to have multiple parents.
Reparent(new_parent, child);
}
void DOMAgentViz::OnUnregisteredFrameSinkHierarchy(
const viz::FrameSinkId& parent_frame_sink_id,
const viz::FrameSinkId& child_frame_sink_id) {
// At this point these elements must be present in a tree.
// We should detach a child from its current parent and attach to the
// RootElement since it wasn't destroyed yet.
auto it_child = frame_sink_elements_.find(child_frame_sink_id);
DCHECK(it_child != frame_sink_elements_.end());
FrameSinkElement* child = it_child->second.get();
// TODO: Add support for |child| to have multiple parents: only adds |child|
// to RootElement if all parents of |child| are unregistered.
Reparent(element_root(), child);
}
SurfaceElement* DOMAgentViz::GetRootSurfaceElement() {
auto it = surface_elements_.find(surface_manager_->GetRootSurfaceId());
DCHECK(it != surface_elements_.end());
return it->second.get();
}
std::unique_ptr<DOM::Node> DOMAgentViz::BuildTreeForFrameSink(
UIElement* parent_element,
const viz::FrameSinkId& parent_id) {
std::unique_ptr<Array<DOM::Node>> children = Array<DOM::Node>::create();
// Once the FrameSinkElement is created it calls this function to build its
// subtree. We iterate through |parent_element|'s children and
// recursively build the subtree for them.
for (auto& child_id : frame_sink_manager_->GetChildrenByParent(parent_id)) {
bool has_created_frame_sink =
!!frame_sink_manager_->GetFrameSinkForId(child_id);
FrameSinkElement* child_element = CreateFrameSinkElement(
child_id, parent_element, /*is_root=*/false, has_created_frame_sink);
children->addItem(BuildTreeForFrameSink(child_element, child_id));
parent_element->AddChild(child_element);
}
return BuildNode("FrameSink", parent_element->GetAttributes(),
std::move(children), parent_element->node_id());
}
std::unique_ptr<DOM::Node> DOMAgentViz::BuildTreeForSurface(
UIElement* parent_element,
const viz::SurfaceId& parent_id) {
std::unique_ptr<Array<DOM::Node>> children = Array<DOM::Node>::create();
// Once the SurfaceElement is created it calls this function to build its
// subtree. We iterate through |parent_element|'s children and
// recursively build the subtree for them.
for (auto& child_id :
surface_manager_->GetSurfacesReferencedByParent(parent_id)) {
// If the child element exists already, destroy it and rebuild here.
auto it_child = surface_elements_.find(child_id);
if (it_child != surface_elements_.end())
DestroyElementAndRemoveSubtree(it_child->second.get());
SurfaceElement* child_element =
CreateSurfaceElement(child_id, parent_element);
children->addItem(BuildTreeForSurface(child_element, child_id));
parent_element->AddChild(child_element);
}
return BuildNode("Surface", parent_element->GetAttributes(),
std::move(children), parent_element->node_id());
}
protocol::Response DOMAgentViz::enable() {
frame_sink_manager_->AddObserver(this);
surface_manager_->AddObserver(this);
return protocol::Response::OK();
}
protocol::Response DOMAgentViz::disable() {
frame_sink_manager_->RemoveObserver(this);
surface_manager_->RemoveObserver(this);
Clear();
return DOMAgent::disable();
}
std::vector<UIElement*> DOMAgentViz::CreateChildrenForRoot() {
std::vector<UIElement*> children;
// All of the FrameSinkElements and SurfaceElements are owned here, so make
// sure the root element doesn't delete our pointers.
element_root()->set_owns_children(false);
// Find all elements that are not part of any hierarchy. This will be
// FrameSinks that are either root, or detached.
std::vector<viz::FrameSinkId> registered_frame_sinks =
frame_sink_manager_->GetRegisteredFrameSinkIds();
base::flat_set<viz::FrameSinkId> detached_frame_sinks(registered_frame_sinks);
for (auto& frame_sink_id : registered_frame_sinks) {
for (auto& child_id :
frame_sink_manager_->GetChildrenByParent(frame_sink_id)) {
detached_frame_sinks.erase(child_id);
}
}
// Add created RootFrameSinks and detached FrameSinks.
for (auto& frame_sink_id : detached_frame_sinks) {
const viz::CompositorFrameSinkSupport* support =
frame_sink_manager_->GetFrameSinkForId(frame_sink_id);
bool is_root = support && support->is_root();
bool has_created_frame_sink = !!support;
children.push_back(CreateFrameSinkElement(frame_sink_id, element_root(),
is_root, has_created_frame_sink));
}
// Add the root surface.
children.push_back(CreateSurfaceElement(surface_manager_->GetRootSurfaceId(),
element_root()));
return children;
}
std::unique_ptr<DOM::Node> DOMAgentViz::BuildTreeForUIElement(
UIElement* ui_element) {
if (ui_element->type() == UIElementType::FRAMESINK) {
return BuildTreeForFrameSink(ui_element,
FrameSinkElement::From(ui_element));
} else if (ui_element->type() == UIElementType::SURFACE) {
return BuildTreeForSurface(ui_element, SurfaceElement::From(ui_element));
}
return nullptr;
}
void DOMAgentViz::Clear() {
frame_sink_elements_.clear();
surface_elements_.clear();
}
void DOMAgentViz::DestroyElementAndRemoveSubtree(UIElement* element) {
// We may come across the case where we've got the event to delete the
// FrameSink or Surface, but we haven't got events to delete its children. We
// should detach all its children and attach them to either RootElement or the
// root surface, and then delete the node we were asked for.
UIElement* new_parent =
(element->type() == SURFACE ? GetRootSurfaceElement() : element_root());
// Make a copy of the list of children, so that it isn't affected when
// elements are moved.
std::vector<UIElement*> children(element->children());
for (auto* child : children)
Reparent(new_parent, child);
element->parent()->RemoveChild(element);
DestroyElement(element);
}
void DOMAgentViz::Reparent(UIElement* new_parent, UIElement* child) {
if (new_parent == child->parent())
return;
DestroySubtree(child);
// This removes the child element from the Node map. It has to be added with
// null parent to recreate the entry.
child->parent()->RemoveChild(child);
OnUIElementAdded(nullptr, child);
new_parent->AddChild(child);
child->set_parent(new_parent);
}
void DOMAgentViz::DestroyElement(UIElement* element) {
if (element->type() == UIElementType::FRAMESINK) {
frame_sink_elements_.erase(FrameSinkElement::From(element));
} else if (element->type() == UIElementType::SURFACE) {
surface_elements_.erase(SurfaceElement::From(element));
} else {
NOTREACHED();
}
}
void DOMAgentViz::DestroySubtree(UIElement* element) {
for (auto* child : element->children()) {
DestroySubtree(child);
DestroyElement(child);
}
element->ClearChildren();
}
FrameSinkElement* DOMAgentViz::CreateFrameSinkElement(
const viz::FrameSinkId& frame_sink_id,
UIElement* parent,
bool is_root,
bool is_client_connected) {
DCHECK(!base::ContainsKey(frame_sink_elements_, frame_sink_id));
frame_sink_elements_[frame_sink_id] = std::make_unique<FrameSinkElement>(
frame_sink_id, frame_sink_manager_, this, parent, is_root,
is_client_connected);
return frame_sink_elements_[frame_sink_id].get();
}
SurfaceElement* DOMAgentViz::CreateSurfaceElement(
const viz::SurfaceId& surface_id,
UIElement* parent) {
DCHECK(!base::ContainsKey(surface_elements_, surface_id));
surface_elements_[surface_id] = std::make_unique<SurfaceElement>(
surface_id, frame_sink_manager_, this, parent);
return surface_elements_[surface_id].get();
}
} // namespace ui_devtools