123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 |
- // 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/dom_agent.h"
- #include <algorithm>
- #include <memory>
- #include <string>
- #include <utility>
- #include "base/containers/adapters.h"
- #include "base/observer_list.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/string_util.h"
- #include "components/ui_devtools/devtools_server.h"
- #include "components/ui_devtools/root_element.h"
- #include "components/ui_devtools/ui_devtools_features.h"
- #include "components/ui_devtools/ui_element.h"
- #include "ui/base/interaction/element_identifier.h"
- namespace {
- std::string CreateIdentifier() {
- static int last_used_identifier;
- return base::NumberToString(++last_used_identifier);
- }
- } // namespace
- namespace ui_devtools {
- using ui_devtools::protocol::Array;
- using ui_devtools::protocol::DOM::Node;
- using ui_devtools::protocol::Response;
- namespace {
- bool FindMatchInStylesProperty(const std::string& query,
- ui_devtools::UIElement* node) {
- for (UIElement::ClassProperties& class_properties :
- node->GetCustomPropertiesForMatchedStyle()) {
- for (UIElement::UIProperty& ui_property : class_properties.properties_) {
- protocol::String dataName = ui_property.name_;
- protocol::String dataValue = ui_property.value_;
- dataName = base::ToLowerASCII(dataName);
- dataValue = base::ToLowerASCII(dataValue);
- protocol::String style_property_match = dataName + ": " + dataValue + ";";
- if (style_property_match.find(query) != protocol::String::npos) {
- return true;
- }
- }
- }
- return false;
- }
- bool FindMatchByID(const std::string& query, ui_devtools::UIElement* node) {
- auto identifier = ui::ElementIdentifier::FromName(query.c_str());
- if (!identifier)
- return false;
- return node->FindMatchByElementID(identifier);
- }
- bool FindMatchInDomProperties(const std::string& query,
- const std::string& tag_name_query,
- const std::string& attribute_query,
- bool exact_attribute_match,
- ui_devtools::UIElement* node) {
- protocol::String node_name = node->GetTypeName();
- node_name = base::ToLowerASCII(node_name);
- if (node_name.find(query) != protocol::String::npos ||
- node_name == tag_name_query) {
- return true;
- }
- for (std::string& data : node->GetAttributes()) {
- data = base::ToLowerASCII(data);
- if (data.find(query) != protocol::String::npos) {
- return true;
- }
- if (data.find(attribute_query) != protocol::String::npos &&
- (!exact_attribute_match || data.length() == attribute_query.length())) {
- return true;
- }
- }
- return false;
- }
- } // namespace
- struct DOMAgent::Query {
- enum QueryType { Normal, Style, ID };
- Query(protocol::String query,
- protocol::String tag_name_query,
- protocol::String attribute_query,
- bool exact_attribute_match,
- QueryType query_type)
- : query_(query),
- tag_name_query_(tag_name_query),
- attribute_query_(attribute_query),
- exact_attribute_match_(exact_attribute_match),
- query_type_(query_type) {}
- protocol::String query_;
- protocol::String tag_name_query_;
- protocol::String attribute_query_;
- bool exact_attribute_match_;
- QueryType query_type_;
- };
- DOMAgent::DOMAgent() {}
- DOMAgent::~DOMAgent() {
- Reset();
- }
- Response DOMAgent::disable() {
- Reset();
- return Response::Success();
- }
- Response DOMAgent::getDocument(std::unique_ptr<Node>* out_root) {
- element_root_->ResetNodeId();
- *out_root = BuildInitialTree();
- is_document_created_ = true;
- return Response::Success();
- }
- Response DOMAgent::pushNodesByBackendIdsToFrontend(
- std::unique_ptr<protocol::Array<int>> backend_node_ids,
- std::unique_ptr<protocol::Array<int>>* result) {
- *result = std::move(backend_node_ids);
- return Response::Success();
- }
- void DOMAgent::OnUIElementAdded(UIElement* parent, UIElement* child) {
- // When parent is null, only need to update |node_id_to_ui_element_|.
- if (!parent) {
- node_id_to_ui_element_[child->node_id()] = child;
- return;
- }
- DCHECK(node_id_to_ui_element_.count(parent->node_id()));
- auto* current_parent = parent;
- while (current_parent) {
- if (current_parent->is_updating()) {
- // One of the parents is updating, so no need to update here.
- return;
- }
- current_parent = current_parent->parent();
- }
- child->set_is_updating(true);
- const auto& children = parent->children();
- auto iter = std::find(children.begin(), children.end(), child);
- int prev_node_id =
- (iter == children.begin()) ? 0 : (*std::prev(iter))->node_id();
- frontend()->childNodeInserted(parent->node_id(), prev_node_id,
- BuildTreeForUIElement(child));
- child->set_is_updating(false);
- for (auto& observer : observers_)
- observer.OnElementAdded(child);
- }
- void DOMAgent::OnUIElementReordered(UIElement* parent, UIElement* child) {
- DCHECK(node_id_to_ui_element_.count(parent->node_id()));
- const auto& children = parent->children();
- auto iter = std::find(children.begin(), children.end(), child);
- int prev_node_id =
- (iter == children.begin()) ? 0 : (*std::prev(iter))->node_id();
- RemoveDomNode(child, false);
- frontend()->childNodeInserted(parent->node_id(), prev_node_id,
- BuildDomNodeFromUIElement(child));
- }
- void DOMAgent::OnUIElementRemoved(UIElement* ui_element) {
- if (node_id_to_ui_element_.count(ui_element->node_id())) {
- // Check if |ui_element| exists in DOM before asking the frontend to remove
- // it. It is possible that this node is already removed along with its
- // ancestor. This happens for example when a bubble closes.
- // The DOM tree is first requested to be removed in
- // |WidgetElement::OnWidgetDestroyed()| then in
- // |ViewElement::OnChildViewRemoved()|.
- RemoveDomNode(ui_element, true);
- }
- }
- void DOMAgent::OnUIElementBoundsChanged(UIElement* ui_element) {
- for (auto& observer : observers_)
- observer.OnElementBoundsChanged(ui_element);
- }
- void DOMAgent::AddObserver(DOMAgentObserver* observer) {
- observers_.AddObserver(observer);
- }
- void DOMAgent::RemoveObserver(DOMAgentObserver* observer) {
- observers_.RemoveObserver(observer);
- }
- UIElement* DOMAgent::GetElementFromNodeId(int node_id) const {
- auto it = node_id_to_ui_element_.find(node_id);
- if (it != node_id_to_ui_element_.end())
- return it->second;
- return nullptr;
- }
- int DOMAgent::GetParentIdOfNodeId(int node_id) const {
- DCHECK(node_id_to_ui_element_.count(node_id));
- const UIElement* element = node_id_to_ui_element_.at(node_id);
- if (element->parent() && element->parent() != element_root_.get())
- return element->parent()->node_id();
- return 0;
- }
- std::unique_ptr<Node> DOMAgent::BuildNode(
- const std::string& name,
- std::unique_ptr<std::vector<std::string>> attributes,
- std::unique_ptr<Array<Node>> children,
- int node_ids) {
- constexpr int kDomElementNodeType = 1;
- std::unique_ptr<Node> node = Node::create()
- .setNodeId(node_ids)
- .setBackendNodeId(node_ids)
- .setNodeName(name)
- .setNodeType(kDomElementNodeType)
- .setAttributes(std::move(attributes))
- .build();
- node->setChildNodeCount(static_cast<int>(children->size()));
- node->setChildren(std::move(children));
- return node;
- }
- std::unique_ptr<Node> DOMAgent::BuildDomNodeFromUIElement(UIElement* root) {
- auto children = std::make_unique<protocol::Array<Node>>();
- for (auto* it : root->children())
- children->emplace_back(BuildDomNodeFromUIElement(it));
- return BuildNode(
- root->GetTypeName(),
- std::make_unique<std::vector<std::string>>(root->GetAttributes()),
- std::move(children), root->node_id());
- }
- std::unique_ptr<Node> DOMAgent::BuildInitialTree() {
- auto children = std::make_unique<protocol::Array<Node>>();
- element_root_ = std::make_unique<RootElement>(this);
- element_root_->set_is_updating(true);
- for (auto* child : CreateChildrenForRoot()) {
- children->emplace_back(BuildTreeForUIElement(child));
- element_root_->AddChild(child);
- }
- std::unique_ptr<Node> root_node =
- BuildNode("root", nullptr, std::move(children), element_root_->node_id());
- element_root_->set_is_updating(false);
- return root_node;
- }
- void DOMAgent::OnElementBoundsChanged(UIElement* ui_element) {
- for (auto& observer : observers_)
- observer.OnElementBoundsChanged(ui_element);
- }
- void DOMAgent::RemoveDomNode(UIElement* ui_element, bool update_node_id_map) {
- for (auto* child_element : ui_element->children())
- RemoveDomNode(child_element, update_node_id_map);
- frontend()->childNodeRemoved(ui_element->parent()->node_id(),
- ui_element->node_id());
- if (update_node_id_map) {
- node_id_to_ui_element_.erase(ui_element->node_id());
- }
- }
- void DOMAgent::Reset() {
- element_root_.reset();
- node_id_to_ui_element_.clear();
- observers_.Clear();
- is_document_created_ = false;
- search_results_.clear();
- }
- DOMAgent::Query DOMAgent::PreprocessQuery(protocol::String query) {
- constexpr char kSearchIDKeyword[] = "id:";
- bool exact_attribute_match = false;
- if (query.find(kSearchIDKeyword) == 0) {
- // remove whitespaces (if they exist) after the 'id:' keyword
- base::TrimWhitespaceASCII(query.substr(strlen(kSearchIDKeyword)),
- base::TrimPositions::TRIM_ALL, &query);
- return Query(query, query, query, exact_attribute_match,
- Query::QueryType::ID);
- }
- // If it's not query by id, transform query to lower case.
- query = base::ToLowerASCII(query);
- constexpr char kSearchStylesPanelKeyword[] = "style:";
- // Temporary Solution: search on styles panel if the keyword 'style:'
- // exists in the beginning of the query.
- if (query.find(kSearchStylesPanelKeyword) == 0) {
- // remove whitespaces (if they exist) after the 'style:' keyword
- base::TrimWhitespaceASCII(query.substr(strlen(kSearchStylesPanelKeyword)),
- base::TrimPositions::TRIM_ALL, &query);
- return Query(query, query, query, exact_attribute_match,
- Query::QueryType::Style);
- }
- // Preprocessing for normal dom search.
- protocol::String tag_name_query = query;
- protocol::String attribute_query = query;
- unsigned query_length = query.length();
- bool start_tag_found = !query.find('<');
- bool end_tag_found = query.rfind('>') + 1 == query_length;
- bool start_quote_found = !query.find('"');
- bool end_quote_found = query.rfind('"') + 1 == query_length;
- exact_attribute_match = start_quote_found && end_quote_found;
- if (start_tag_found)
- tag_name_query = tag_name_query.substr(1, tag_name_query.length() - 1);
- if (end_tag_found)
- tag_name_query = tag_name_query.substr(0, tag_name_query.length() - 1);
- if (start_quote_found)
- attribute_query = attribute_query.substr(1, attribute_query.length() - 1);
- if (end_quote_found)
- attribute_query = attribute_query.substr(0, attribute_query.length() - 1);
- return Query(query, tag_name_query, attribute_query, exact_attribute_match,
- Query::QueryType::Normal);
- }
- void DOMAgent::SearchDomTree(const DOMAgent::Query& query_data,
- std::vector<int>* result_collector) {
- std::vector<UIElement*> stack;
- // Root node from element_root() is not a real node from the DOM tree.
- // The children of the root node are the 'actual' roots of the DOM tree.
- std::vector<UIElement*> root_list = element_root()->children();
- DCHECK(root_list.size());
- // Children are accessed from bottom to top. So iterate backwards.
- for (auto* root : base::Reversed(root_list))
- stack.push_back(root);
- // Manual plain text search. DFS traversal.
- while (!stack.empty()) {
- UIElement* node = stack.back();
- stack.pop_back();
- std::vector<UIElement*> children_array = node->children();
- // Children are accessed from bottom to top. So iterate backwards.
- for (auto* child : base::Reversed(children_array))
- stack.push_back(child);
- bool found_match = false;
- if (query_data.query_type_ == Query::QueryType::Style)
- found_match = FindMatchInStylesProperty(query_data.query_, node);
- else if (query_data.query_type_ == Query::QueryType::ID)
- found_match = FindMatchByID(query_data.query_, node);
- else
- found_match = FindMatchInDomProperties(
- query_data.query_, query_data.tag_name_query_,
- query_data.attribute_query_, query_data.exact_attribute_match_, node);
- if (found_match)
- result_collector->push_back(node->node_id());
- }
- }
- // Code is based on InspectorDOMAgent::performSearch() from
- // src/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
- Response DOMAgent::performSearch(
- const protocol::String& whitespace_trimmed_query,
- protocol::Maybe<bool> optional_include_user_agent_shadow_dom,
- protocol::String* search_id,
- int* result_count) {
- Query query_data = PreprocessQuery(whitespace_trimmed_query);
- if (!query_data.query_.length())
- return Response::Success();
- std::vector<int> result_collector;
- SearchDomTree(query_data, &result_collector);
- *search_id = CreateIdentifier();
- *result_count = result_collector.size();
- search_results_.insert(
- std::make_pair(*search_id, std::move(result_collector)));
- return Response::Success();
- }
- Response DOMAgent::getSearchResults(
- const protocol::String& search_id,
- int from_index,
- int to_index,
- std::unique_ptr<protocol::Array<int>>* node_ids) {
- SearchResults::iterator it = search_results_.find(search_id);
- if (it == search_results_.end())
- return Response::ServerError("No search session with given id found");
- int size = it->second.size();
- if (from_index < 0 || to_index > size || from_index >= to_index)
- return Response::ServerError("Invalid search result range");
- *node_ids = std::make_unique<protocol::Array<int>>();
- for (int i = from_index; i < to_index; ++i)
- (*node_ids)->emplace_back((it->second)[i]);
- return Response::Success();
- }
- Response DOMAgent::discardSearchResults(const protocol::String& search_id) {
- search_results_.erase(search_id);
- return Response::Success();
- }
- protocol::Response DOMAgent::dispatchMouseEvent(
- int node_id,
- std::unique_ptr<protocol::DOM::MouseEvent> event) {
- if (!base::FeatureList::IsEnabled(
- ui_devtools::kUIDebugToolsEnableSyntheticEvents))
- return Response::ServerError("Dispatch mouse events is not enabled.");
- if (node_id_to_ui_element_.count(node_id) == 0)
- return Response::ServerError("Element not found on node id");
- if (!node_id_to_ui_element_[node_id]->DispatchMouseEvent(event.get()))
- return Response::ServerError("Failed to dispatch mouse event for node id");
- return Response::Success();
- }
- protocol::Response DOMAgent::dispatchKeyEvent(
- int node_id,
- std::unique_ptr<protocol::DOM::KeyEvent> event) {
- if (!base::FeatureList::IsEnabled(
- ui_devtools::kUIDebugToolsEnableSyntheticEvents))
- return Response::ServerError("Dispatch key events is not enabled.");
- if (node_id_to_ui_element_.count(node_id) == 0)
- return Response::ServerError("Element not found on node id");
- if (!node_id_to_ui_element_[node_id]->DispatchKeyEvent(event.get()))
- return Response::ServerError("Failed to dispatch key event for node id");
- return Response::Success();
- }
- } // namespace ui_devtools
|