blob: ec1d27d3d7ec115d46bbfbb70d52efa84beb7220 [file] [log] [blame]
Leonard Greybc2b3832018-09-19 15:15:211// 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
5#include "components/ui_devtools/css_agent.h"
6
7#include "base/strings/string_util.h"
8#include "base/strings/stringprintf.h"
9#include "components/ui_devtools/dom_agent.h"
10#include "components/ui_devtools/ui_devtools_unittest_utils.h"
11#include "components/ui_devtools/ui_element.h"
12#include "testing/gtest/include/gtest/gtest.h"
13
14namespace ui_devtools {
15
16class FakeUIElement : public UIElement {
17 public:
18 FakeUIElement(UIElementDelegate* ui_element_delegate)
19 : UIElement(UIElementType::ROOT, ui_element_delegate, nullptr) {}
20
21 ~FakeUIElement() override {}
22 // Return a vector of pairs of properties' names and values.
23 std::vector<std::pair<std::string, std::string>> GetCustomProperties()
24 const override {
25 return {};
26 }
27 void GetBounds(gfx::Rect* bounds) const override { *bounds = bounds_; }
28 void SetBounds(const gfx::Rect& bounds) override { bounds_ = bounds; }
29 void GetVisible(bool* visible) const override { *visible = visible_; }
30 void SetVisible(bool visible) override { visible_ = visible; }
31
32 bool visible() const { return visible_; }
33 gfx::Rect bounds() const { return bounds_; }
34
35 std::pair<gfx::NativeWindow, gfx::Rect> GetNodeWindowAndBounds()
36 const override {
37 return {nullptr, gfx::Rect()};
38 }
39
40 std::unique_ptr<protocol::Array<std::string>> GetAttributes() const override {
41 return protocol::Array<std::string>::create();
42 }
43
44 private:
45 gfx::Rect bounds_;
46 bool visible_;
47};
48
49class FakeDOMAgent : public DOMAgent {
50 public:
51 void OnUIElementAdded(UIElement* parent, UIElement* child) override {
52 // nullptr root short circuits everything but adding |child|
53 // to the node ID map, which is all we need here.
54 DOMAgent::OnUIElementAdded(nullptr, child);
55 }
56
57 std::unique_ptr<protocol::DOM::Node> BuildTreeForUIElement(
58 UIElement* root) override {
59 return BuildDomNodeFromUIElement(root);
60 }
61
62 std::vector<UIElement*> CreateChildrenForRoot() override { return {}; }
63};
64
65class CSSAgentTest : public testing::Test {
66 public:
67 void SetUp() override {
68 testing::Test::SetUp();
69 frontend_channel_ = std::make_unique<FakeFrontendChannel>();
70 uber_dispatcher_ =
71 std::make_unique<protocol::UberDispatcher>(frontend_channel_.get());
72 dom_agent_ = std::make_unique<FakeDOMAgent>();
73 dom_agent_->Init(uber_dispatcher_.get());
74 css_agent_ = std::make_unique<CSSAgent>(dom_agent_.get());
75 css_agent_->Init(uber_dispatcher_.get());
76 css_agent_->enable();
77 element_ = std::make_unique<FakeUIElement>(dom_agent_.get());
78 dom_agent_->OnUIElementAdded(nullptr, element_.get());
79 }
80
81 protected:
82 using StyleArray = protocol::Array<protocol::CSS::CSSStyle>;
83
84 std::pair<bool, std::unique_ptr<StyleArray>> SetStyle(
85 const std::string& style_text,
86 int node_id) {
87 auto edits = protocol::Array<protocol::CSS::StyleDeclarationEdit>::create();
88 auto edit = protocol::CSS::StyleDeclarationEdit::create()
89 .setStyleSheetId(base::IntToString(node_id))
90 .setText(style_text)
91 .build();
92 edits->addItem(std::move(edit));
93 std::unique_ptr<StyleArray> output;
94 auto response = css_agent_->setStyleTexts(std::move(edits), &output);
95 return {response.isSuccess(), std::move(output)};
96 }
97
98 std::string GetValueForProperty(protocol::CSS::CSSStyle* style,
99 const std::string& property_name) {
100 auto* properties = style->getCssProperties();
101 for (size_t i = 0; i < properties->length(); ++i) {
102 auto* property = properties->get(i);
103 if (property->getName() == property_name) {
104 return property->getValue();
105 }
106 }
107 return std::string();
108 }
109 int GetStyleSheetChangedCount(int node_id) {
110 return frontend_channel_->CountProtocolNotificationMessage(
111 base::StringPrintf("{\"method\":\"CSS.styleSheetChanged\",\"params\":{"
112 "\"styleSheetId\":\"%d\"}}",
113 node_id));
114 }
115
116 CSSAgent* css_agent() { return css_agent_.get(); }
117 DOMAgent* dom_agent() { return dom_agent_.get(); }
118 FakeUIElement* element() { return element_.get(); }
119
120 private:
121 std::unique_ptr<CSSAgent> css_agent_;
122 std::unique_ptr<DOMAgent> dom_agent_;
123 std::unique_ptr<FakeFrontendChannel> frontend_channel_;
124 std::unique_ptr<protocol::UberDispatcher> uber_dispatcher_;
125 std::unique_ptr<FakeUIElement> element_;
126};
127
128TEST_F(CSSAgentTest, UnrecognizedNodeFails) {
129 EXPECT_FALSE(SetStyle("x : 25", 42).first);
130}
131
132TEST_F(CSSAgentTest, UnrecognizedKeyFails) {
133 element()->SetVisible(true);
134 element()->SetBounds(gfx::Rect(1, 2, 3, 4));
135
136 auto result = SetStyle("nonsense : 3.14", element()->node_id());
137
138 EXPECT_FALSE(result.first);
139 EXPECT_FALSE(result.second);
140 // Ensure element didn't change.
141 EXPECT_TRUE(element()->visible());
142 EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4));
143}
144
145TEST_F(CSSAgentTest, UnrecognizedValueFails) {
146 element()->SetVisible(true);
147 element()->SetBounds(gfx::Rect(1, 2, 3, 4));
148
149 auto result = SetStyle("visibility : banana", element()->node_id());
150 EXPECT_FALSE(result.first);
151 EXPECT_FALSE(result.second);
152 // Ensure element didn't change.
153 EXPECT_TRUE(element()->visible());
154 EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4));
155}
156
157TEST_F(CSSAgentTest, SettingVisibility) {
158 element()->SetVisible(false);
159 DCHECK(!element()->visible());
160
161 auto result = SetStyle("visibility: 1", element()->node_id());
162 EXPECT_TRUE(result.first);
163 EXPECT_TRUE(element()->visible());
164
165 EXPECT_EQ(result.second->length(), 1U);
166 protocol::CSS::CSSStyle* style = result.second->get(0);
167 EXPECT_EQ(style->getStyleSheetId("default"),
168 base::IntToString(element()->node_id()));
169 EXPECT_EQ(GetValueForProperty(style, "visibility"), "1");
170}
171
172TEST_F(CSSAgentTest, SettingX) {
173 DCHECK_EQ(element()->bounds().x(), 0);
174
175 auto result = SetStyle("x: 500", element()->node_id());
176 EXPECT_TRUE(result.first);
177 EXPECT_EQ(element()->bounds().x(), 500);
178 EXPECT_EQ(result.second->length(), 1U);
179 protocol::CSS::CSSStyle* style = result.second->get(0);
180 EXPECT_EQ(style->getStyleSheetId("default"),
181 base::IntToString(element()->node_id()));
182 EXPECT_EQ(GetValueForProperty(style, "x"), "500");
183}
184
185TEST_F(CSSAgentTest, SettingY) {
186 DCHECK_EQ(element()->bounds().y(), 0);
187
188 auto result = SetStyle("y: 100", element()->node_id());
189 EXPECT_TRUE(result.first);
190 EXPECT_EQ(element()->bounds().y(), 100);
191 EXPECT_EQ(result.second->length(), 1U);
192 protocol::CSS::CSSStyle* style = result.second->get(0);
193 EXPECT_EQ(style->getStyleSheetId("default"),
194 base::IntToString(element()->node_id()));
195 EXPECT_EQ(GetValueForProperty(style, "y"), "100");
196}
197TEST_F(CSSAgentTest, SettingWidth) {
198 DCHECK_EQ(element()->bounds().width(), 0);
199
200 auto result = SetStyle("width: 20", element()->node_id());
201 EXPECT_TRUE(result.first);
202 EXPECT_EQ(element()->bounds().width(), 20);
203 EXPECT_EQ(result.second->length(), 1U);
204 protocol::CSS::CSSStyle* style = result.second->get(0);
205 EXPECT_EQ(style->getStyleSheetId("default"),
206 base::IntToString(element()->node_id()));
207 EXPECT_EQ(GetValueForProperty(style, "width"), "20");
208}
209TEST_F(CSSAgentTest, SettingHeight) {
210 DCHECK_EQ(element()->bounds().height(), 0);
211
212 auto result = SetStyle("height: 30", element()->node_id());
213 EXPECT_TRUE(result.first);
214 EXPECT_EQ(element()->bounds().height(), 30);
215 EXPECT_EQ(result.second->length(), 1U);
216 protocol::CSS::CSSStyle* style = result.second->get(0);
217 EXPECT_EQ(style->getStyleSheetId("default"),
218 base::IntToString(element()->node_id()));
219 EXPECT_EQ(GetValueForProperty(style, "height"), "30");
220}
221
222TEST_F(CSSAgentTest, SettingAll) {
223 DCHECK(element()->bounds() == gfx::Rect());
224 DCHECK(element()->visible());
225
226 // Throw in odd whitespace while we're at it.
227 std::string new_text(
228 "\ny: 25; width: 50; visibility:0; height: 30;\nx: 9000;\n\n");
229 auto result = SetStyle(new_text, element()->node_id());
230 EXPECT_TRUE(result.first);
231 EXPECT_EQ(element()->bounds(), gfx::Rect(9000, 25, 50, 30));
232 EXPECT_FALSE(element()->visible());
233 EXPECT_EQ(result.second->length(), 1U);
234 protocol::CSS::CSSStyle* style = result.second->get(0);
235 EXPECT_EQ(style->getStyleSheetId("default"),
236 base::IntToString(element()->node_id()));
237 EXPECT_EQ(GetValueForProperty(style, "x"), "9000");
238 EXPECT_EQ(GetValueForProperty(style, "y"), "25");
239 EXPECT_EQ(GetValueForProperty(style, "width"), "50");
240 EXPECT_EQ(GetValueForProperty(style, "height"), "30");
241 EXPECT_EQ(GetValueForProperty(style, "visibility"), "0");
242}
243
244TEST_F(CSSAgentTest, UpdateOnBoundsChange) {
245 FakeUIElement another_element(dom_agent());
246 EXPECT_EQ(0, GetStyleSheetChangedCount(element()->node_id()));
247 EXPECT_EQ(0, GetStyleSheetChangedCount(another_element.node_id()));
248 css_agent()->OnElementBoundsChanged(element());
249 EXPECT_EQ(1, GetStyleSheetChangedCount(element()->node_id()));
250 EXPECT_EQ(0, GetStyleSheetChangedCount(another_element.node_id()));
251 css_agent()->OnElementBoundsChanged(&another_element);
252 EXPECT_EQ(1, GetStyleSheetChangedCount(element()->node_id()));
253 EXPECT_EQ(1, GetStyleSheetChangedCount(another_element.node_id()));
254
255 css_agent()->OnElementBoundsChanged(&another_element);
256 EXPECT_EQ(1, GetStyleSheetChangedCount(element()->node_id()));
257 EXPECT_EQ(2, GetStyleSheetChangedCount(another_element.node_id()));
258}
259} // namespace ui_devtools