dom_agent.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/ui_devtools/dom_agent.h"
  5. #include <algorithm>
  6. #include <memory>
  7. #include <string>
  8. #include <utility>
  9. #include "base/containers/adapters.h"
  10. #include "base/observer_list.h"
  11. #include "base/strings/string_number_conversions.h"
  12. #include "base/strings/string_util.h"
  13. #include "components/ui_devtools/devtools_server.h"
  14. #include "components/ui_devtools/root_element.h"
  15. #include "components/ui_devtools/ui_devtools_features.h"
  16. #include "components/ui_devtools/ui_element.h"
  17. #include "ui/base/interaction/element_identifier.h"
  18. namespace {
  19. std::string CreateIdentifier() {
  20. static int last_used_identifier;
  21. return base::NumberToString(++last_used_identifier);
  22. }
  23. } // namespace
  24. namespace ui_devtools {
  25. using ui_devtools::protocol::Array;
  26. using ui_devtools::protocol::DOM::Node;
  27. using ui_devtools::protocol::Response;
  28. namespace {
  29. bool FindMatchInStylesProperty(const std::string& query,
  30. ui_devtools::UIElement* node) {
  31. for (UIElement::ClassProperties& class_properties :
  32. node->GetCustomPropertiesForMatchedStyle()) {
  33. for (UIElement::UIProperty& ui_property : class_properties.properties_) {
  34. protocol::String dataName = ui_property.name_;
  35. protocol::String dataValue = ui_property.value_;
  36. dataName = base::ToLowerASCII(dataName);
  37. dataValue = base::ToLowerASCII(dataValue);
  38. protocol::String style_property_match = dataName + ": " + dataValue + ";";
  39. if (style_property_match.find(query) != protocol::String::npos) {
  40. return true;
  41. }
  42. }
  43. }
  44. return false;
  45. }
  46. bool FindMatchByID(const std::string& query, ui_devtools::UIElement* node) {
  47. auto identifier = ui::ElementIdentifier::FromName(query.c_str());
  48. if (!identifier)
  49. return false;
  50. return node->FindMatchByElementID(identifier);
  51. }
  52. bool FindMatchInDomProperties(const std::string& query,
  53. const std::string& tag_name_query,
  54. const std::string& attribute_query,
  55. bool exact_attribute_match,
  56. ui_devtools::UIElement* node) {
  57. protocol::String node_name = node->GetTypeName();
  58. node_name = base::ToLowerASCII(node_name);
  59. if (node_name.find(query) != protocol::String::npos ||
  60. node_name == tag_name_query) {
  61. return true;
  62. }
  63. for (std::string& data : node->GetAttributes()) {
  64. data = base::ToLowerASCII(data);
  65. if (data.find(query) != protocol::String::npos) {
  66. return true;
  67. }
  68. if (data.find(attribute_query) != protocol::String::npos &&
  69. (!exact_attribute_match || data.length() == attribute_query.length())) {
  70. return true;
  71. }
  72. }
  73. return false;
  74. }
  75. } // namespace
  76. struct DOMAgent::Query {
  77. enum QueryType { Normal, Style, ID };
  78. Query(protocol::String query,
  79. protocol::String tag_name_query,
  80. protocol::String attribute_query,
  81. bool exact_attribute_match,
  82. QueryType query_type)
  83. : query_(query),
  84. tag_name_query_(tag_name_query),
  85. attribute_query_(attribute_query),
  86. exact_attribute_match_(exact_attribute_match),
  87. query_type_(query_type) {}
  88. protocol::String query_;
  89. protocol::String tag_name_query_;
  90. protocol::String attribute_query_;
  91. bool exact_attribute_match_;
  92. QueryType query_type_;
  93. };
  94. DOMAgent::DOMAgent() {}
  95. DOMAgent::~DOMAgent() {
  96. Reset();
  97. }
  98. Response DOMAgent::disable() {
  99. Reset();
  100. return Response::Success();
  101. }
  102. Response DOMAgent::getDocument(std::unique_ptr<Node>* out_root) {
  103. element_root_->ResetNodeId();
  104. *out_root = BuildInitialTree();
  105. is_document_created_ = true;
  106. return Response::Success();
  107. }
  108. Response DOMAgent::pushNodesByBackendIdsToFrontend(
  109. std::unique_ptr<protocol::Array<int>> backend_node_ids,
  110. std::unique_ptr<protocol::Array<int>>* result) {
  111. *result = std::move(backend_node_ids);
  112. return Response::Success();
  113. }
  114. void DOMAgent::OnUIElementAdded(UIElement* parent, UIElement* child) {
  115. // When parent is null, only need to update |node_id_to_ui_element_|.
  116. if (!parent) {
  117. node_id_to_ui_element_[child->node_id()] = child;
  118. return;
  119. }
  120. DCHECK(node_id_to_ui_element_.count(parent->node_id()));
  121. auto* current_parent = parent;
  122. while (current_parent) {
  123. if (current_parent->is_updating()) {
  124. // One of the parents is updating, so no need to update here.
  125. return;
  126. }
  127. current_parent = current_parent->parent();
  128. }
  129. child->set_is_updating(true);
  130. const auto& children = parent->children();
  131. auto iter = std::find(children.begin(), children.end(), child);
  132. int prev_node_id =
  133. (iter == children.begin()) ? 0 : (*std::prev(iter))->node_id();
  134. frontend()->childNodeInserted(parent->node_id(), prev_node_id,
  135. BuildTreeForUIElement(child));
  136. child->set_is_updating(false);
  137. for (auto& observer : observers_)
  138. observer.OnElementAdded(child);
  139. }
  140. void DOMAgent::OnUIElementReordered(UIElement* parent, UIElement* child) {
  141. DCHECK(node_id_to_ui_element_.count(parent->node_id()));
  142. const auto& children = parent->children();
  143. auto iter = std::find(children.begin(), children.end(), child);
  144. int prev_node_id =
  145. (iter == children.begin()) ? 0 : (*std::prev(iter))->node_id();
  146. RemoveDomNode(child, false);
  147. frontend()->childNodeInserted(parent->node_id(), prev_node_id,
  148. BuildDomNodeFromUIElement(child));
  149. }
  150. void DOMAgent::OnUIElementRemoved(UIElement* ui_element) {
  151. if (node_id_to_ui_element_.count(ui_element->node_id())) {
  152. // Check if |ui_element| exists in DOM before asking the frontend to remove
  153. // it. It is possible that this node is already removed along with its
  154. // ancestor. This happens for example when a bubble closes.
  155. // The DOM tree is first requested to be removed in
  156. // |WidgetElement::OnWidgetDestroyed()| then in
  157. // |ViewElement::OnChildViewRemoved()|.
  158. RemoveDomNode(ui_element, true);
  159. }
  160. }
  161. void DOMAgent::OnUIElementBoundsChanged(UIElement* ui_element) {
  162. for (auto& observer : observers_)
  163. observer.OnElementBoundsChanged(ui_element);
  164. }
  165. void DOMAgent::AddObserver(DOMAgentObserver* observer) {
  166. observers_.AddObserver(observer);
  167. }
  168. void DOMAgent::RemoveObserver(DOMAgentObserver* observer) {
  169. observers_.RemoveObserver(observer);
  170. }
  171. UIElement* DOMAgent::GetElementFromNodeId(int node_id) const {
  172. auto it = node_id_to_ui_element_.find(node_id);
  173. if (it != node_id_to_ui_element_.end())
  174. return it->second;
  175. return nullptr;
  176. }
  177. int DOMAgent::GetParentIdOfNodeId(int node_id) const {
  178. DCHECK(node_id_to_ui_element_.count(node_id));
  179. const UIElement* element = node_id_to_ui_element_.at(node_id);
  180. if (element->parent() && element->parent() != element_root_.get())
  181. return element->parent()->node_id();
  182. return 0;
  183. }
  184. std::unique_ptr<Node> DOMAgent::BuildNode(
  185. const std::string& name,
  186. std::unique_ptr<std::vector<std::string>> attributes,
  187. std::unique_ptr<Array<Node>> children,
  188. int node_ids) {
  189. constexpr int kDomElementNodeType = 1;
  190. std::unique_ptr<Node> node = Node::create()
  191. .setNodeId(node_ids)
  192. .setBackendNodeId(node_ids)
  193. .setNodeName(name)
  194. .setNodeType(kDomElementNodeType)
  195. .setAttributes(std::move(attributes))
  196. .build();
  197. node->setChildNodeCount(static_cast<int>(children->size()));
  198. node->setChildren(std::move(children));
  199. return node;
  200. }
  201. std::unique_ptr<Node> DOMAgent::BuildDomNodeFromUIElement(UIElement* root) {
  202. auto children = std::make_unique<protocol::Array<Node>>();
  203. for (auto* it : root->children())
  204. children->emplace_back(BuildDomNodeFromUIElement(it));
  205. return BuildNode(
  206. root->GetTypeName(),
  207. std::make_unique<std::vector<std::string>>(root->GetAttributes()),
  208. std::move(children), root->node_id());
  209. }
  210. std::unique_ptr<Node> DOMAgent::BuildInitialTree() {
  211. auto children = std::make_unique<protocol::Array<Node>>();
  212. element_root_ = std::make_unique<RootElement>(this);
  213. element_root_->set_is_updating(true);
  214. for (auto* child : CreateChildrenForRoot()) {
  215. children->emplace_back(BuildTreeForUIElement(child));
  216. element_root_->AddChild(child);
  217. }
  218. std::unique_ptr<Node> root_node =
  219. BuildNode("root", nullptr, std::move(children), element_root_->node_id());
  220. element_root_->set_is_updating(false);
  221. return root_node;
  222. }
  223. void DOMAgent::OnElementBoundsChanged(UIElement* ui_element) {
  224. for (auto& observer : observers_)
  225. observer.OnElementBoundsChanged(ui_element);
  226. }
  227. void DOMAgent::RemoveDomNode(UIElement* ui_element, bool update_node_id_map) {
  228. for (auto* child_element : ui_element->children())
  229. RemoveDomNode(child_element, update_node_id_map);
  230. frontend()->childNodeRemoved(ui_element->parent()->node_id(),
  231. ui_element->node_id());
  232. if (update_node_id_map) {
  233. node_id_to_ui_element_.erase(ui_element->node_id());
  234. }
  235. }
  236. void DOMAgent::Reset() {
  237. element_root_.reset();
  238. node_id_to_ui_element_.clear();
  239. observers_.Clear();
  240. is_document_created_ = false;
  241. search_results_.clear();
  242. }
  243. DOMAgent::Query DOMAgent::PreprocessQuery(protocol::String query) {
  244. constexpr char kSearchIDKeyword[] = "id:";
  245. bool exact_attribute_match = false;
  246. if (query.find(kSearchIDKeyword) == 0) {
  247. // remove whitespaces (if they exist) after the 'id:' keyword
  248. base::TrimWhitespaceASCII(query.substr(strlen(kSearchIDKeyword)),
  249. base::TrimPositions::TRIM_ALL, &query);
  250. return Query(query, query, query, exact_attribute_match,
  251. Query::QueryType::ID);
  252. }
  253. // If it's not query by id, transform query to lower case.
  254. query = base::ToLowerASCII(query);
  255. constexpr char kSearchStylesPanelKeyword[] = "style:";
  256. // Temporary Solution: search on styles panel if the keyword 'style:'
  257. // exists in the beginning of the query.
  258. if (query.find(kSearchStylesPanelKeyword) == 0) {
  259. // remove whitespaces (if they exist) after the 'style:' keyword
  260. base::TrimWhitespaceASCII(query.substr(strlen(kSearchStylesPanelKeyword)),
  261. base::TrimPositions::TRIM_ALL, &query);
  262. return Query(query, query, query, exact_attribute_match,
  263. Query::QueryType::Style);
  264. }
  265. // Preprocessing for normal dom search.
  266. protocol::String tag_name_query = query;
  267. protocol::String attribute_query = query;
  268. unsigned query_length = query.length();
  269. bool start_tag_found = !query.find('<');
  270. bool end_tag_found = query.rfind('>') + 1 == query_length;
  271. bool start_quote_found = !query.find('"');
  272. bool end_quote_found = query.rfind('"') + 1 == query_length;
  273. exact_attribute_match = start_quote_found && end_quote_found;
  274. if (start_tag_found)
  275. tag_name_query = tag_name_query.substr(1, tag_name_query.length() - 1);
  276. if (end_tag_found)
  277. tag_name_query = tag_name_query.substr(0, tag_name_query.length() - 1);
  278. if (start_quote_found)
  279. attribute_query = attribute_query.substr(1, attribute_query.length() - 1);
  280. if (end_quote_found)
  281. attribute_query = attribute_query.substr(0, attribute_query.length() - 1);
  282. return Query(query, tag_name_query, attribute_query, exact_attribute_match,
  283. Query::QueryType::Normal);
  284. }
  285. void DOMAgent::SearchDomTree(const DOMAgent::Query& query_data,
  286. std::vector<int>* result_collector) {
  287. std::vector<UIElement*> stack;
  288. // Root node from element_root() is not a real node from the DOM tree.
  289. // The children of the root node are the 'actual' roots of the DOM tree.
  290. std::vector<UIElement*> root_list = element_root()->children();
  291. DCHECK(root_list.size());
  292. // Children are accessed from bottom to top. So iterate backwards.
  293. for (auto* root : base::Reversed(root_list))
  294. stack.push_back(root);
  295. // Manual plain text search. DFS traversal.
  296. while (!stack.empty()) {
  297. UIElement* node = stack.back();
  298. stack.pop_back();
  299. std::vector<UIElement*> children_array = node->children();
  300. // Children are accessed from bottom to top. So iterate backwards.
  301. for (auto* child : base::Reversed(children_array))
  302. stack.push_back(child);
  303. bool found_match = false;
  304. if (query_data.query_type_ == Query::QueryType::Style)
  305. found_match = FindMatchInStylesProperty(query_data.query_, node);
  306. else if (query_data.query_type_ == Query::QueryType::ID)
  307. found_match = FindMatchByID(query_data.query_, node);
  308. else
  309. found_match = FindMatchInDomProperties(
  310. query_data.query_, query_data.tag_name_query_,
  311. query_data.attribute_query_, query_data.exact_attribute_match_, node);
  312. if (found_match)
  313. result_collector->push_back(node->node_id());
  314. }
  315. }
  316. // Code is based on InspectorDOMAgent::performSearch() from
  317. // src/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
  318. Response DOMAgent::performSearch(
  319. const protocol::String& whitespace_trimmed_query,
  320. protocol::Maybe<bool> optional_include_user_agent_shadow_dom,
  321. protocol::String* search_id,
  322. int* result_count) {
  323. Query query_data = PreprocessQuery(whitespace_trimmed_query);
  324. if (!query_data.query_.length())
  325. return Response::Success();
  326. std::vector<int> result_collector;
  327. SearchDomTree(query_data, &result_collector);
  328. *search_id = CreateIdentifier();
  329. *result_count = result_collector.size();
  330. search_results_.insert(
  331. std::make_pair(*search_id, std::move(result_collector)));
  332. return Response::Success();
  333. }
  334. Response DOMAgent::getSearchResults(
  335. const protocol::String& search_id,
  336. int from_index,
  337. int to_index,
  338. std::unique_ptr<protocol::Array<int>>* node_ids) {
  339. SearchResults::iterator it = search_results_.find(search_id);
  340. if (it == search_results_.end())
  341. return Response::ServerError("No search session with given id found");
  342. int size = it->second.size();
  343. if (from_index < 0 || to_index > size || from_index >= to_index)
  344. return Response::ServerError("Invalid search result range");
  345. *node_ids = std::make_unique<protocol::Array<int>>();
  346. for (int i = from_index; i < to_index; ++i)
  347. (*node_ids)->emplace_back((it->second)[i]);
  348. return Response::Success();
  349. }
  350. Response DOMAgent::discardSearchResults(const protocol::String& search_id) {
  351. search_results_.erase(search_id);
  352. return Response::Success();
  353. }
  354. protocol::Response DOMAgent::dispatchMouseEvent(
  355. int node_id,
  356. std::unique_ptr<protocol::DOM::MouseEvent> event) {
  357. if (!base::FeatureList::IsEnabled(
  358. ui_devtools::kUIDebugToolsEnableSyntheticEvents))
  359. return Response::ServerError("Dispatch mouse events is not enabled.");
  360. if (node_id_to_ui_element_.count(node_id) == 0)
  361. return Response::ServerError("Element not found on node id");
  362. if (!node_id_to_ui_element_[node_id]->DispatchMouseEvent(event.get()))
  363. return Response::ServerError("Failed to dispatch mouse event for node id");
  364. return Response::Success();
  365. }
  366. protocol::Response DOMAgent::dispatchKeyEvent(
  367. int node_id,
  368. std::unique_ptr<protocol::DOM::KeyEvent> event) {
  369. if (!base::FeatureList::IsEnabled(
  370. ui_devtools::kUIDebugToolsEnableSyntheticEvents))
  371. return Response::ServerError("Dispatch key events is not enabled.");
  372. if (node_id_to_ui_element_.count(node_id) == 0)
  373. return Response::ServerError("Element not found on node id");
  374. if (!node_id_to_ui_element_[node_id]->DispatchKeyEvent(event.get()))
  375. return Response::ServerError("Failed to dispatch key event for node id");
  376. return Response::Success();
  377. }
  378. } // namespace ui_devtools