blob: d9b596a6681f1accc8d2ee43f0cda559ce4d34f2 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/ui_devtools/views/view_element.h"
#include <algorithm>
#include "base/containers/contains.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/ui_devtools/protocol.h"
#include "components/ui_devtools/ui_element_delegate.h"
#include "components/ui_devtools/views/devtools_event_util.h"
#include "components/ui_devtools/views/element_utility.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/metadata/metadata_types.h"
#include "ui/gfx/color_utils.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/interaction/element_tracker_views.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"
namespace ui_devtools {
namespace {
ui::EventType GetMouseEventType(const std::string& type) {
if (type == protocol::DOM::MouseEvent::TypeEnum::MousePressed)
return ui::EventType::kMousePressed;
if (type == protocol::DOM::MouseEvent::TypeEnum::MouseDragged)
return ui::EventType::kMouseDragged;
if (type == protocol::DOM::MouseEvent::TypeEnum::MouseReleased)
return ui::EventType::kMouseReleased;
if (type == protocol::DOM::MouseEvent::TypeEnum::MouseMoved)
return ui::EventType::kMouseMoved;
if (type == protocol::DOM::MouseEvent::TypeEnum::MouseEntered)
return ui::EventType::kMouseEntered;
if (type == protocol::DOM::MouseEvent::TypeEnum::MouseExited)
return ui::EventType::kMouseExited;
if (type == protocol::DOM::MouseEvent::TypeEnum::MouseWheel)
return ui::EventType::kMousewheel;
return ui::EventType::kUnknown;
}
int GetButtonFlags(const std::string& button) {
if (button == protocol::DOM::MouseEvent::ButtonEnum::Left)
return ui::EF_LEFT_MOUSE_BUTTON;
if (button == protocol::DOM::MouseEvent::ButtonEnum::Right)
return ui::EF_RIGHT_MOUSE_BUTTON;
if (button == protocol::DOM::MouseEvent::ButtonEnum::Middle)
return ui::EF_MIDDLE_MOUSE_BUTTON;
if (button == protocol::DOM::MouseEvent::ButtonEnum::Back)
return ui::EF_BACK_MOUSE_BUTTON;
if (button == protocol::DOM::MouseEvent::ButtonEnum::Forward)
return ui::EF_FORWARD_MOUSE_BUTTON;
return ui::EF_NONE;
}
int GetMouseWheelXOffset(const std::string& mouse_wheel_direction) {
if (mouse_wheel_direction ==
protocol::DOM::MouseEvent::WheelDirectionEnum::Left)
return ui::MouseWheelEvent::kWheelDelta;
if (mouse_wheel_direction ==
protocol::DOM::MouseEvent::WheelDirectionEnum::Right)
return -ui::MouseWheelEvent::kWheelDelta;
return 0;
}
int GetMouseWheelYOffset(const std::string& mouse_wheel_direction) {
if (mouse_wheel_direction ==
protocol::DOM::MouseEvent::WheelDirectionEnum::Up)
return ui::MouseWheelEvent::kWheelDelta;
if (mouse_wheel_direction ==
protocol::DOM::MouseEvent::WheelDirectionEnum::Down)
return -ui::MouseWheelEvent::kWheelDelta;
return 0;
}
} // namespace
ViewElement::ViewElement(views::View* view,
UIElementDelegate* ui_element_delegate,
UIElement* parent)
: UIElementWithMetaData(UIElementType::VIEW, ui_element_delegate, parent),
view_(view) {
observer_.Observe(view_.get());
}
ViewElement::~ViewElement() = default;
void ViewElement::OnChildViewRemoved(views::View* parent, views::View* view) {
DCHECK_EQ(parent, view_);
auto iter = std::ranges::find(children(), view, [](UIElement* child) {
return UIElement::GetBackingElement<views::View, ViewElement>(child);
});
if (iter == children().end()) {
RebuildTree();
return;
}
UIElement* child_element = *iter;
RemoveChild(child_element);
delete child_element;
}
void ViewElement::OnChildViewAdded(views::View* parent, views::View* view) {
DCHECK_EQ(parent, view_);
if (base::Contains(children(), view, [](UIElement* child) {
return UIElement::GetBackingElement<views::View, ViewElement>(child);
})) {
RebuildTree();
return;
}
AddChild(new ViewElement(view, delegate(), this));
}
void ViewElement::OnChildViewReordered(views::View* parent, views::View* view) {
DCHECK_EQ(parent, view_);
auto iter = std::ranges::find(children(), view, [](UIElement* child) {
return UIElement::GetBackingElement<views::View, ViewElement>(child);
});
if (iter == children().end() ||
children().size() != view_->children().size()) {
RebuildTree();
return;
}
UIElement* child_element = *iter;
ReorderChild(child_element, parent->GetIndexOf(view).value());
}
void ViewElement::OnViewBoundsChanged(views::View* view) {
DCHECK_EQ(view_, view);
delegate()->OnUIElementBoundsChanged(this);
}
void ViewElement::GetBounds(gfx::Rect* bounds) const {
*bounds = view_->bounds();
}
void ViewElement::SetBounds(const gfx::Rect& bounds) {
view_->SetBoundsRect(bounds);
}
std::vector<std::string> ViewElement::GetAttributes() const {
// TODO(lgrey): Change name to class after updating tests.
return {"class", std::string(view_->GetClassName()), "name",
view_->GetObjectName()};
}
std::pair<gfx::NativeWindow, gfx::Rect>
ViewElement::GetNodeWindowAndScreenBounds() const {
return std::make_pair(view_->GetWidget()->GetNativeWindow(),
view_->GetBoundsInScreen());
}
// static
views::View* ViewElement::From(const UIElement* element) {
DCHECK_EQ(UIElementType::VIEW, element->type());
return static_cast<const ViewElement*>(element)->view_;
}
template <>
int UIElement::FindUIElementIdForBackendElement<views::View>(
views::View* element) const {
if (type_ == UIElementType::VIEW &&
UIElement::GetBackingElement<views::View, ViewElement>(this) == element) {
return node_id_;
}
for (ui_devtools::UIElement* child : children_) {
int ui_element_id = child->FindUIElementIdForBackendElement(element);
if (ui_element_id)
return ui_element_id;
}
return 0;
}
void ViewElement::PaintRect() const {
view()->SchedulePaint();
}
bool ViewElement::FindMatchByElementID(
const ui::ElementIdentifier& identifier) {
return base::Contains(views::ElementTrackerViews::GetInstance()
->GetAllMatchingViewsInAnyContext(identifier),
view_);
}
bool ViewElement::DispatchMouseEvent(protocol::DOM::MouseEvent* event) {
ui::EventType event_type = GetMouseEventType(event->getType());
int button_flags = GetButtonFlags(event->getButton());
if (event_type == ui::EventType::kUnknown) {
return false;
}
gfx::Point location(event->getX(), event->getY());
if (event_type == ui::EventType::kMousewheel) {
int x_offset = GetMouseWheelXOffset(event->getWheelDirection());
int y_offset = GetMouseWheelYOffset(event->getWheelDirection());
ui::MouseWheelEvent mouse_wheel_event(
gfx::Vector2d(x_offset, y_offset), location, location,
ui::EventTimeForNow(), button_flags, button_flags);
view_->OnMouseWheel(mouse_wheel_event);
} else {
ui::MouseEvent mouse_event(event_type, location, location,
ui::EventTimeForNow(), button_flags,
button_flags);
view_->OnMouseEvent(&mouse_event);
}
return true;
}
bool ViewElement::DispatchKeyEvent(protocol::DOM::KeyEvent* event) {
ui::KeyEvent key_event = ConvertToUIKeyEvent(event);
// Key events are processed differently based on classes. Character events are
// routed to the text input client while key stroke events are propragated
// through the normal event flow. The IME flow is bypassed.
if (key_event.is_char()) {
// Since the IME flow is bypassed, we need to manually add ui components
// we want to receive character events here.
if (views::IsViewClass<views::Textfield>(view_)) {
static_cast<views::Textfield*>(view_)->InsertChar(key_event);
} else {
return false;
}
} else {
view_->OnKeyEvent(&key_event);
}
return true;
}
ui::metadata::ClassMetaData* ViewElement::GetClassMetaData() const {
return view_->GetClassMetaData();
}
void* ViewElement::GetClassInstance() const {
return view_;
}
ui::Layer* ViewElement::GetLayer() const {
return view_->layer();
}
void ViewElement::RebuildTree() {
ClearChildren();
for (views::View* child : view_->children()) {
AddChild(new ViewElement(child, delegate(), this));
}
}
} // namespace ui_devtools