blob: 3e1e64b09224a7f4ca92d230d2a88a26fe34802d [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
Johannes Henkel5cd89a672020-02-06 21:48:057#include "base/strings/string_number_conversions.h"
Leonard Greybc2b3832018-09-19 15:15:218#include "base/strings/string_util.h"
9#include "base/strings/stringprintf.h"
Gabriel Charettec7108742019-08-23 03:31:4010#include "base/test/task_environment.h"
Kristyn Hamasaki4de3d0a12019-08-12 19:31:4211#include "components/ui_devtools/agent_util.h"
Leonard Greybc2b3832018-09-19 15:15:2112#include "components/ui_devtools/dom_agent.h"
13#include "components/ui_devtools/ui_devtools_unittest_utils.h"
14#include "components/ui_devtools/ui_element.h"
15#include "testing/gtest/include/gtest/gtest.h"
16
17namespace ui_devtools {
18
19class FakeUIElement : public UIElement {
20 public:
21 FakeUIElement(UIElementDelegate* ui_element_delegate)
22 : UIElement(UIElementType::ROOT, ui_element_delegate, nullptr) {}
23
24 ~FakeUIElement() override {}
Leonard Greybc2b3832018-09-19 15:15:2125 void GetBounds(gfx::Rect* bounds) const override { *bounds = bounds_; }
26 void SetBounds(const gfx::Rect& bounds) override { bounds_ = bounds; }
27 void GetVisible(bool* visible) const override { *visible = visible_; }
28 void SetVisible(bool visible) override { visible_ = visible; }
Kristyn Hamasaki99877ab2019-07-12 17:02:2529 std::vector<std::string> GetAttributes() const override { return {}; }
30 std::pair<gfx::NativeWindow, gfx::Rect> GetNodeWindowAndScreenBounds()
31 const override {
32 return {};
33 }
Leonard Greybc2b3832018-09-19 15:15:2134 bool visible() const { return visible_; }
35 gfx::Rect bounds() const { return bounds_; }
Kristyn Hamasaki4de3d0a12019-08-12 19:31:4236 void AddSource(std::string path, int line) {
37 UIElement::AddSource(path, line);
38 }
Leonard Greybc2b3832018-09-19 15:15:2139
Leonard Greybc2b3832018-09-19 15:15:2140 private:
41 gfx::Rect bounds_;
42 bool visible_;
43};
44
45class FakeDOMAgent : public DOMAgent {
46 public:
47 void OnUIElementAdded(UIElement* parent, UIElement* child) override {
48 // nullptr root short circuits everything but adding |child|
49 // to the node ID map, which is all we need here.
50 DOMAgent::OnUIElementAdded(nullptr, child);
51 }
52
53 std::unique_ptr<protocol::DOM::Node> BuildTreeForUIElement(
54 UIElement* root) override {
55 return BuildDomNodeFromUIElement(root);
56 }
57
58 std::vector<UIElement*> CreateChildrenForRoot() override { return {}; }
59};
60
61class CSSAgentTest : public testing::Test {
62 public:
63 void SetUp() override {
64 testing::Test::SetUp();
65 frontend_channel_ = std::make_unique<FakeFrontendChannel>();
66 uber_dispatcher_ =
67 std::make_unique<protocol::UberDispatcher>(frontend_channel_.get());
68 dom_agent_ = std::make_unique<FakeDOMAgent>();
69 dom_agent_->Init(uber_dispatcher_.get());
70 css_agent_ = std::make_unique<CSSAgent>(dom_agent_.get());
71 css_agent_->Init(uber_dispatcher_.get());
72 css_agent_->enable();
73 element_ = std::make_unique<FakeUIElement>(dom_agent_.get());
Kristyn Hamasaki99877ab2019-07-12 17:02:2574 element()->SetBaseStylesheetId(0);
Leonard Greybc2b3832018-09-19 15:15:2175 dom_agent_->OnUIElementAdded(nullptr, element_.get());
76 }
77
Kristyn Hamasaki99877ab2019-07-12 17:02:2578 void TearDown() override {
79 css_agent_.reset();
80 dom_agent_.reset();
81 uber_dispatcher_.reset();
82 frontend_channel_.reset();
83 element_.reset();
84 testing::Test::TearDown();
85 }
86
87 std::string BuildStylesheetUId(int node_id, int stylesheet_id) {
88 return base::NumberToString(node_id) + "_" +
89 base::NumberToString(stylesheet_id);
90 }
91
Leonard Greybc2b3832018-09-19 15:15:2192 protected:
Gabriel Charettedfa36042019-08-19 17:30:1193 base::test::TaskEnvironment task_environment_;
Leonard Greybc2b3832018-09-19 15:15:2194 using StyleArray = protocol::Array<protocol::CSS::CSSStyle>;
95
Kristyn Hamasaki99877ab2019-07-12 17:02:2596 std::pair<bool, std::unique_ptr<StyleArray>>
97 SetStyle(const std::string& style_text, int node_id, int stylesheet_id = 0) {
98 auto edits = std::make_unique<
99 protocol::Array<protocol::CSS::StyleDeclarationEdit>>();
Leonard Greybc2b3832018-09-19 15:15:21100 auto edit = protocol::CSS::StyleDeclarationEdit::create()
Kristyn Hamasaki99877ab2019-07-12 17:02:25101 .setStyleSheetId(BuildStylesheetUId(node_id, stylesheet_id))
Wei Lia7ff68ad2018-10-15 18:28:22102 .setRange(protocol::CSS::SourceRange::create()
103 .setStartLine(0)
104 .setStartColumn(0)
105 .setEndLine(0)
106 .setEndColumn(0)
107 .build())
Leonard Greybc2b3832018-09-19 15:15:21108 .setText(style_text)
109 .build();
Kristyn Hamasaki99877ab2019-07-12 17:02:25110 edits->emplace_back(std::move(edit));
Leonard Greybc2b3832018-09-19 15:15:21111 std::unique_ptr<StyleArray> output;
112 auto response = css_agent_->setStyleTexts(std::move(edits), &output);
Johannes Henkeld5fead72020-03-20 02:09:01113 return {response.IsSuccess(), std::move(output)};
Leonard Greybc2b3832018-09-19 15:15:21114 }
115
116 std::string GetValueForProperty(protocol::CSS::CSSStyle* style,
117 const std::string& property_name) {
Kristyn Hamasaki99877ab2019-07-12 17:02:25118 for (const auto& property : *(style->getCssProperties())) {
Leonard Greybc2b3832018-09-19 15:15:21119 if (property->getName() == property_name) {
120 return property->getValue();
121 }
122 }
123 return std::string();
124 }
Kristyn Hamasaki4de3d0a12019-08-12 19:31:42125
Kristyn Hamasaki99877ab2019-07-12 17:02:25126 int GetStyleSheetChangedCount(std::string stylesheet_id) {
Leonard Greybc2b3832018-09-19 15:15:21127 return frontend_channel_->CountProtocolNotificationMessage(
Kristyn Hamasaki99877ab2019-07-12 17:02:25128 "{\"method\":\"CSS.styleSheetChanged\",\"params\":{"
129 "\"styleSheetId\":\"" +
130 stylesheet_id + "\"}}");
Leonard Greybc2b3832018-09-19 15:15:21131 }
132
Kristyn Hamasaki4de3d0a12019-08-12 19:31:42133 std::pair<bool, std::string> GetSourceForElement() {
134 std::string output;
135 auto response = css_agent_->getStyleSheetText(
136 BuildStylesheetUId(element()->node_id(), 0), &output);
Johannes Henkeld5fead72020-03-20 02:09:01137 return {response.IsSuccess(), output};
Kristyn Hamasaki4de3d0a12019-08-12 19:31:42138 }
139
Leonard Greybc2b3832018-09-19 15:15:21140 CSSAgent* css_agent() { return css_agent_.get(); }
141 DOMAgent* dom_agent() { return dom_agent_.get(); }
142 FakeUIElement* element() { return element_.get(); }
143
144 private:
145 std::unique_ptr<CSSAgent> css_agent_;
146 std::unique_ptr<DOMAgent> dom_agent_;
147 std::unique_ptr<FakeFrontendChannel> frontend_channel_;
148 std::unique_ptr<protocol::UberDispatcher> uber_dispatcher_;
149 std::unique_ptr<FakeUIElement> element_;
150};
151
152TEST_F(CSSAgentTest, UnrecognizedNodeFails) {
153 EXPECT_FALSE(SetStyle("x : 25", 42).first);
154}
155
156TEST_F(CSSAgentTest, UnrecognizedKeyFails) {
157 element()->SetVisible(true);
158 element()->SetBounds(gfx::Rect(1, 2, 3, 4));
159
160 auto result = SetStyle("nonsense : 3.14", element()->node_id());
161
162 EXPECT_FALSE(result.first);
163 EXPECT_FALSE(result.second);
164 // Ensure element didn't change.
165 EXPECT_TRUE(element()->visible());
166 EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4));
167}
168
169TEST_F(CSSAgentTest, UnrecognizedValueFails) {
170 element()->SetVisible(true);
171 element()->SetBounds(gfx::Rect(1, 2, 3, 4));
172
173 auto result = SetStyle("visibility : banana", element()->node_id());
174 EXPECT_FALSE(result.first);
175 EXPECT_FALSE(result.second);
176 // Ensure element didn't change.
177 EXPECT_TRUE(element()->visible());
178 EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4));
179}
180
Kristyn Hamasaki9424c4b2019-08-10 03:57:27181TEST_F(CSSAgentTest, BadInputsFail) {
182 element()->SetVisible(true);
183 element()->SetBounds(gfx::Rect(1, 2, 3, 4));
184
185 // Input with no property name.
186 auto result = SetStyle(": 1;", element()->node_id());
187 EXPECT_FALSE(result.first);
188 EXPECT_FALSE(result.second);
189 // Ensure element didn't change.
190 EXPECT_TRUE(element()->visible());
191 EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4));
192
193 // Input with no property value.
194 result = SetStyle("visibility:", element()->node_id());
195 EXPECT_FALSE(result.first);
196 EXPECT_FALSE(result.second);
197 // Ensure element didn't change.
198 EXPECT_TRUE(element()->visible());
199 EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4));
200
201 // Blank input.
202 result = SetStyle(":", element()->node_id());
203 EXPECT_FALSE(result.first);
204 EXPECT_FALSE(result.second);
205 // Ensure element didn't change.
206 EXPECT_TRUE(element()->visible());
207 EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4));
208}
209
Leonard Greybc2b3832018-09-19 15:15:21210TEST_F(CSSAgentTest, SettingVisibility) {
211 element()->SetVisible(false);
212 DCHECK(!element()->visible());
213
214 auto result = SetStyle("visibility: 1", element()->node_id());
215 EXPECT_TRUE(result.first);
216 EXPECT_TRUE(element()->visible());
217
Kristyn Hamasaki99877ab2019-07-12 17:02:25218 EXPECT_EQ(result.second->size(), 1U);
219 std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0);
Leonard Greybc2b3832018-09-19 15:15:21220 EXPECT_EQ(style->getStyleSheetId("default"),
Kristyn Hamasaki99877ab2019-07-12 17:02:25221 base::NumberToString(element()->node_id()) + "_0");
222 EXPECT_EQ(GetValueForProperty(style.get(), "visibility"), "true");
Leonard Greybc2b3832018-09-19 15:15:21223}
224
225TEST_F(CSSAgentTest, SettingX) {
226 DCHECK_EQ(element()->bounds().x(), 0);
227
228 auto result = SetStyle("x: 500", element()->node_id());
229 EXPECT_TRUE(result.first);
230 EXPECT_EQ(element()->bounds().x(), 500);
Kristyn Hamasaki99877ab2019-07-12 17:02:25231 EXPECT_EQ(result.second->size(), 1U);
232 std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0);
Leonard Greybc2b3832018-09-19 15:15:21233 EXPECT_EQ(style->getStyleSheetId("default"),
Kristyn Hamasaki99877ab2019-07-12 17:02:25234 base::NumberToString(element()->node_id()) + "_0");
235 EXPECT_EQ(GetValueForProperty(style.get(), "x"), "500");
Leonard Greybc2b3832018-09-19 15:15:21236}
237
238TEST_F(CSSAgentTest, SettingY) {
239 DCHECK_EQ(element()->bounds().y(), 0);
240
241 auto result = SetStyle("y: 100", element()->node_id());
242 EXPECT_TRUE(result.first);
243 EXPECT_EQ(element()->bounds().y(), 100);
Kristyn Hamasaki99877ab2019-07-12 17:02:25244 EXPECT_EQ(result.second->size(), 1U);
245 std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0);
Leonard Greybc2b3832018-09-19 15:15:21246 EXPECT_EQ(style->getStyleSheetId("default"),
Kristyn Hamasaki99877ab2019-07-12 17:02:25247 base::NumberToString(element()->node_id()) + "_0");
248 EXPECT_EQ(GetValueForProperty(style.get(), "y"), "100");
Leonard Greybc2b3832018-09-19 15:15:21249}
250TEST_F(CSSAgentTest, SettingWidth) {
251 DCHECK_EQ(element()->bounds().width(), 0);
252
253 auto result = SetStyle("width: 20", element()->node_id());
254 EXPECT_TRUE(result.first);
255 EXPECT_EQ(element()->bounds().width(), 20);
Kristyn Hamasaki99877ab2019-07-12 17:02:25256 EXPECT_EQ(result.second->size(), 1U);
257 std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0);
Leonard Greybc2b3832018-09-19 15:15:21258 EXPECT_EQ(style->getStyleSheetId("default"),
Kristyn Hamasaki99877ab2019-07-12 17:02:25259 base::NumberToString(element()->node_id()) + "_0");
260 EXPECT_EQ(GetValueForProperty(style.get(), "width"), "20");
Leonard Greybc2b3832018-09-19 15:15:21261}
262TEST_F(CSSAgentTest, SettingHeight) {
263 DCHECK_EQ(element()->bounds().height(), 0);
264
265 auto result = SetStyle("height: 30", element()->node_id());
266 EXPECT_TRUE(result.first);
267 EXPECT_EQ(element()->bounds().height(), 30);
Kristyn Hamasaki99877ab2019-07-12 17:02:25268 EXPECT_EQ(result.second->size(), 1U);
269 std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0);
Leonard Greybc2b3832018-09-19 15:15:21270 EXPECT_EQ(style->getStyleSheetId("default"),
Kristyn Hamasaki99877ab2019-07-12 17:02:25271 base::NumberToString(element()->node_id()) + "_0");
272 EXPECT_EQ(GetValueForProperty(style.get(), "height"), "30");
Leonard Greybc2b3832018-09-19 15:15:21273}
274
275TEST_F(CSSAgentTest, SettingAll) {
276 DCHECK(element()->bounds() == gfx::Rect());
Kristyn Hamasaki99877ab2019-07-12 17:02:25277 element()->SetVisible(true);
Leonard Greybc2b3832018-09-19 15:15:21278 DCHECK(element()->visible());
279
280 // Throw in odd whitespace while we're at it.
281 std::string new_text(
282 "\ny: 25; width: 50; visibility:0; height: 30;\nx: 9000;\n\n");
283 auto result = SetStyle(new_text, element()->node_id());
284 EXPECT_TRUE(result.first);
285 EXPECT_EQ(element()->bounds(), gfx::Rect(9000, 25, 50, 30));
286 EXPECT_FALSE(element()->visible());
Kristyn Hamasaki99877ab2019-07-12 17:02:25287 EXPECT_EQ(result.second->size(), 1U);
288 std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0);
Leonard Greybc2b3832018-09-19 15:15:21289 EXPECT_EQ(style->getStyleSheetId("default"),
Kristyn Hamasaki99877ab2019-07-12 17:02:25290 base::NumberToString(element()->node_id()) + "_0");
291 EXPECT_EQ(GetValueForProperty(style.get(), "x"), "9000");
292 EXPECT_EQ(GetValueForProperty(style.get(), "y"), "25");
293 EXPECT_EQ(GetValueForProperty(style.get(), "width"), "50");
294 EXPECT_EQ(GetValueForProperty(style.get(), "height"), "30");
295 EXPECT_EQ(GetValueForProperty(style.get(), "visibility"), "false");
Leonard Greybc2b3832018-09-19 15:15:21296}
297
298TEST_F(CSSAgentTest, UpdateOnBoundsChange) {
299 FakeUIElement another_element(dom_agent());
Kristyn Hamasaki99877ab2019-07-12 17:02:25300 another_element.SetBaseStylesheetId(0);
301 std::string element_stylesheet_id =
302 BuildStylesheetUId(element()->node_id(), 0);
303 std::string another_element_stylesheet_id =
304 BuildStylesheetUId(another_element.node_id(), 0);
305
306 EXPECT_EQ(0, GetStyleSheetChangedCount(element_stylesheet_id));
307 EXPECT_EQ(0, GetStyleSheetChangedCount(another_element_stylesheet_id));
Leonard Greybc2b3832018-09-19 15:15:21308 css_agent()->OnElementBoundsChanged(element());
Kristyn Hamasaki99877ab2019-07-12 17:02:25309 EXPECT_EQ(1, GetStyleSheetChangedCount(element_stylesheet_id));
310 EXPECT_EQ(0, GetStyleSheetChangedCount(another_element_stylesheet_id));
Leonard Greybc2b3832018-09-19 15:15:21311 css_agent()->OnElementBoundsChanged(&another_element);
Kristyn Hamasaki99877ab2019-07-12 17:02:25312 EXPECT_EQ(1, GetStyleSheetChangedCount(element_stylesheet_id));
313 EXPECT_EQ(1, GetStyleSheetChangedCount(another_element_stylesheet_id));
Leonard Greybc2b3832018-09-19 15:15:21314
315 css_agent()->OnElementBoundsChanged(&another_element);
Kristyn Hamasaki99877ab2019-07-12 17:02:25316 EXPECT_EQ(1, GetStyleSheetChangedCount(element_stylesheet_id));
317 EXPECT_EQ(2, GetStyleSheetChangedCount(another_element_stylesheet_id));
Leonard Greybc2b3832018-09-19 15:15:21318}
Kristyn Hamasaki99877ab2019-07-12 17:02:25319
Kristyn Hamasaki52898792019-08-16 18:30:02320TEST_F(CSSAgentTest, GetSource) {
321 // Tests CSSAgent::getStyleSheetText() with one source file.
Kristyn Hamasaki4de3d0a12019-08-12 19:31:42322 std::string file = "components/test/data/ui_devtools/test_file.cc";
323 element()->AddSource(file, 0);
324 auto result = GetSourceForElement();
325
Kristyn Hamasaki52898792019-08-16 18:30:02326 // Ensure that test_file.cc was read in correctly.
Kristyn Hamasaki4de3d0a12019-08-12 19:31:42327 EXPECT_TRUE(result.first);
Kristyn Hamasaki52898792019-08-16 18:30:02328 EXPECT_NE(std::string::npos,
329 result.second.find("This file is for testing GetSource."));
Kristyn Hamasaki4de3d0a12019-08-12 19:31:42330}
331
332TEST_F(CSSAgentTest, BadPathFails) {
333 element()->AddSource("not/a/real/path", 0);
334 auto result = GetSourceForElement();
335
336 EXPECT_FALSE(result.first);
337 EXPECT_EQ(result.second, "");
338}
339
Leonard Greybc2b3832018-09-19 15:15:21340} // namespace ui_devtools