blob: fbbbedb1a6852f930abcb4fb53998fbde89a178d [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"
Kristyn Hamasaki4de3d0a12019-08-12 19:31:429#include "base/test/scoped_task_environment.h"
10#include "components/ui_devtools/agent_util.h"
Leonard Greybc2b3832018-09-19 15:15:2111#include "components/ui_devtools/dom_agent.h"
12#include "components/ui_devtools/ui_devtools_unittest_utils.h"
13#include "components/ui_devtools/ui_element.h"
14#include "testing/gtest/include/gtest/gtest.h"
15
16namespace ui_devtools {
17
18class FakeUIElement : public UIElement {
19 public:
20 FakeUIElement(UIElementDelegate* ui_element_delegate)
21 : UIElement(UIElementType::ROOT, ui_element_delegate, nullptr) {}
22
23 ~FakeUIElement() override {}
Leonard Greybc2b3832018-09-19 15:15:2124 void GetBounds(gfx::Rect* bounds) const override { *bounds = bounds_; }
25 void SetBounds(const gfx::Rect& bounds) override { bounds_ = bounds; }
26 void GetVisible(bool* visible) const override { *visible = visible_; }
27 void SetVisible(bool visible) override { visible_ = visible; }
Kristyn Hamasaki99877ab2019-07-12 17:02:2528 std::vector<std::string> GetAttributes() const override { return {}; }
29 std::pair<gfx::NativeWindow, gfx::Rect> GetNodeWindowAndScreenBounds()
30 const override {
31 return {};
32 }
Leonard Greybc2b3832018-09-19 15:15:2133 bool visible() const { return visible_; }
34 gfx::Rect bounds() const { return bounds_; }
Kristyn Hamasaki4de3d0a12019-08-12 19:31:4235 void AddSource(std::string path, int line) {
36 UIElement::AddSource(path, line);
37 }
Leonard Greybc2b3832018-09-19 15:15:2138
Leonard Greybc2b3832018-09-19 15:15:2139 private:
40 gfx::Rect bounds_;
41 bool visible_;
42};
43
44class FakeDOMAgent : public DOMAgent {
45 public:
46 void OnUIElementAdded(UIElement* parent, UIElement* child) override {
47 // nullptr root short circuits everything but adding |child|
48 // to the node ID map, which is all we need here.
49 DOMAgent::OnUIElementAdded(nullptr, child);
50 }
51
52 std::unique_ptr<protocol::DOM::Node> BuildTreeForUIElement(
53 UIElement* root) override {
54 return BuildDomNodeFromUIElement(root);
55 }
56
57 std::vector<UIElement*> CreateChildrenForRoot() override { return {}; }
58};
59
60class CSSAgentTest : public testing::Test {
61 public:
62 void SetUp() override {
63 testing::Test::SetUp();
64 frontend_channel_ = std::make_unique<FakeFrontendChannel>();
65 uber_dispatcher_ =
66 std::make_unique<protocol::UberDispatcher>(frontend_channel_.get());
67 dom_agent_ = std::make_unique<FakeDOMAgent>();
68 dom_agent_->Init(uber_dispatcher_.get());
69 css_agent_ = std::make_unique<CSSAgent>(dom_agent_.get());
70 css_agent_->Init(uber_dispatcher_.get());
71 css_agent_->enable();
72 element_ = std::make_unique<FakeUIElement>(dom_agent_.get());
Kristyn Hamasaki99877ab2019-07-12 17:02:2573 element()->SetBaseStylesheetId(0);
Leonard Greybc2b3832018-09-19 15:15:2174 dom_agent_->OnUIElementAdded(nullptr, element_.get());
75 }
76
Kristyn Hamasaki99877ab2019-07-12 17:02:2577 void TearDown() override {
78 css_agent_.reset();
79 dom_agent_.reset();
80 uber_dispatcher_.reset();
81 frontend_channel_.reset();
82 element_.reset();
83 testing::Test::TearDown();
84 }
85
86 std::string BuildStylesheetUId(int node_id, int stylesheet_id) {
87 return base::NumberToString(node_id) + "_" +
88 base::NumberToString(stylesheet_id);
89 }
90
Leonard Greybc2b3832018-09-19 15:15:2191 protected:
Kristyn Hamasaki4de3d0a12019-08-12 19:31:4292 base::test::ScopedTaskEnvironment scoped_task_environment_;
Leonard Greybc2b3832018-09-19 15:15:2193 using StyleArray = protocol::Array<protocol::CSS::CSSStyle>;
94
Kristyn Hamasaki99877ab2019-07-12 17:02:2595 std::pair<bool, std::unique_ptr<StyleArray>>
96 SetStyle(const std::string& style_text, int node_id, int stylesheet_id = 0) {
97 auto edits = std::make_unique<
98 protocol::Array<protocol::CSS::StyleDeclarationEdit>>();
Leonard Greybc2b3832018-09-19 15:15:2199 auto edit = protocol::CSS::StyleDeclarationEdit::create()
Kristyn Hamasaki99877ab2019-07-12 17:02:25100 .setStyleSheetId(BuildStylesheetUId(node_id, stylesheet_id))
Wei Lia7ff68ad2018-10-15 18:28:22101 .setRange(protocol::CSS::SourceRange::create()
102 .setStartLine(0)
103 .setStartColumn(0)
104 .setEndLine(0)
105 .setEndColumn(0)
106 .build())
Leonard Greybc2b3832018-09-19 15:15:21107 .setText(style_text)
108 .build();
Kristyn Hamasaki99877ab2019-07-12 17:02:25109 edits->emplace_back(std::move(edit));
Leonard Greybc2b3832018-09-19 15:15:21110 std::unique_ptr<StyleArray> output;
111 auto response = css_agent_->setStyleTexts(std::move(edits), &output);
112 return {response.isSuccess(), std::move(output)};
113 }
114
115 std::string GetValueForProperty(protocol::CSS::CSSStyle* style,
116 const std::string& property_name) {
Kristyn Hamasaki99877ab2019-07-12 17:02:25117 for (const auto& property : *(style->getCssProperties())) {
Leonard Greybc2b3832018-09-19 15:15:21118 if (property->getName() == property_name) {
119 return property->getValue();
120 }
121 }
122 return std::string();
123 }
Kristyn Hamasaki4de3d0a12019-08-12 19:31:42124
Kristyn Hamasaki99877ab2019-07-12 17:02:25125 int GetStyleSheetChangedCount(std::string stylesheet_id) {
Leonard Greybc2b3832018-09-19 15:15:21126 return frontend_channel_->CountProtocolNotificationMessage(
Kristyn Hamasaki99877ab2019-07-12 17:02:25127 "{\"method\":\"CSS.styleSheetChanged\",\"params\":{"
128 "\"styleSheetId\":\"" +
129 stylesheet_id + "\"}}");
Leonard Greybc2b3832018-09-19 15:15:21130 }
131
Kristyn Hamasaki4de3d0a12019-08-12 19:31:42132 std::pair<bool, std::string> GetSourceForElement() {
133 std::string output;
134 auto response = css_agent_->getStyleSheetText(
135 BuildStylesheetUId(element()->node_id(), 0), &output);
136 return {response.isSuccess(), output};
137 }
138
Leonard Greybc2b3832018-09-19 15:15:21139 CSSAgent* css_agent() { return css_agent_.get(); }
140 DOMAgent* dom_agent() { return dom_agent_.get(); }
141 FakeUIElement* element() { return element_.get(); }
142
143 private:
144 std::unique_ptr<CSSAgent> css_agent_;
145 std::unique_ptr<DOMAgent> dom_agent_;
146 std::unique_ptr<FakeFrontendChannel> frontend_channel_;
147 std::unique_ptr<protocol::UberDispatcher> uber_dispatcher_;
148 std::unique_ptr<FakeUIElement> element_;
149};
150
151TEST_F(CSSAgentTest, UnrecognizedNodeFails) {
152 EXPECT_FALSE(SetStyle("x : 25", 42).first);
153}
154
155TEST_F(CSSAgentTest, UnrecognizedKeyFails) {
156 element()->SetVisible(true);
157 element()->SetBounds(gfx::Rect(1, 2, 3, 4));
158
159 auto result = SetStyle("nonsense : 3.14", element()->node_id());
160
161 EXPECT_FALSE(result.first);
162 EXPECT_FALSE(result.second);
163 // Ensure element didn't change.
164 EXPECT_TRUE(element()->visible());
165 EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4));
166}
167
168TEST_F(CSSAgentTest, UnrecognizedValueFails) {
169 element()->SetVisible(true);
170 element()->SetBounds(gfx::Rect(1, 2, 3, 4));
171
172 auto result = SetStyle("visibility : banana", element()->node_id());
173 EXPECT_FALSE(result.first);
174 EXPECT_FALSE(result.second);
175 // Ensure element didn't change.
176 EXPECT_TRUE(element()->visible());
177 EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4));
178}
179
Kristyn Hamasaki9424c4b2019-08-10 03:57:27180TEST_F(CSSAgentTest, BadInputsFail) {
181 element()->SetVisible(true);
182 element()->SetBounds(gfx::Rect(1, 2, 3, 4));
183
184 // Input with no property name.
185 auto result = SetStyle(": 1;", element()->node_id());
186 EXPECT_FALSE(result.first);
187 EXPECT_FALSE(result.second);
188 // Ensure element didn't change.
189 EXPECT_TRUE(element()->visible());
190 EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4));
191
192 // Input with no property value.
193 result = SetStyle("visibility:", element()->node_id());
194 EXPECT_FALSE(result.first);
195 EXPECT_FALSE(result.second);
196 // Ensure element didn't change.
197 EXPECT_TRUE(element()->visible());
198 EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4));
199
200 // Blank input.
201 result = SetStyle(":", element()->node_id());
202 EXPECT_FALSE(result.first);
203 EXPECT_FALSE(result.second);
204 // Ensure element didn't change.
205 EXPECT_TRUE(element()->visible());
206 EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4));
207}
208
Leonard Greybc2b3832018-09-19 15:15:21209TEST_F(CSSAgentTest, SettingVisibility) {
210 element()->SetVisible(false);
211 DCHECK(!element()->visible());
212
213 auto result = SetStyle("visibility: 1", element()->node_id());
214 EXPECT_TRUE(result.first);
215 EXPECT_TRUE(element()->visible());
216
Kristyn Hamasaki99877ab2019-07-12 17:02:25217 EXPECT_EQ(result.second->size(), 1U);
218 std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0);
Leonard Greybc2b3832018-09-19 15:15:21219 EXPECT_EQ(style->getStyleSheetId("default"),
Kristyn Hamasaki99877ab2019-07-12 17:02:25220 base::NumberToString(element()->node_id()) + "_0");
221 EXPECT_EQ(GetValueForProperty(style.get(), "visibility"), "true");
Leonard Greybc2b3832018-09-19 15:15:21222}
223
224TEST_F(CSSAgentTest, SettingX) {
225 DCHECK_EQ(element()->bounds().x(), 0);
226
227 auto result = SetStyle("x: 500", element()->node_id());
228 EXPECT_TRUE(result.first);
229 EXPECT_EQ(element()->bounds().x(), 500);
Kristyn Hamasaki99877ab2019-07-12 17:02:25230 EXPECT_EQ(result.second->size(), 1U);
231 std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0);
Leonard Greybc2b3832018-09-19 15:15:21232 EXPECT_EQ(style->getStyleSheetId("default"),
Kristyn Hamasaki99877ab2019-07-12 17:02:25233 base::NumberToString(element()->node_id()) + "_0");
234 EXPECT_EQ(GetValueForProperty(style.get(), "x"), "500");
Leonard Greybc2b3832018-09-19 15:15:21235}
236
237TEST_F(CSSAgentTest, SettingY) {
238 DCHECK_EQ(element()->bounds().y(), 0);
239
240 auto result = SetStyle("y: 100", element()->node_id());
241 EXPECT_TRUE(result.first);
242 EXPECT_EQ(element()->bounds().y(), 100);
Kristyn Hamasaki99877ab2019-07-12 17:02:25243 EXPECT_EQ(result.second->size(), 1U);
244 std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0);
Leonard Greybc2b3832018-09-19 15:15:21245 EXPECT_EQ(style->getStyleSheetId("default"),
Kristyn Hamasaki99877ab2019-07-12 17:02:25246 base::NumberToString(element()->node_id()) + "_0");
247 EXPECT_EQ(GetValueForProperty(style.get(), "y"), "100");
Leonard Greybc2b3832018-09-19 15:15:21248}
249TEST_F(CSSAgentTest, SettingWidth) {
250 DCHECK_EQ(element()->bounds().width(), 0);
251
252 auto result = SetStyle("width: 20", element()->node_id());
253 EXPECT_TRUE(result.first);
254 EXPECT_EQ(element()->bounds().width(), 20);
Kristyn Hamasaki99877ab2019-07-12 17:02:25255 EXPECT_EQ(result.second->size(), 1U);
256 std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0);
Leonard Greybc2b3832018-09-19 15:15:21257 EXPECT_EQ(style->getStyleSheetId("default"),
Kristyn Hamasaki99877ab2019-07-12 17:02:25258 base::NumberToString(element()->node_id()) + "_0");
259 EXPECT_EQ(GetValueForProperty(style.get(), "width"), "20");
Leonard Greybc2b3832018-09-19 15:15:21260}
261TEST_F(CSSAgentTest, SettingHeight) {
262 DCHECK_EQ(element()->bounds().height(), 0);
263
264 auto result = SetStyle("height: 30", element()->node_id());
265 EXPECT_TRUE(result.first);
266 EXPECT_EQ(element()->bounds().height(), 30);
Kristyn Hamasaki99877ab2019-07-12 17:02:25267 EXPECT_EQ(result.second->size(), 1U);
268 std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0);
Leonard Greybc2b3832018-09-19 15:15:21269 EXPECT_EQ(style->getStyleSheetId("default"),
Kristyn Hamasaki99877ab2019-07-12 17:02:25270 base::NumberToString(element()->node_id()) + "_0");
271 EXPECT_EQ(GetValueForProperty(style.get(), "height"), "30");
Leonard Greybc2b3832018-09-19 15:15:21272}
273
274TEST_F(CSSAgentTest, SettingAll) {
275 DCHECK(element()->bounds() == gfx::Rect());
Kristyn Hamasaki99877ab2019-07-12 17:02:25276 element()->SetVisible(true);
Leonard Greybc2b3832018-09-19 15:15:21277 DCHECK(element()->visible());
278
279 // Throw in odd whitespace while we're at it.
280 std::string new_text(
281 "\ny: 25; width: 50; visibility:0; height: 30;\nx: 9000;\n\n");
282 auto result = SetStyle(new_text, element()->node_id());
283 EXPECT_TRUE(result.first);
284 EXPECT_EQ(element()->bounds(), gfx::Rect(9000, 25, 50, 30));
285 EXPECT_FALSE(element()->visible());
Kristyn Hamasaki99877ab2019-07-12 17:02:25286 EXPECT_EQ(result.second->size(), 1U);
287 std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0);
Leonard Greybc2b3832018-09-19 15:15:21288 EXPECT_EQ(style->getStyleSheetId("default"),
Kristyn Hamasaki99877ab2019-07-12 17:02:25289 base::NumberToString(element()->node_id()) + "_0");
290 EXPECT_EQ(GetValueForProperty(style.get(), "x"), "9000");
291 EXPECT_EQ(GetValueForProperty(style.get(), "y"), "25");
292 EXPECT_EQ(GetValueForProperty(style.get(), "width"), "50");
293 EXPECT_EQ(GetValueForProperty(style.get(), "height"), "30");
294 EXPECT_EQ(GetValueForProperty(style.get(), "visibility"), "false");
Leonard Greybc2b3832018-09-19 15:15:21295}
296
297TEST_F(CSSAgentTest, UpdateOnBoundsChange) {
298 FakeUIElement another_element(dom_agent());
Kristyn Hamasaki99877ab2019-07-12 17:02:25299 another_element.SetBaseStylesheetId(0);
300 std::string element_stylesheet_id =
301 BuildStylesheetUId(element()->node_id(), 0);
302 std::string another_element_stylesheet_id =
303 BuildStylesheetUId(another_element.node_id(), 0);
304
305 EXPECT_EQ(0, GetStyleSheetChangedCount(element_stylesheet_id));
306 EXPECT_EQ(0, GetStyleSheetChangedCount(another_element_stylesheet_id));
Leonard Greybc2b3832018-09-19 15:15:21307 css_agent()->OnElementBoundsChanged(element());
Kristyn Hamasaki99877ab2019-07-12 17:02:25308 EXPECT_EQ(1, GetStyleSheetChangedCount(element_stylesheet_id));
309 EXPECT_EQ(0, GetStyleSheetChangedCount(another_element_stylesheet_id));
Leonard Greybc2b3832018-09-19 15:15:21310 css_agent()->OnElementBoundsChanged(&another_element);
Kristyn Hamasaki99877ab2019-07-12 17:02:25311 EXPECT_EQ(1, GetStyleSheetChangedCount(element_stylesheet_id));
312 EXPECT_EQ(1, GetStyleSheetChangedCount(another_element_stylesheet_id));
Leonard Greybc2b3832018-09-19 15:15:21313
314 css_agent()->OnElementBoundsChanged(&another_element);
Kristyn Hamasaki99877ab2019-07-12 17:02:25315 EXPECT_EQ(1, GetStyleSheetChangedCount(element_stylesheet_id));
316 EXPECT_EQ(2, GetStyleSheetChangedCount(another_element_stylesheet_id));
Leonard Greybc2b3832018-09-19 15:15:21317}
Kristyn Hamasaki99877ab2019-07-12 17:02:25318
Kristyn Hamasaki52898792019-08-16 18:30:02319TEST_F(CSSAgentTest, GetSource) {
320 // Tests CSSAgent::getStyleSheetText() with one source file.
Kristyn Hamasaki4de3d0a12019-08-12 19:31:42321 std::string file = "components/test/data/ui_devtools/test_file.cc";
322 element()->AddSource(file, 0);
323 auto result = GetSourceForElement();
324
Kristyn Hamasaki52898792019-08-16 18:30:02325 // Ensure that test_file.cc was read in correctly.
Kristyn Hamasaki4de3d0a12019-08-12 19:31:42326 EXPECT_TRUE(result.first);
Kristyn Hamasaki52898792019-08-16 18:30:02327 EXPECT_NE(std::string::npos,
328 result.second.find("This file is for testing GetSource."));
Kristyn Hamasaki4de3d0a12019-08-12 19:31:42329}
330
331TEST_F(CSSAgentTest, BadPathFails) {
332 element()->AddSource("not/a/real/path", 0);
333 auto result = GetSourceForElement();
334
335 EXPECT_FALSE(result.first);
336 EXPECT_EQ(result.second, "");
337}
338
Leonard Greybc2b3832018-09-19 15:15:21339} // namespace ui_devtools