| // 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 |