| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/ui_devtools/css_agent.h" |
| |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/test/task_environment.h" |
| #include "components/ui_devtools/agent_util.h" |
| #include "components/ui_devtools/dom_agent.h" |
| #include "components/ui_devtools/ui_devtools_unittest_utils.h" |
| #include "components/ui_devtools/ui_element.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace ui_devtools { |
| |
| class FakeDOMAgent : public DOMAgent { |
| public: |
| void OnUIElementAdded(UIElement* parent, UIElement* child) override { |
| // nullptr root short circuits everything but adding |child| |
| // to the node ID map, which is all we need here. |
| DOMAgent::OnUIElementAdded(nullptr, child); |
| } |
| |
| std::unique_ptr<protocol::DOM::Node> BuildTreeForUIElement( |
| UIElement* root) override { |
| return BuildDomNodeFromUIElement(root); |
| } |
| |
| std::vector<UIElement*> CreateChildrenForRoot() override { return {}; } |
| }; |
| |
| class CSSAgentTest : public testing::Test { |
| public: |
| void SetUp() override { |
| testing::Test::SetUp(); |
| frontend_channel_ = std::make_unique<FakeFrontendChannel>(); |
| uber_dispatcher_ = |
| std::make_unique<protocol::UberDispatcher>(frontend_channel_.get()); |
| dom_agent_ = std::make_unique<FakeDOMAgent>(); |
| dom_agent_->Init(uber_dispatcher_.get()); |
| css_agent_ = std::make_unique<CSSAgent>(dom_agent_.get()); |
| css_agent_->Init(uber_dispatcher_.get()); |
| css_agent_->enable(); |
| element_ = std::make_unique<FakeUIElement>(dom_agent_.get()); |
| element()->SetBaseStylesheetId(0); |
| dom_agent_->OnUIElementAdded(nullptr, element_.get()); |
| } |
| |
| void TearDown() override { |
| css_agent_.reset(); |
| dom_agent_.reset(); |
| uber_dispatcher_.reset(); |
| frontend_channel_.reset(); |
| element_.reset(); |
| testing::Test::TearDown(); |
| } |
| |
| std::string BuildStylesheetUId(int node_id, int stylesheet_id) { |
| return base::NumberToString(node_id) + "_" + |
| base::NumberToString(stylesheet_id); |
| } |
| |
| protected: |
| base::test::TaskEnvironment task_environment_; |
| using StyleArray = protocol::Array<protocol::CSS::CSSStyle>; |
| |
| std::pair<bool, std::unique_ptr<StyleArray>> |
| SetStyle(const std::string& style_text, int node_id, int stylesheet_id = 0) { |
| auto edits = std::make_unique< |
| protocol::Array<protocol::CSS::StyleDeclarationEdit>>(); |
| auto edit = protocol::CSS::StyleDeclarationEdit::create() |
| .setStyleSheetId(BuildStylesheetUId(node_id, stylesheet_id)) |
| .setRange(protocol::CSS::SourceRange::create() |
| .setStartLine(0) |
| .setStartColumn(0) |
| .setEndLine(0) |
| .setEndColumn(0) |
| .build()) |
| .setText(style_text) |
| .build(); |
| edits->emplace_back(std::move(edit)); |
| std::unique_ptr<StyleArray> output; |
| auto response = css_agent_->setStyleTexts(std::move(edits), &output); |
| return {response.IsSuccess(), std::move(output)}; |
| } |
| |
| std::string GetValueForProperty(protocol::CSS::CSSStyle* style, |
| const std::string& property_name) { |
| for (const auto& property : *(style->getCssProperties())) { |
| if (property->getName() == property_name) { |
| return property->getValue(); |
| } |
| } |
| return std::string(); |
| } |
| |
| int GetStyleSheetChangedCount(std::string stylesheet_id) { |
| return frontend_channel_->CountProtocolNotificationMessage( |
| "{\"method\":\"CSS.styleSheetChanged\",\"params\":{" |
| "\"styleSheetId\":\"" + |
| stylesheet_id + "\"}}"); |
| } |
| |
| std::pair<bool, std::string> GetSourceForElement() { |
| std::string output; |
| auto response = css_agent_->getStyleSheetText( |
| BuildStylesheetUId(element()->node_id(), 0), &output); |
| return {response.IsSuccess(), output}; |
| } |
| |
| CSSAgent* css_agent() { return css_agent_.get(); } |
| DOMAgent* dom_agent() { return dom_agent_.get(); } |
| FakeUIElement* element() { return element_.get(); } |
| |
| private: |
| std::unique_ptr<CSSAgent> css_agent_; |
| std::unique_ptr<DOMAgent> dom_agent_; |
| std::unique_ptr<FakeFrontendChannel> frontend_channel_; |
| std::unique_ptr<protocol::UberDispatcher> uber_dispatcher_; |
| std::unique_ptr<FakeUIElement> element_; |
| }; |
| |
| TEST_F(CSSAgentTest, UnrecognizedNodeFails) { |
| EXPECT_FALSE(SetStyle("x : 25", 42).first); |
| } |
| |
| TEST_F(CSSAgentTest, UnrecognizedKeyFails) { |
| element()->SetVisible(true); |
| element()->SetBounds(gfx::Rect(1, 2, 3, 4)); |
| |
| auto result = SetStyle("nonsense : 3.14", element()->node_id()); |
| |
| EXPECT_FALSE(result.first); |
| EXPECT_FALSE(result.second); |
| // Ensure element didn't change. |
| EXPECT_TRUE(element()->visible()); |
| EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4)); |
| } |
| |
| TEST_F(CSSAgentTest, UnrecognizedValueFails) { |
| element()->SetVisible(true); |
| element()->SetBounds(gfx::Rect(1, 2, 3, 4)); |
| |
| auto result = SetStyle("visibility : banana", element()->node_id()); |
| EXPECT_FALSE(result.first); |
| EXPECT_FALSE(result.second); |
| // Ensure element didn't change. |
| EXPECT_TRUE(element()->visible()); |
| EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4)); |
| } |
| |
| TEST_F(CSSAgentTest, BadInputsFail) { |
| element()->SetVisible(true); |
| element()->SetBounds(gfx::Rect(1, 2, 3, 4)); |
| |
| // Input with no property name. |
| auto result = SetStyle(": 1;", element()->node_id()); |
| EXPECT_FALSE(result.first); |
| EXPECT_FALSE(result.second); |
| // Ensure element didn't change. |
| EXPECT_TRUE(element()->visible()); |
| EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4)); |
| |
| // Input with no property value. |
| result = SetStyle("visibility:", element()->node_id()); |
| EXPECT_FALSE(result.first); |
| EXPECT_FALSE(result.second); |
| // Ensure element didn't change. |
| EXPECT_TRUE(element()->visible()); |
| EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4)); |
| |
| // Blank input. |
| result = SetStyle(":", element()->node_id()); |
| EXPECT_FALSE(result.first); |
| EXPECT_FALSE(result.second); |
| // Ensure element didn't change. |
| EXPECT_TRUE(element()->visible()); |
| EXPECT_EQ(element()->bounds(), gfx::Rect(1, 2, 3, 4)); |
| } |
| |
| TEST_F(CSSAgentTest, SettingVisibility) { |
| element()->SetVisible(false); |
| DCHECK(!element()->visible()); |
| |
| auto result = SetStyle("visibility: 1", element()->node_id()); |
| EXPECT_TRUE(result.first); |
| EXPECT_TRUE(element()->visible()); |
| |
| EXPECT_EQ(result.second->size(), 1U); |
| std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0); |
| EXPECT_EQ(style->getStyleSheetId("default"), |
| base::NumberToString(element()->node_id()) + "_0"); |
| EXPECT_EQ(GetValueForProperty(style.get(), "visibility"), "true"); |
| } |
| |
| TEST_F(CSSAgentTest, SettingX) { |
| DCHECK_EQ(element()->bounds().x(), 0); |
| |
| auto result = SetStyle("x: 500", element()->node_id()); |
| EXPECT_TRUE(result.first); |
| EXPECT_EQ(element()->bounds().x(), 500); |
| EXPECT_EQ(result.second->size(), 1U); |
| std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0); |
| EXPECT_EQ(style->getStyleSheetId("default"), |
| base::NumberToString(element()->node_id()) + "_0"); |
| EXPECT_EQ(GetValueForProperty(style.get(), "x"), "500"); |
| } |
| |
| TEST_F(CSSAgentTest, SettingY) { |
| DCHECK_EQ(element()->bounds().y(), 0); |
| |
| auto result = SetStyle("y: 100", element()->node_id()); |
| EXPECT_TRUE(result.first); |
| EXPECT_EQ(element()->bounds().y(), 100); |
| EXPECT_EQ(result.second->size(), 1U); |
| std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0); |
| EXPECT_EQ(style->getStyleSheetId("default"), |
| base::NumberToString(element()->node_id()) + "_0"); |
| EXPECT_EQ(GetValueForProperty(style.get(), "y"), "100"); |
| } |
| TEST_F(CSSAgentTest, SettingWidth) { |
| DCHECK_EQ(element()->bounds().width(), 0); |
| |
| auto result = SetStyle("width: 20", element()->node_id()); |
| EXPECT_TRUE(result.first); |
| EXPECT_EQ(element()->bounds().width(), 20); |
| EXPECT_EQ(result.second->size(), 1U); |
| std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0); |
| EXPECT_EQ(style->getStyleSheetId("default"), |
| base::NumberToString(element()->node_id()) + "_0"); |
| EXPECT_EQ(GetValueForProperty(style.get(), "width"), "20"); |
| } |
| TEST_F(CSSAgentTest, SettingHeight) { |
| DCHECK_EQ(element()->bounds().height(), 0); |
| |
| auto result = SetStyle("height: 30", element()->node_id()); |
| EXPECT_TRUE(result.first); |
| EXPECT_EQ(element()->bounds().height(), 30); |
| EXPECT_EQ(result.second->size(), 1U); |
| std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0); |
| EXPECT_EQ(style->getStyleSheetId("default"), |
| base::NumberToString(element()->node_id()) + "_0"); |
| EXPECT_EQ(GetValueForProperty(style.get(), "height"), "30"); |
| } |
| |
| TEST_F(CSSAgentTest, SettingAll) { |
| DCHECK(element()->bounds() == gfx::Rect()); |
| element()->SetVisible(true); |
| DCHECK(element()->visible()); |
| |
| // Throw in odd whitespace while we're at it. |
| std::string new_text( |
| "\ny: 25; width: 50; visibility:0; height: 30;\nx: 9000;\n\n"); |
| auto result = SetStyle(new_text, element()->node_id()); |
| EXPECT_TRUE(result.first); |
| EXPECT_EQ(element()->bounds(), gfx::Rect(9000, 25, 50, 30)); |
| EXPECT_FALSE(element()->visible()); |
| EXPECT_EQ(result.second->size(), 1U); |
| std::unique_ptr<protocol::CSS::CSSStyle>& style = result.second->at(0); |
| EXPECT_EQ(style->getStyleSheetId("default"), |
| base::NumberToString(element()->node_id()) + "_0"); |
| EXPECT_EQ(GetValueForProperty(style.get(), "x"), "9000"); |
| EXPECT_EQ(GetValueForProperty(style.get(), "y"), "25"); |
| EXPECT_EQ(GetValueForProperty(style.get(), "width"), "50"); |
| EXPECT_EQ(GetValueForProperty(style.get(), "height"), "30"); |
| EXPECT_EQ(GetValueForProperty(style.get(), "visibility"), "false"); |
| } |
| |
| TEST_F(CSSAgentTest, UpdateOnBoundsChange) { |
| FakeUIElement another_element(dom_agent()); |
| another_element.SetBaseStylesheetId(0); |
| std::string element_stylesheet_id = |
| BuildStylesheetUId(element()->node_id(), 0); |
| std::string another_element_stylesheet_id = |
| BuildStylesheetUId(another_element.node_id(), 0); |
| |
| EXPECT_EQ(0, GetStyleSheetChangedCount(element_stylesheet_id)); |
| EXPECT_EQ(0, GetStyleSheetChangedCount(another_element_stylesheet_id)); |
| css_agent()->OnElementBoundsChanged(element()); |
| EXPECT_EQ(1, GetStyleSheetChangedCount(element_stylesheet_id)); |
| EXPECT_EQ(0, GetStyleSheetChangedCount(another_element_stylesheet_id)); |
| css_agent()->OnElementBoundsChanged(&another_element); |
| EXPECT_EQ(1, GetStyleSheetChangedCount(element_stylesheet_id)); |
| EXPECT_EQ(1, GetStyleSheetChangedCount(another_element_stylesheet_id)); |
| |
| css_agent()->OnElementBoundsChanged(&another_element); |
| EXPECT_EQ(1, GetStyleSheetChangedCount(element_stylesheet_id)); |
| EXPECT_EQ(2, GetStyleSheetChangedCount(another_element_stylesheet_id)); |
| } |
| |
| TEST_F(CSSAgentTest, GetSource) { |
| // Tests CSSAgent::getStyleSheetText() with one source file. |
| std::string file = "components/test/data/ui_devtools/test_file.cc"; |
| element()->AddSource(file, 0); |
| auto result = GetSourceForElement(); |
| |
| // Ensure that test_file.cc was read in correctly. |
| EXPECT_TRUE(result.first); |
| EXPECT_NE(std::string::npos, |
| result.second.find("This file is for testing GetSource.")); |
| } |
| |
| TEST_F(CSSAgentTest, BadPathFails) { |
| element()->AddSource("not/a/real/path", 0); |
| auto result = GetSourceForElement(); |
| |
| EXPECT_FALSE(result.first); |
| EXPECT_EQ(result.second, ""); |
| } |
| |
| } // namespace ui_devtools |