blob: 675103f1918141eccbc42087d632673ce507882c [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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_util.h"
#include "base/strings/stringprintf.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 FakeUIElement : public UIElement {
public:
FakeUIElement(UIElementDelegate* ui_element_delegate)
: UIElement(UIElementType::ROOT, ui_element_delegate, nullptr) {}
~FakeUIElement() override {}
// Return a vector of pairs of properties' names and values.
std::vector<std::pair<std::string, std::string>> GetCustomProperties()
const override {
return {};
}
void GetBounds(gfx::Rect* bounds) const override { *bounds = bounds_; }
void SetBounds(const gfx::Rect& bounds) override { bounds_ = bounds; }
void GetVisible(bool* visible) const override { *visible = visible_; }
void SetVisible(bool visible) override { visible_ = visible; }
bool visible() const { return visible_; }
gfx::Rect bounds() const { return bounds_; }
std::pair<gfx::NativeWindow, gfx::Rect> GetNodeWindowAndBounds()
const override {
return {nullptr, gfx::Rect()};
}
std::unique_ptr<protocol::Array<std::string>> GetAttributes() const override {
return protocol::Array<std::string>::create();
}
private:
gfx::Rect bounds_;
bool visible_;
};
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());
dom_agent_->OnUIElementAdded(nullptr, element_.get());
}
protected:
using StyleArray = protocol::Array<protocol::CSS::CSSStyle>;
std::pair<bool, std::unique_ptr<StyleArray>> SetStyle(
const std::string& style_text,
int node_id) {
auto edits = protocol::Array<protocol::CSS::StyleDeclarationEdit>::create();
auto edit = protocol::CSS::StyleDeclarationEdit::create()
.setStyleSheetId(base::NumberToString(node_id))
.setRange(protocol::CSS::SourceRange::create()
.setStartLine(0)
.setStartColumn(0)
.setEndLine(0)
.setEndColumn(0)
.build())
.setText(style_text)
.build();
edits->addItem(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) {
auto* properties = style->getCssProperties();
for (size_t i = 0; i < properties->length(); ++i) {
auto* property = properties->get(i);
if (property->getName() == property_name) {
return property->getValue();
}
}
return std::string();
}
int GetStyleSheetChangedCount(int node_id) {
return frontend_channel_->CountProtocolNotificationMessage(
base::StringPrintf("{\"method\":\"CSS.styleSheetChanged\",\"params\":{"
"\"styleSheetId\":\"%d\"}}",
node_id));
}
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, 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->length(), 1U);
protocol::CSS::CSSStyle* style = result.second->get(0);
EXPECT_EQ(style->getStyleSheetId("default"),
base::NumberToString(element()->node_id()));
EXPECT_EQ(GetValueForProperty(style, "visibility"), "1");
}
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->length(), 1U);
protocol::CSS::CSSStyle* style = result.second->get(0);
EXPECT_EQ(style->getStyleSheetId("default"),
base::NumberToString(element()->node_id()));
EXPECT_EQ(GetValueForProperty(style, "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->length(), 1U);
protocol::CSS::CSSStyle* style = result.second->get(0);
EXPECT_EQ(style->getStyleSheetId("default"),
base::NumberToString(element()->node_id()));
EXPECT_EQ(GetValueForProperty(style, "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->length(), 1U);
protocol::CSS::CSSStyle* style = result.second->get(0);
EXPECT_EQ(style->getStyleSheetId("default"),
base::NumberToString(element()->node_id()));
EXPECT_EQ(GetValueForProperty(style, "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->length(), 1U);
protocol::CSS::CSSStyle* style = result.second->get(0);
EXPECT_EQ(style->getStyleSheetId("default"),
base::NumberToString(element()->node_id()));
EXPECT_EQ(GetValueForProperty(style, "height"), "30");
}
TEST_F(CSSAgentTest, SettingAll) {
DCHECK(element()->bounds() == gfx::Rect());
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->length(), 1U);
protocol::CSS::CSSStyle* style = result.second->get(0);
EXPECT_EQ(style->getStyleSheetId("default"),
base::NumberToString(element()->node_id()));
EXPECT_EQ(GetValueForProperty(style, "x"), "9000");
EXPECT_EQ(GetValueForProperty(style, "y"), "25");
EXPECT_EQ(GetValueForProperty(style, "width"), "50");
EXPECT_EQ(GetValueForProperty(style, "height"), "30");
EXPECT_EQ(GetValueForProperty(style, "visibility"), "0");
}
TEST_F(CSSAgentTest, UpdateOnBoundsChange) {
FakeUIElement another_element(dom_agent());
EXPECT_EQ(0, GetStyleSheetChangedCount(element()->node_id()));
EXPECT_EQ(0, GetStyleSheetChangedCount(another_element.node_id()));
css_agent()->OnElementBoundsChanged(element());
EXPECT_EQ(1, GetStyleSheetChangedCount(element()->node_id()));
EXPECT_EQ(0, GetStyleSheetChangedCount(another_element.node_id()));
css_agent()->OnElementBoundsChanged(&another_element);
EXPECT_EQ(1, GetStyleSheetChangedCount(element()->node_id()));
EXPECT_EQ(1, GetStyleSheetChangedCount(another_element.node_id()));
css_agent()->OnElementBoundsChanged(&another_element);
EXPECT_EQ(1, GetStyleSheetChangedCount(element()->node_id()));
EXPECT_EQ(2, GetStyleSheetChangedCount(another_element.node_id()));
}
} // namespace ui_devtools