blob: 78f6b509e37dd31be3802ad2640ac6825aefbe63 [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_views/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_views/frame_sink_element.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.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 (|is_registered_| or
// |is_client_connected_|). 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 |is_client_connected_|
// = true and |is_registered_| = true and we get a |OnDestroyedFrameSink| event
// we just set |is_client_connected_| = 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 set |is_registered_| =
// false and since both these fields are false we can go ahead and 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.
using namespace ui_devtools::protocol;
DOMAgentViz::DOMAgentViz(viz::FrameSinkManagerImpl* frame_sink_manager)
: frame_sink_manager_(frame_sink_manager) {}
DOMAgentViz::~DOMAgentViz() {
Clear();
}
void DOMAgentViz::OnRegisteredFrameSinkId(
const viz::FrameSinkId& frame_sink_id) {
if (!base::ContainsKey(frame_sink_elements_, 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(
new FrameSinkElement(frame_sink_id, frame_sink_manager_, this,
element_root(), /*is_root=*/false,
/*is_registered=*/true,
/*is_client_connected=*/false),
element_root()->children().empty() ? nullptr
: element_root()->children().back());
}
}
void DOMAgentViz::OnInvalidatedFrameSinkId(
const viz::FrameSinkId& frame_sink_id) {
auto it = frame_sink_elements_.find(frame_sink_id);
DCHECK(it != frame_sink_elements_.end());
// Destroy the FrameSinkElement |element| after updating the frame-tree.
std::unique_ptr<FrameSinkElement> element(it->second);
element->SetRegistered(false);
// A FrameSinkElement with |frame_sink_id| can only be invalidated after
// being destroyed.
DCHECK(!element->is_client_connected());
RemoveFrameSinkSubtree(element.get());
frame_sink_elements_.erase(frame_sink_id);
}
void DOMAgentViz::OnCreatedCompositorFrameSink(
const viz::FrameSinkId& frame_sink_id,
bool is_root) {
auto frame_sink_element = frame_sink_elements_.find(frame_sink_id);
DCHECK(frame_sink_element != frame_sink_elements_.end());
// The corresponding element is already present in a tree, so we
// should update its |is_client_connected_| and |is_root_| properties.
frame_sink_element->second->SetClientConnected(true);
frame_sink_element->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());
FrameSinkElement* element = it->second;
// Set FrameSinkElement to not connected to make it as destroyed.
element->SetClientConnected(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;
FrameSinkElement* new_parent = it_parent->second;
// TODO: Add support for |child| to have multiple parents.
if (child->parent())
child->parent()->RemoveChild(child);
new_parent->AddChild(child);
child->set_parent(new_parent);
}
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_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;
FrameSinkElement* parent = it_parent->second;
parent->RemoveChild(child);
// TODO: Add support for |child| to have multiple parents: only adds |child|
// to RootElement if all parents of |child| are unregistered.
child->set_parent(element_root());
}
std::unique_ptr<DOM::Node> DOMAgentViz::BuildTreeForFrameSink(
FrameSinkElement* frame_sink_element,
const viz::FrameSinkId& frame_sink_id) {
frame_sink_elements_.emplace(frame_sink_id, frame_sink_element);
std::unique_ptr<Array<DOM::Node>> children = Array<DOM::Node>::create();
// Once the FrameSinkElement is created it calls this function to build its
// subtree. So we iterate through |frame_sink_element|'s children and
// recursively build the subtree for them.
for (auto& child : frame_sink_manager_->GetChildrenByParent(frame_sink_id)) {
bool is_registered = base::ContainsValue(
frame_sink_manager_->GetRegisteredFrameSinkIds(), frame_sink_id);
bool is_client_connected =
is_registered &&
base::ContainsValue(frame_sink_manager_->GetCreatedFrameSinkIds(),
frame_sink_id);
FrameSinkElement* f_s_element = new FrameSinkElement(
child, frame_sink_manager_, this, frame_sink_element,
/*is_root=*/false, is_registered, is_client_connected);
children->addItem(BuildTreeForFrameSink(f_s_element, child));
frame_sink_element->AddChild(f_s_element);
}
std::unique_ptr<DOM::Node> node =
BuildNode("FrameSink", frame_sink_element->GetAttributes(),
std::move(children), frame_sink_element->node_id());
return node;
}
protocol::Response DOMAgentViz::enable() {
InitFrameSinkSets();
frame_sink_manager_->AddObserver(this);
return protocol::Response::OK();
}
protocol::Response DOMAgentViz::disable() {
frame_sink_manager_->RemoveObserver(this);
Clear();
return DOMAgent::disable();
}
std::vector<UIElement*> DOMAgentViz::CreateChildrenForRoot() {
std::vector<UIElement*> children;
// Add created RootFrameSinks and detached FrameSinks.
for (auto& frame_sink_id : frame_sink_manager_->GetRegisteredFrameSinkIds()) {
if (base::ContainsValue(frame_sink_manager_->GetCreatedFrameSinkIds(),
frame_sink_id)) {
const viz::CompositorFrameSinkSupport* support =
frame_sink_manager_->GetFrameSinkForId(frame_sink_id);
// Do nothing if it's a non-detached non-root FrameSink.
if (support && !support->is_root() &&
attached_frame_sinks_.find(frame_sink_id) !=
attached_frame_sinks_.end()) {
continue;
}
bool is_root = support && support->is_root();
UIElement* frame_sink_element = new FrameSinkElement(
frame_sink_id, frame_sink_manager_, this, element_root(), is_root,
/*is_registered=*/true, /*is_client_connected=*/true);
children.push_back(frame_sink_element);
// Add registered but not created FrameSinks. If a FrameSinkId was
// registered but not created we don't really know whether it's a root or
// not. And we don't know any information about the hierarchy. Therefore
// we process FrameSinks that are in the correct state first and only
// after that we process registered but not created FrameSinks. We
// consider them unembedded as well.
} else {
UIElement* frame_sink_element = new FrameSinkElement(
frame_sink_id, frame_sink_manager_, this, element_root(),
/*is_root=*/false, /*is_registered=*/true,
/*is_client_connected=*/false);
children.push_back(frame_sink_element);
}
}
return children;
}
std::unique_ptr<DOM::Node> DOMAgentViz::BuildTreeForUIElement(
UIElement* ui_element) {
if (ui_element->type() == UIElementType::FRAMESINK) {
viz::FrameSinkId frame_sink_id = FrameSinkElement::From(ui_element);
bool is_registered = base::ContainsValue(
frame_sink_manager_->GetRegisteredFrameSinkIds(), frame_sink_id);
bool is_client_connected =
is_registered &&
base::ContainsValue(frame_sink_manager_->GetCreatedFrameSinkIds(),
frame_sink_id);
FrameSinkElement* frame_sink_element = new FrameSinkElement(
frame_sink_id, frame_sink_manager_, this, nullptr,
/*is_root=*/false, is_registered, is_client_connected);
return BuildTreeForFrameSink(frame_sink_element, frame_sink_id);
}
return nullptr;
}
void DOMAgentViz::Clear() {
attached_frame_sinks_.clear();
frame_sink_elements_.clear();
}
void DOMAgentViz::InitFrameSinkSets() {
// Init the |attached_frame_sinks_| set. All RootFrameSinks and accessible
// from roots are attached. All the others are detached.
for (auto& frame_sink_id : frame_sink_manager_->GetRegisteredFrameSinkIds()) {
if (base::ContainsValue(frame_sink_manager_->GetCreatedFrameSinkIds(),
frame_sink_id)) {
const viz::CompositorFrameSinkSupport* support =
frame_sink_manager_->GetFrameSinkForId(frame_sink_id);
// Start only from roots.
if (!support || !support->is_root())
continue;
SetAttachedFrameSink(frame_sink_id);
}
}
}
void DOMAgentViz::SetAttachedFrameSink(const viz::FrameSinkId& frame_sink_id) {
attached_frame_sinks_.insert(frame_sink_id);
for (auto& child : frame_sink_manager_->GetChildrenByParent(frame_sink_id))
SetAttachedFrameSink(child);
}
void DOMAgentViz::RemoveFrameSinkSubtree(UIElement* root) {
// We may come across the case where we've got the event to delete the
// FrameSink, but we haven't got events to delete its children. We should
// detach all its children and attach them to RootElement and then delete the
// node we were asked for.
std::vector<viz::FrameSinkId> children;
for (auto* child : root->children())
child->set_parent(element_root());
if (root->parent())
root->parent()->RemoveChild(root);
}
} // namespace ui_devtools