overlay_agent_unittest.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  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/views/overlay_agent_views.h"
  5. #include "base/strings/stringprintf.h"
  6. #include "components/ui_devtools/ui_devtools_unittest_utils.h"
  7. #include "components/ui_devtools/ui_element.h"
  8. #include "components/ui_devtools/views/dom_agent_views.h"
  9. #include "components/ui_devtools/views/view_element.h"
  10. #include "components/ui_devtools/views/widget_element.h"
  11. #include "ui/events/base_event_utils.h"
  12. #include "ui/events/event_constants.h"
  13. #include "ui/events/test/event_generator.h"
  14. #include "ui/events/types/event_type.h"
  15. #include "ui/gfx/geometry/rect.h"
  16. #include "ui/views/test/views_test_base.h"
  17. #include "ui/views/widget/widget_utils.h"
  18. #include "ui/views/window/non_client_view.h"
  19. #if defined(USE_AURA)
  20. #include "components/ui_devtools/views/window_element.h"
  21. #include "ui/aura/env.h"
  22. #include "ui/aura/test/test_window_delegate.h"
  23. #include "ui/aura/window.h"
  24. #endif
  25. namespace ui_devtools {
  26. namespace {
  27. gfx::Point GetOriginInScreen(views::View* view) {
  28. gfx::Point point(0, 0); // Since it's local bounds, origin is always 0,0.
  29. views::View::ConvertPointToScreen(view, &point);
  30. return point;
  31. }
  32. } // namespace
  33. class OverlayAgentTest : public views::ViewsTestBase {
  34. public:
  35. void SetUp() override {
  36. fake_frontend_channel_ = std::make_unique<FakeFrontendChannel>();
  37. uber_dispatcher_ = std::make_unique<protocol::UberDispatcher>(
  38. fake_frontend_channel_.get());
  39. dom_agent_ = DOMAgentViews::Create();
  40. dom_agent_->Init(uber_dispatcher_.get());
  41. overlay_agent_ = OverlayAgentViews::Create(dom_agent_.get());
  42. overlay_agent_->Init(uber_dispatcher_.get());
  43. overlay_agent_->enable();
  44. views::ViewsTestBase::SetUp();
  45. }
  46. void TearDown() override {
  47. // Ensure DOMAgent shuts down before the root window closes to avoid
  48. // lifetime issues.
  49. overlay_agent_->disable();
  50. overlay_agent_.reset();
  51. dom_agent_->disable();
  52. dom_agent_.reset();
  53. uber_dispatcher_.reset();
  54. fake_frontend_channel_.reset();
  55. widget_.reset();
  56. views::ViewsTestBase::TearDown();
  57. }
  58. protected:
  59. std::unique_ptr<ui::MouseEvent> MouseEventAtRootLocation(gfx::Point p) {
  60. #if defined(USE_AURA)
  61. ui::EventTarget* target = GetContext();
  62. #else
  63. ui::EventTarget* target = widget()->GetRootView();
  64. #endif
  65. auto event = std::make_unique<ui::MouseEvent>(ui::ET_MOUSE_MOVED, p, p,
  66. ui::EventTimeForNow(),
  67. ui::EF_NONE, ui::EF_NONE);
  68. ui::Event::DispatcherApi(event.get()).set_target(target);
  69. return event;
  70. }
  71. views::View* GetViewAtPoint(int x, int y) {
  72. gfx::Point point(x, y);
  73. int element_id = overlay_agent()->FindElementIdTargetedByPoint(
  74. MouseEventAtRootLocation(point).get());
  75. UIElement* element = dom_agent()->GetElementFromNodeId(element_id);
  76. DCHECK_EQ(element->type(), UIElementType::VIEW);
  77. return UIElement::GetBackingElement<views::View, ViewElement>(element);
  78. }
  79. int GetOverlayNodeHighlightRequestedCount(int node_id) {
  80. return frontend_channel()->CountProtocolNotificationMessage(
  81. base::StringPrintf(
  82. "{\"method\":\"Overlay.nodeHighlightRequested\",\"params\":{"
  83. "\"nodeId\":%d}}",
  84. node_id));
  85. }
  86. int GetOverlayInspectNodeRequestedCount(int node_id) {
  87. return frontend_channel()->CountProtocolNotificationMessage(
  88. base::StringPrintf(
  89. "{\"method\":\"Overlay.inspectNodeRequested\",\"params\":{"
  90. "\"backendNodeId\":%d}}",
  91. node_id));
  92. }
  93. #if defined(USE_AURA)
  94. std::unique_ptr<aura::Window> CreateWindowElement(const gfx::Rect& bounds) {
  95. std::unique_ptr<aura::Window> window = std::make_unique<aura::Window>(
  96. nullptr, aura::client::WINDOW_TYPE_NORMAL);
  97. window->Init(ui::LAYER_NOT_DRAWN);
  98. window->SetBounds(bounds);
  99. GetContext()->AddChild(window.get());
  100. window->Show();
  101. return window;
  102. }
  103. #endif
  104. void CreateWidget(const gfx::Rect& bounds,
  105. views::Widget::InitParams::Type type) {
  106. widget_ = std::make_unique<views::Widget>();
  107. views::Widget::InitParams params;
  108. params.delegate = nullptr;
  109. params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
  110. params.bounds = bounds;
  111. params.type = type;
  112. #if defined(USE_AURA)
  113. params.parent = GetContext();
  114. #endif
  115. widget_->Init(std::move(params));
  116. widget_->Show();
  117. }
  118. void CreateWidget() {
  119. // Create a widget with default bounds.
  120. return CreateWidget(gfx::Rect(0, 0, 400, 400),
  121. views::Widget::InitParams::Type::TYPE_WINDOW);
  122. }
  123. views::Widget* widget() { return widget_.get(); }
  124. DOMAgentViews* dom_agent() { return dom_agent_.get(); }
  125. OverlayAgentViews* overlay_agent() { return overlay_agent_.get(); }
  126. FakeFrontendChannel* frontend_channel() {
  127. return fake_frontend_channel_.get();
  128. }
  129. std::unique_ptr<protocol::UberDispatcher> uber_dispatcher_;
  130. std::unique_ptr<FakeFrontendChannel> fake_frontend_channel_;
  131. std::unique_ptr<DOMAgentViews> dom_agent_;
  132. std::unique_ptr<OverlayAgentViews> overlay_agent_;
  133. std::unique_ptr<views::Widget> widget_;
  134. };
  135. #if defined(USE_AURA)
  136. TEST_F(OverlayAgentTest, FindElementIdTargetedByPointWindow) {
  137. // Windows without delegates won't act as an event handler.
  138. aura::test::TestWindowDelegate delegate;
  139. std::unique_ptr<aura::Window> window = std::make_unique<aura::Window>(
  140. &delegate, aura::client::WINDOW_TYPE_NORMAL);
  141. window->Init(ui::LAYER_NOT_DRAWN);
  142. window->SetBounds(GetContext()->bounds());
  143. GetContext()->AddChild(window.get());
  144. window->Show();
  145. std::unique_ptr<protocol::DOM::Node> root;
  146. dom_agent()->getDocument(&root);
  147. int element_id = overlay_agent()->FindElementIdTargetedByPoint(
  148. MouseEventAtRootLocation(gfx::Point(1, 1)).get());
  149. UIElement* element = dom_agent()->GetElementFromNodeId(element_id);
  150. DCHECK_EQ(element->type(), UIElementType::WINDOW);
  151. aura::Window* element_window =
  152. UIElement::GetBackingElement<aura::Window, WindowElement>(element);
  153. EXPECT_EQ(element_window, window.get());
  154. gfx::Point out_of_bounds =
  155. window->bounds().bottom_right() + gfx::Vector2d(20, 20);
  156. EXPECT_EQ(0, overlay_agent()->FindElementIdTargetedByPoint(
  157. MouseEventAtRootLocation(out_of_bounds).get()));
  158. }
  159. #endif
  160. TEST_F(OverlayAgentTest, FindElementIdTargetedByPointViews) {
  161. // Use a frameless window instead of deleting all children of |contents_view|
  162. CreateWidget(gfx::Rect(0, 0, 400, 400),
  163. views::Widget::InitParams::Type::TYPE_WINDOW_FRAMELESS);
  164. std::unique_ptr<protocol::DOM::Node> root;
  165. dom_agent()->getDocument(&root);
  166. views::View* contents_view = widget()->GetRootView();
  167. views::View* child_1 = new views::View;
  168. views::View* child_2 = new views::View;
  169. // Not to scale!
  170. // ------------------------
  171. // | contents_view |
  172. // | ---------- |
  173. // | |child_1 |------- |
  174. // | | | | |
  175. // | ---------- | |
  176. // | |child_2| |
  177. // | --------- |
  178. // | |
  179. // ------------------------
  180. contents_view->AddChildView(child_2);
  181. contents_view->AddChildView(child_1);
  182. child_1->SetBounds(20, 20, 100, 100);
  183. child_2->SetBounds(90, 50, 100, 100);
  184. EXPECT_EQ(GetViewAtPoint(1, 1), widget()->GetRootView());
  185. EXPECT_EQ(GetViewAtPoint(21, 21), child_1);
  186. EXPECT_EQ(GetViewAtPoint(170, 130), child_2);
  187. // At the overlap.
  188. EXPECT_EQ(GetViewAtPoint(110, 110), child_1);
  189. }
  190. TEST_F(OverlayAgentTest, HighlightRects) {
  191. const struct {
  192. std::string name;
  193. gfx::Rect first_element_bounds;
  194. gfx::Rect second_element_bounds;
  195. HighlightRectsConfiguration expected_configuration;
  196. } kTestCases[] = {
  197. {"R1_CONTAINS_R2", gfx::Rect(1, 1, 100, 100), gfx::Rect(2, 2, 50, 50),
  198. R1_CONTAINS_R2},
  199. {"R1_HORIZONTAL_FULL_LEFT_R2", gfx::Rect(1, 1, 50, 50),
  200. gfx::Rect(60, 1, 60, 60), R1_HORIZONTAL_FULL_LEFT_R2},
  201. {"R1_TOP_FULL_LEFT_R2", gfx::Rect(30, 30, 50, 50),
  202. gfx::Rect(100, 100, 50, 50), R1_TOP_FULL_LEFT_R2},
  203. {"R1_BOTTOM_FULL_LEFT_R2", gfx::Rect(100, 100, 50, 50),
  204. gfx::Rect(200, 50, 40, 40), R1_BOTTOM_FULL_LEFT_R2},
  205. {"R1_TOP_PARTIAL_LEFT_R2", gfx::Rect(100, 100, 50, 50),
  206. gfx::Rect(120, 200, 50, 50), R1_TOP_PARTIAL_LEFT_R2},
  207. {"R1_BOTTOM_PARTIAL_LEFT_R2", gfx::Rect(50, 200, 100, 100),
  208. gfx::Rect(100, 50, 50, 50), R1_BOTTOM_PARTIAL_LEFT_R2},
  209. {"R1_INTERSECTS_R2", gfx::Rect(100, 100, 50, 50),
  210. gfx::Rect(120, 120, 50, 50), R1_INTERSECTS_R2},
  211. };
  212. // Use a non-zero origin to test screen coordinates.
  213. const gfx::Rect kWidgetBounds(10, 10, 510, 510);
  214. for (const auto& test_case : kTestCases) {
  215. SCOPED_TRACE(testing::Message() << "Case: " << test_case.name);
  216. CreateWidget(kWidgetBounds, views::Widget::InitParams::Type::TYPE_WINDOW);
  217. // Can't just use kWidgetBounds because of Mac's menu bar.
  218. gfx::Vector2d widget_screen_offset =
  219. widget()->GetClientAreaBoundsInScreen().OffsetFromOrigin();
  220. std::unique_ptr<protocol::DOM::Node> root;
  221. dom_agent()->getDocument(&root);
  222. // Fish out the client view to serve as superview. Emptying out the content
  223. // view and adding the subviews directly causes NonClientView's hit test to
  224. // fail.
  225. views::View* contents_view = widget()->GetContentsView();
  226. DCHECK_EQ(contents_view->GetClassName(),
  227. views::NonClientView::kViewClassName);
  228. views::NonClientView* non_client_view =
  229. static_cast<views::NonClientView*>(contents_view);
  230. views::View* client_view = non_client_view->client_view();
  231. views::View* child_1 = new views::View;
  232. views::View* child_2 = new views::View;
  233. client_view->AddChildView(child_1);
  234. client_view->AddChildView(child_2);
  235. child_1->SetBoundsRect(test_case.first_element_bounds);
  236. child_2->SetBoundsRect(test_case.second_element_bounds);
  237. overlay_agent()->setInspectMode(
  238. "searchForNode", protocol::Maybe<protocol::Overlay::HighlightConfig>());
  239. ui::test::EventGenerator generator(GetRootWindow(widget()));
  240. // Highlight child 1.
  241. generator.MoveMouseTo(GetOriginInScreen(child_1));
  242. // Click to pin it.
  243. generator.ClickLeftButton();
  244. // Highlight child 2. Now, the distance overlay is showing.
  245. generator.MoveMouseTo(GetOriginInScreen(child_2));
  246. // Check calculated highlight config.
  247. EXPECT_EQ(test_case.expected_configuration,
  248. overlay_agent()->highlight_rect_config());
  249. // Check results of pinned and hovered rectangles.
  250. gfx::Rect expected_pinned_rect =
  251. client_view->ConvertRectToParent(test_case.first_element_bounds);
  252. expected_pinned_rect.Offset(widget_screen_offset);
  253. EXPECT_EQ(expected_pinned_rect, overlay_agent()->pinned_rect_);
  254. gfx::Rect expected_hovered_rect =
  255. client_view->ConvertRectToParent(test_case.second_element_bounds);
  256. expected_hovered_rect.Offset(widget_screen_offset);
  257. EXPECT_EQ(expected_hovered_rect, overlay_agent()->hovered_rect_);
  258. // If we don't explicitly stop inspecting, we'll leave ourselves as
  259. // a pretarget handler for the root window and UAF in the next test.
  260. // TODO(lgrey): Fix this when refactoring to support Mac.
  261. overlay_agent()->setInspectMode(
  262. "none", protocol::Maybe<protocol::Overlay::HighlightConfig>());
  263. }
  264. }
  265. // Tests that the correct Overlay events are dispatched to the frontend when
  266. // hovering and clicking over a UI element in inspect mode.
  267. TEST_F(OverlayAgentTest, MouseEventsGenerateFEEventsInInspectMode) {
  268. CreateWidget();
  269. std::unique_ptr<protocol::DOM::Node> root;
  270. dom_agent()->getDocument(&root);
  271. gfx::Point p(1, 1);
  272. int node_id = overlay_agent()->FindElementIdTargetedByPoint(
  273. MouseEventAtRootLocation(p).get());
  274. EXPECT_EQ(0, GetOverlayInspectNodeRequestedCount(node_id));
  275. EXPECT_EQ(0, GetOverlayNodeHighlightRequestedCount(node_id));
  276. overlay_agent()->setInspectMode(
  277. "searchForNode", protocol::Maybe<protocol::Overlay::HighlightConfig>());
  278. // Moving the mouse cursor over the widget bounds should request a node
  279. // highlight.
  280. ui::test::EventGenerator generator(GetRootWindow(widget()));
  281. generator.MoveMouseTo(widget()->GetClientAreaBoundsInScreen().origin());
  282. // Aura platforms generate both ET_MOUSE_ENTERED and ET_MOUSE_MOVED for
  283. // this but Mac just generates ET_MOUSE_ENTERED, so just ensure we sent
  284. // at least one.
  285. EXPECT_GT(GetOverlayNodeHighlightRequestedCount(node_id), 0);
  286. EXPECT_EQ(0, GetOverlayInspectNodeRequestedCount(node_id));
  287. // Clicking on the widget should pin that element.
  288. generator.ClickLeftButton();
  289. // Pin parent node after mouse wheel moves up.
  290. int parent_id = dom_agent()->GetParentIdOfNodeId(node_id);
  291. EXPECT_NE(parent_id, overlay_agent()->pinned_id());
  292. generator.MoveMouseWheel(0, 1);
  293. EXPECT_EQ(parent_id, overlay_agent()->pinned_id());
  294. // Re-assign pin node.
  295. node_id = parent_id;
  296. int inspect_node_notification_count =
  297. GetOverlayInspectNodeRequestedCount(node_id);
  298. // Press escape to exit inspect mode. We're intentionally not supporting
  299. // this on Mac due do difficulties in receiving key events without aura::Env.
  300. #if defined(USE_AURA)
  301. generator.PressKey(ui::KeyboardCode::VKEY_ESCAPE, ui::EF_NONE);
  302. // Upon exiting inspect mode, the element is inspected and highlighted.
  303. EXPECT_EQ(inspect_node_notification_count + 1,
  304. GetOverlayInspectNodeRequestedCount(node_id));
  305. ui::Layer* highlighting_layer = overlay_agent()->layer_for_highlighting();
  306. const SkColor kBackgroundColor = 0;
  307. EXPECT_EQ(kBackgroundColor, highlighting_layer->GetTargetColor());
  308. EXPECT_TRUE(highlighting_layer->visible());
  309. #else
  310. overlay_agent()->setInspectMode(
  311. "none", protocol::Maybe<protocol::Overlay::HighlightConfig>());
  312. #endif
  313. int highlight_notification_count =
  314. GetOverlayNodeHighlightRequestedCount(node_id);
  315. inspect_node_notification_count =
  316. GetOverlayInspectNodeRequestedCount(node_id);
  317. // Since inspect mode is exited, a subsequent mouse move should generate no
  318. // nodeHighlightRequested or inspectNodeRequested events.
  319. generator.MoveMouseBy(p.x(), p.y());
  320. EXPECT_EQ(highlight_notification_count,
  321. GetOverlayNodeHighlightRequestedCount(node_id));
  322. EXPECT_EQ(inspect_node_notification_count,
  323. GetOverlayInspectNodeRequestedCount(node_id));
  324. }
  325. TEST_F(OverlayAgentTest, HighlightNonexistentNode) {
  326. std::unique_ptr<protocol::DOM::Node> root;
  327. dom_agent()->getDocument(&root);
  328. const int id = 1000;
  329. DCHECK(dom_agent()->GetElementFromNodeId(id) == nullptr);
  330. overlay_agent()->highlightNode(nullptr, id);
  331. if (overlay_agent()->layer_for_highlighting()) {
  332. EXPECT_FALSE(overlay_agent()->layer_for_highlighting()->parent());
  333. EXPECT_FALSE(overlay_agent()->layer_for_highlighting()->visible());
  334. }
  335. }
  336. #if defined(USE_AURA)
  337. TEST_F(OverlayAgentTest, HighlightWindow) {
  338. std::unique_ptr<protocol::DOM::Node> root;
  339. dom_agent()->getDocument(&root);
  340. std::unique_ptr<aura::Window> window =
  341. CreateWindowElement(gfx::Rect(0, 0, 20, 20));
  342. int window_id =
  343. dom_agent()
  344. ->element_root()
  345. ->FindUIElementIdForBackendElement<aura::Window>(window.get());
  346. DCHECK_NE(window_id, 0);
  347. overlay_agent()->highlightNode(nullptr, window_id);
  348. ui::Layer* highlightingLayer = overlay_agent()->layer_for_highlighting();
  349. DCHECK(highlightingLayer);
  350. EXPECT_EQ(highlightingLayer->parent(), GetContext()->layer());
  351. EXPECT_TRUE(highlightingLayer->visible());
  352. overlay_agent()->hideHighlight();
  353. EXPECT_FALSE(highlightingLayer->visible());
  354. }
  355. TEST_F(OverlayAgentTest, HighlightEmptyOrInvisibleWindow) {
  356. std::unique_ptr<protocol::DOM::Node> root;
  357. dom_agent()->getDocument(&root);
  358. std::unique_ptr<aura::Window> window = CreateWindowElement(gfx::Rect());
  359. int window_id =
  360. dom_agent()
  361. ->element_root()
  362. ->FindUIElementIdForBackendElement<aura::Window>(window.get());
  363. DCHECK_NE(window_id, 0);
  364. overlay_agent()->highlightNode(nullptr, window_id);
  365. ui::Layer* highlightingLayer = overlay_agent()->layer_for_highlighting();
  366. DCHECK(highlightingLayer);
  367. // Highlight doesn't show for empty element.
  368. EXPECT_FALSE(highlightingLayer->parent());
  369. EXPECT_FALSE(highlightingLayer->visible());
  370. // Make the window non-empty, the highlight shows up.
  371. window->SetBounds(gfx::Rect(10, 10, 50, 50));
  372. overlay_agent()->highlightNode(nullptr, window_id);
  373. EXPECT_EQ(highlightingLayer->parent(), GetContext()->layer());
  374. EXPECT_TRUE(highlightingLayer->visible());
  375. // Make the window invisible, the highlight still shows.
  376. window->Hide();
  377. overlay_agent()->highlightNode(nullptr, window_id);
  378. EXPECT_EQ(highlightingLayer->parent(), GetContext()->layer());
  379. EXPECT_TRUE(highlightingLayer->visible());
  380. }
  381. #endif
  382. TEST_F(OverlayAgentTest, HighlightWidget) {
  383. CreateWidget();
  384. std::unique_ptr<protocol::DOM::Node> root;
  385. dom_agent()->getDocument(&root);
  386. int widget_id =
  387. dom_agent()
  388. ->element_root()
  389. ->FindUIElementIdForBackendElement<views::Widget>(widget());
  390. DCHECK_NE(widget_id, 0);
  391. overlay_agent()->highlightNode(nullptr, widget_id);
  392. ui::Layer* highlightingLayer = overlay_agent()->layer_for_highlighting();
  393. DCHECK(highlightingLayer);
  394. #if defined(USE_AURA)
  395. EXPECT_EQ(highlightingLayer->parent(), GetContext()->layer());
  396. #else
  397. // TODO(https://crbug.com/898280): Fix this for Mac.
  398. #endif
  399. EXPECT_TRUE(highlightingLayer->visible());
  400. overlay_agent()->hideHighlight();
  401. EXPECT_FALSE(highlightingLayer->visible());
  402. }
  403. } // namespace ui_devtools