| // Copyright 2016 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_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/to_string.h" |
| #include "components/ui_devtools/agent_util.h" |
| #include "components/ui_devtools/ui_element.h" |
| |
| namespace ui_devtools { |
| |
| namespace CSS = protocol::CSS; |
| using protocol::Array; |
| using protocol::Response; |
| |
| namespace { |
| |
| const char kHeight[] = "height"; |
| const char kWidth[] = "width"; |
| const char kX[] = "x"; |
| const char kY[] = "y"; |
| const char kVisibility[] = "visibility"; |
| |
| std::unique_ptr<CSS::SourceRange> BuildDefaultPropertySourceRange() { |
| // These tell the frontend where in the stylesheet a certain style |
| // is located. Since we don't have stylesheets, this is all 0. |
| // We need this because CSS fields are not editable unless |
| // the range is provided. |
| return CSS::SourceRange::create() |
| .setStartLine(0) |
| .setEndLine(0) |
| .setStartColumn(0) |
| .setEndColumn(0) |
| .build(); |
| } |
| |
| std::unique_ptr<CSS::SourceRange> BuildDefaultSelectorSourceRange() { |
| // This is a different source range from BuildDefaultPropertySourceRange() |
| // used for the Selectors, so the frontend correctly handles property edits. |
| return CSS::SourceRange::create() |
| .setStartLine(1) |
| .setEndLine(0) |
| .setStartColumn(0) |
| .setEndColumn(0) |
| .build(); |
| } |
| |
| std::unique_ptr<Array<int>> BuildDefaultMatchingSelectors() { |
| auto matching_selectors = std::make_unique<Array<int>>(); |
| |
| // Add index 0 to matching selectors array, so frontend uses the class name |
| // from the selectors array as the header for the properties section |
| matching_selectors->emplace_back(0); |
| return matching_selectors; |
| } |
| |
| std::unique_ptr<CSS::CSSProperty> BuildCSSProperty(const std::string& name, |
| const std::string& value) { |
| return CSS::CSSProperty::create() |
| .setRange(BuildDefaultPropertySourceRange()) |
| .setName(name) |
| .setValue(value) |
| .build(); |
| } |
| |
| std::unique_ptr<Array<CSS::CSSProperty>> BuildCSSProperties( |
| const std::vector<UIElement::UIProperty>& properties_vector) { |
| auto css_properties = std::make_unique<Array<CSS::CSSProperty>>(); |
| for (const auto& property : properties_vector) { |
| css_properties->emplace_back( |
| BuildCSSProperty(property.name_, property.value_)); |
| } |
| return css_properties; |
| } |
| |
| std::unique_ptr<CSS::CSSStyle> BuildCSSStyle( |
| std::string stylesheet_uid, |
| const std::vector<UIElement::UIProperty>& properties) { |
| return CSS::CSSStyle::create() |
| .setRange(BuildDefaultPropertySourceRange()) |
| .setCssProperties(BuildCSSProperties(properties)) |
| .setShorthandEntries(std::make_unique<Array<CSS::ShorthandEntry>>()) |
| .setStyleSheetId(stylesheet_uid) |
| .build(); |
| } |
| |
| std::unique_ptr<Array<CSS::Value>> BuildSelectors(const std::string& name) { |
| auto selectors = std::make_unique<Array<CSS::Value>>(); |
| selectors->emplace_back(CSS::Value::create() |
| .setText(name) |
| .setRange(BuildDefaultSelectorSourceRange()) |
| .build()); |
| return selectors; |
| } |
| |
| std::unique_ptr<CSS::SelectorList> BuildSelectorList(const std::string& name) { |
| return CSS::SelectorList::create().setSelectors(BuildSelectors(name)).build(); |
| } |
| |
| std::unique_ptr<CSS::CSSRule> BuildCSSRule( |
| std::string stylesheet_uid, |
| const UIElement::ClassProperties& class_properties) { |
| return CSS::CSSRule::create() |
| .setStyleSheetId(stylesheet_uid) |
| .setSelectorList(BuildSelectorList(class_properties.class_name_)) |
| .setStyle(BuildCSSStyle(stylesheet_uid, class_properties.properties_)) |
| .build(); |
| } |
| |
| std::vector<UIElement::ClassProperties> GetClassPropertiesWithBounds( |
| UIElement* ui_element) { |
| std::vector<UIElement::ClassProperties> properties_vector = |
| ui_element->GetCustomPropertiesForMatchedStyle(); |
| |
| // If GetCustomPropertiesForMatchedStyle not overridden to return custom |
| // properties, populate vector with bounds properties. |
| if (properties_vector.empty()) { |
| gfx::Rect bounds; |
| ui_element->GetBounds(&bounds); |
| std::vector<UIElement::UIProperty> bound_properties; |
| bound_properties.emplace_back(kX, base::NumberToString(bounds.x())); |
| bound_properties.emplace_back(kY, base::NumberToString(bounds.y())); |
| bound_properties.emplace_back(kWidth, base::NumberToString(bounds.width())); |
| bound_properties.emplace_back(kHeight, |
| base::NumberToString(bounds.height())); |
| if (ui_element->type() != VIEW) { |
| bool visible; |
| ui_element->GetVisible(&visible); |
| bound_properties.emplace_back(kVisibility, base::ToString(visible)); |
| } |
| properties_vector.emplace_back(ui_element->GetTypeName(), bound_properties); |
| } |
| |
| // Set base stylesheet ID to the last index in the vector, so when bounds |
| // properties are modified, CSSAgent can update and return the right |
| // properties section |
| ui_element->SetBaseStylesheetId(properties_vector.size() - 1); |
| return properties_vector; |
| } |
| |
| std::string BuildStylesheetUId(int node_id, int stylesheet_id) { |
| return base::NumberToString(node_id) + "_" + |
| base::NumberToString(stylesheet_id); |
| } |
| |
| Response NodeNotFoundError(int node_id) { |
| return Response::ServerError("Node with id=" + base::NumberToString(node_id) + |
| " not found"); |
| } |
| |
| Response ParseProperties(const std::string& style_text, |
| gfx::Rect* bounds, |
| bool* visible) { |
| std::vector<std::string> tokens = base::SplitString( |
| style_text, ":;", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| |
| if (tokens.size() < 2 || tokens.size() % 2 != 0) |
| return Response::ServerError("Need both a property name and value."); |
| |
| for (size_t i = 0; i < tokens.size() - 1; i += 2) { |
| const std::string& property = tokens.at(i); |
| int value; |
| if (!base::StringToInt(tokens.at(i + 1), &value)) { |
| return Response::ServerError("Unable to parse value for property=" + |
| property); |
| } |
| |
| if (property == kHeight) |
| bounds->set_height(std::max(0, value)); |
| else if (property == kWidth) |
| bounds->set_width(std::max(0, value)); |
| else if (property == kX) |
| bounds->set_x(value); |
| else if (property == kY) |
| bounds->set_y(value); |
| else if (property == kVisibility) |
| *visible = std::max(0, value) == 1; |
| else |
| return Response::ServerError("Unsupported property=" + property); |
| } |
| return Response::Success(); |
| } |
| |
| std::unique_ptr<CSS::CSSStyleSheetHeader> BuildObjectForStyleSheetInfo( |
| std::string stylesheet_uid, |
| std::string url_path, |
| int line) { |
| std::unique_ptr<CSS::CSSStyleSheetHeader> result = |
| CSS::CSSStyleSheetHeader::create() |
| .setStyleSheetId(stylesheet_uid) |
| .setSourceURL(kChromiumCodeSearchSrcURL + url_path + |
| "?l=" + base::NumberToString(line)) |
| .setStartLine(line) |
| .setStartColumn(0) |
| .build(); |
| return result; |
| } |
| |
| } // namespace |
| |
| CSSAgent::CSSAgent(DOMAgent* dom_agent) : dom_agent_(dom_agent) { |
| DCHECK(dom_agent_); |
| } |
| |
| CSSAgent::~CSSAgent() { |
| disable(); |
| } |
| |
| Response CSSAgent::enable() { |
| dom_agent_->AddObserver(this); |
| return Response::Success(); |
| } |
| |
| Response CSSAgent::disable() { |
| dom_agent_->RemoveObserver(this); |
| return Response::Success(); |
| } |
| |
| Response CSSAgent::getMatchedStylesForNode( |
| int node_id, |
| std::unique_ptr<Array<CSS::RuleMatch>>* matched_css_rules) { |
| UIElement* ui_element = dom_agent_->GetElementFromNodeId(node_id); |
| if (!ui_element) |
| return NodeNotFoundError(node_id); |
| *matched_css_rules = BuildMatchedStyles(ui_element); |
| return Response::Success(); |
| } |
| |
| Response CSSAgent::getStyleSheetText(const protocol::String& style_sheet_id, |
| protocol::String* result) { |
| int node_id; |
| int stylesheet_id; |
| std::vector<std::string> ids = base::SplitString( |
| style_sheet_id, "_", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| if (ids.size() < 2 || !base::StringToInt(ids[0], &node_id) || |
| !base::StringToInt(ids[1], &stylesheet_id)) |
| return Response::ServerError("Invalid stylesheet id"); |
| |
| UIElement* ui_element = dom_agent_->GetElementFromNodeId(node_id); |
| if (!ui_element) |
| return Response::ServerError("Node id not found"); |
| |
| auto sources = ui_element->GetSources(); |
| if (static_cast<int>(sources.size()) <= stylesheet_id) |
| return Response::ServerError("Stylesheet id not found"); |
| |
| if (GetSourceCode(sources[stylesheet_id].path_, result)) |
| return Response::Success(); |
| return Response::ServerError("Could not read source file"); |
| } |
| |
| Response CSSAgent::setStyleTexts( |
| std::unique_ptr<Array<CSS::StyleDeclarationEdit>> edits, |
| std::unique_ptr<Array<CSS::CSSStyle>>* result) { |
| auto updated_styles = std::make_unique<Array<CSS::CSSStyle>>(); |
| for (const auto& edit : *edits) { |
| int node_id; |
| int stylesheet_id; |
| |
| std::vector<std::string> ids = |
| base::SplitString(edit->getStyleSheetId(), "_", base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY); |
| if (ids.size() < 2 || !base::StringToInt(ids[0], &node_id) || |
| !base::StringToInt(ids[1], &stylesheet_id)) |
| return Response::ServerError("Invalid stylesheet id"); |
| |
| UIElement* ui_element = dom_agent_->GetElementFromNodeId(node_id); |
| |
| if (!ui_element) |
| return Response::ServerError("Node id not found"); |
| // Handle setting properties from metadata for elements which use metadata. |
| if (!ui_element->SetPropertiesFromString(edit->getText())) { |
| gfx::Rect updated_bounds; |
| bool visible = false; |
| if (!GetPropertiesForUIElement(ui_element, &updated_bounds, &visible)) |
| return NodeNotFoundError(node_id); |
| |
| Response response( |
| ParseProperties(edit->getText(), &updated_bounds, &visible)); |
| if (!response.IsSuccess()) |
| return response; |
| |
| if (!SetPropertiesForUIElement(ui_element, updated_bounds, visible)) |
| return NodeNotFoundError(node_id); |
| } |
| |
| updated_styles->emplace_back(BuildCSSStyle( |
| edit->getStyleSheetId(), GetClassPropertiesWithBounds(ui_element) |
| .at(stylesheet_id) |
| .properties_)); |
| } |
| *result = std::move(updated_styles); |
| return Response::Success(); |
| } |
| |
| void CSSAgent::OnElementBoundsChanged(UIElement* ui_element) { |
| InvalidateStyleSheet(ui_element); |
| } |
| |
| void CSSAgent::InvalidateStyleSheet(UIElement* ui_element) { |
| // The stylesheetId for each node is equivalent to a string of its |
| // node_id + "_" + index of CSS::RuleMatch in vector. |
| frontend()->styleSheetChanged(BuildStylesheetUId( |
| ui_element->node_id(), ui_element->GetBaseStylesheetId())); |
| } |
| |
| bool CSSAgent::GetPropertiesForUIElement(UIElement* ui_element, |
| gfx::Rect* bounds, |
| bool* visible) { |
| if (ui_element) { |
| ui_element->GetBounds(bounds); |
| if (ui_element->type() != VIEW) |
| ui_element->GetVisible(visible); |
| return true; |
| } |
| return false; |
| } |
| |
| bool CSSAgent::SetPropertiesForUIElement(UIElement* ui_element, |
| const gfx::Rect& bounds, |
| bool visible) { |
| if (ui_element) { |
| ui_element->SetBounds(bounds); |
| ui_element->SetVisible(visible); |
| return true; |
| } |
| return false; |
| } |
| |
| std::unique_ptr<Array<CSS::RuleMatch>> CSSAgent::BuildMatchedStyles( |
| UIElement* ui_element) { |
| auto result = std::make_unique<Array<CSS::RuleMatch>>(); |
| std::vector<UIElement::ClassProperties> properties_vector = |
| GetClassPropertiesWithBounds(ui_element); |
| |
| for (size_t i = 0; i < properties_vector.size(); i++) { |
| result->emplace_back( |
| CSS::RuleMatch::create() |
| .setRule(BuildCSSRule(BuildStylesheetUId(ui_element->node_id(), i), |
| properties_vector[i])) |
| .setMatchingSelectors(BuildDefaultMatchingSelectors()) |
| .build()); |
| } |
| if (!ui_element->header_sent()) { |
| InitStylesheetHeaders(ui_element); |
| } |
| return result; |
| } |
| |
| void CSSAgent::InitStylesheetHeaders(UIElement* ui_element) { |
| std::vector<UIElement::Source> sources = ui_element->GetSources(); |
| for (size_t i = 0; i < sources.size(); i++) { |
| frontend()->styleSheetAdded(BuildObjectForStyleSheetInfo( |
| BuildStylesheetUId(ui_element->node_id(), i), sources[i].path_, |
| sources[i].line_)); |
| } |
| ui_element->set_header_sent(); |
| } |
| |
| } // namespace ui_devtools |