css_agent.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. // Copyright 2016 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. #include "components/ui_devtools/css_agent.h"
  5. #include "base/strings/string_number_conversions.h"
  6. #include "base/strings/string_split.h"
  7. #include "base/strings/string_util.h"
  8. #include "components/ui_devtools/agent_util.h"
  9. #include "components/ui_devtools/ui_element.h"
  10. namespace ui_devtools {
  11. namespace CSS = protocol::CSS;
  12. using protocol::Array;
  13. using protocol::Response;
  14. namespace {
  15. const char kHeight[] = "height";
  16. const char kWidth[] = "width";
  17. const char kX[] = "x";
  18. const char kY[] = "y";
  19. const char kVisibility[] = "visibility";
  20. std::unique_ptr<CSS::SourceRange> BuildDefaultPropertySourceRange() {
  21. // These tell the frontend where in the stylesheet a certain style
  22. // is located. Since we don't have stylesheets, this is all 0.
  23. // We need this because CSS fields are not editable unless
  24. // the range is provided.
  25. return CSS::SourceRange::create()
  26. .setStartLine(0)
  27. .setEndLine(0)
  28. .setStartColumn(0)
  29. .setEndColumn(0)
  30. .build();
  31. }
  32. std::unique_ptr<CSS::SourceRange> BuildDefaultSelectorSourceRange() {
  33. // This is a different source range from BuildDefaultPropertySourceRange()
  34. // used for the Selectors, so the frontend correctly handles property edits.
  35. return CSS::SourceRange::create()
  36. .setStartLine(1)
  37. .setEndLine(0)
  38. .setStartColumn(0)
  39. .setEndColumn(0)
  40. .build();
  41. }
  42. std::unique_ptr<Array<int>> BuildDefaultMatchingSelectors() {
  43. auto matching_selectors = std::make_unique<Array<int>>();
  44. // Add index 0 to matching selectors array, so frontend uses the class name
  45. // from the selectors array as the header for the properties section
  46. matching_selectors->emplace_back(0);
  47. return matching_selectors;
  48. }
  49. std::unique_ptr<CSS::CSSProperty> BuildCSSProperty(const std::string& name,
  50. const std::string& value) {
  51. return CSS::CSSProperty::create()
  52. .setRange(BuildDefaultPropertySourceRange())
  53. .setName(name)
  54. .setValue(value)
  55. .build();
  56. }
  57. std::unique_ptr<Array<CSS::CSSProperty>> BuildCSSProperties(
  58. const std::vector<UIElement::UIProperty>& properties_vector) {
  59. auto css_properties = std::make_unique<Array<CSS::CSSProperty>>();
  60. for (const auto& property : properties_vector) {
  61. css_properties->emplace_back(
  62. BuildCSSProperty(property.name_, property.value_));
  63. }
  64. return css_properties;
  65. }
  66. std::unique_ptr<CSS::CSSStyle> BuildCSSStyle(
  67. std::string stylesheet_uid,
  68. const std::vector<UIElement::UIProperty>& properties) {
  69. return CSS::CSSStyle::create()
  70. .setRange(BuildDefaultPropertySourceRange())
  71. .setCssProperties(BuildCSSProperties(properties))
  72. .setShorthandEntries(std::make_unique<Array<CSS::ShorthandEntry>>())
  73. .setStyleSheetId(stylesheet_uid)
  74. .build();
  75. }
  76. std::unique_ptr<Array<CSS::Value>> BuildSelectors(const std::string& name) {
  77. auto selectors = std::make_unique<Array<CSS::Value>>();
  78. selectors->emplace_back(CSS::Value::create()
  79. .setText(name)
  80. .setRange(BuildDefaultSelectorSourceRange())
  81. .build());
  82. return selectors;
  83. }
  84. std::unique_ptr<CSS::SelectorList> BuildSelectorList(const std::string& name) {
  85. return CSS::SelectorList::create().setSelectors(BuildSelectors(name)).build();
  86. }
  87. std::unique_ptr<CSS::CSSRule> BuildCSSRule(
  88. std::string stylesheet_uid,
  89. const UIElement::ClassProperties& class_properties) {
  90. return CSS::CSSRule::create()
  91. .setStyleSheetId(stylesheet_uid)
  92. .setSelectorList(BuildSelectorList(class_properties.class_name_))
  93. .setStyle(BuildCSSStyle(stylesheet_uid, class_properties.properties_))
  94. .build();
  95. }
  96. std::vector<UIElement::ClassProperties> GetClassPropertiesWithBounds(
  97. UIElement* ui_element) {
  98. std::vector<UIElement::ClassProperties> properties_vector =
  99. ui_element->GetCustomPropertiesForMatchedStyle();
  100. // If GetCustomPropertiesForMatchedStyle not overridden to return custom
  101. // properties, populate vector with bounds properties.
  102. if (properties_vector.empty()) {
  103. gfx::Rect bounds;
  104. ui_element->GetBounds(&bounds);
  105. std::vector<UIElement::UIProperty> bound_properties;
  106. bound_properties.emplace_back(kX, base::NumberToString(bounds.x()));
  107. bound_properties.emplace_back(kY, base::NumberToString(bounds.y()));
  108. bound_properties.emplace_back(kWidth, base::NumberToString(bounds.width()));
  109. bound_properties.emplace_back(kHeight,
  110. base::NumberToString(bounds.height()));
  111. if (ui_element->type() != VIEW) {
  112. bool visible;
  113. ui_element->GetVisible(&visible);
  114. bound_properties.emplace_back(kVisibility, visible ? "true" : "false");
  115. }
  116. properties_vector.emplace_back(ui_element->GetTypeName(), bound_properties);
  117. }
  118. // Set base stylesheet ID to the last index in the vector, so when bounds
  119. // properties are modified, CSSAgent can update and return the right
  120. // properties section
  121. ui_element->SetBaseStylesheetId(properties_vector.size() - 1);
  122. return properties_vector;
  123. }
  124. std::string BuildStylesheetUId(int node_id, int stylesheet_id) {
  125. return base::NumberToString(node_id) + "_" +
  126. base::NumberToString(stylesheet_id);
  127. }
  128. Response NodeNotFoundError(int node_id) {
  129. return Response::ServerError("Node with id=" + base::NumberToString(node_id) +
  130. " not found");
  131. }
  132. Response ParseProperties(const std::string& style_text,
  133. gfx::Rect* bounds,
  134. bool* visible) {
  135. std::vector<std::string> tokens = base::SplitString(
  136. style_text, ":;", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  137. if (tokens.size() < 2 || tokens.size() % 2 != 0)
  138. return Response::ServerError("Need both a property name and value.");
  139. for (size_t i = 0; i < tokens.size() - 1; i += 2) {
  140. const std::string& property = tokens.at(i);
  141. int value;
  142. if (!base::StringToInt(tokens.at(i + 1), &value)) {
  143. return Response::ServerError("Unable to parse value for property=" +
  144. property);
  145. }
  146. if (property == kHeight)
  147. bounds->set_height(std::max(0, value));
  148. else if (property == kWidth)
  149. bounds->set_width(std::max(0, value));
  150. else if (property == kX)
  151. bounds->set_x(value);
  152. else if (property == kY)
  153. bounds->set_y(value);
  154. else if (property == kVisibility)
  155. *visible = std::max(0, value) == 1;
  156. else
  157. return Response::ServerError("Unsupported property=" + property);
  158. }
  159. return Response::Success();
  160. }
  161. std::unique_ptr<CSS::CSSStyleSheetHeader> BuildObjectForStyleSheetInfo(
  162. std::string stylesheet_uid,
  163. std::string url_path,
  164. int line) {
  165. std::unique_ptr<CSS::CSSStyleSheetHeader> result =
  166. CSS::CSSStyleSheetHeader::create()
  167. .setStyleSheetId(stylesheet_uid)
  168. .setSourceURL(kChromiumCodeSearchSrcURL + url_path +
  169. "?l=" + base::NumberToString(line))
  170. .setStartLine(line)
  171. .setStartColumn(0)
  172. .build();
  173. return result;
  174. }
  175. } // namespace
  176. CSSAgent::CSSAgent(DOMAgent* dom_agent) : dom_agent_(dom_agent) {
  177. DCHECK(dom_agent_);
  178. }
  179. CSSAgent::~CSSAgent() {
  180. disable();
  181. }
  182. Response CSSAgent::enable() {
  183. dom_agent_->AddObserver(this);
  184. return Response::Success();
  185. }
  186. Response CSSAgent::disable() {
  187. dom_agent_->RemoveObserver(this);
  188. return Response::Success();
  189. }
  190. Response CSSAgent::getMatchedStylesForNode(
  191. int node_id,
  192. protocol::Maybe<Array<CSS::RuleMatch>>* matched_css_rules) {
  193. UIElement* ui_element = dom_agent_->GetElementFromNodeId(node_id);
  194. if (!ui_element)
  195. return NodeNotFoundError(node_id);
  196. *matched_css_rules = BuildMatchedStyles(ui_element);
  197. return Response::Success();
  198. }
  199. Response CSSAgent::getStyleSheetText(const protocol::String& style_sheet_id,
  200. protocol::String* result) {
  201. int node_id;
  202. int stylesheet_id;
  203. std::vector<std::string> ids = base::SplitString(
  204. style_sheet_id, "_", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  205. if (ids.size() < 2 || !base::StringToInt(ids[0], &node_id) ||
  206. !base::StringToInt(ids[1], &stylesheet_id))
  207. return Response::ServerError("Invalid stylesheet id");
  208. UIElement* ui_element = dom_agent_->GetElementFromNodeId(node_id);
  209. if (!ui_element)
  210. return Response::ServerError("Node id not found");
  211. auto sources = ui_element->GetSources();
  212. if (static_cast<int>(sources.size()) <= stylesheet_id)
  213. return Response::ServerError("Stylesheet id not found");
  214. if (GetSourceCode(sources[stylesheet_id].path_, result))
  215. return Response::Success();
  216. return Response::ServerError("Could not read source file");
  217. }
  218. Response CSSAgent::setStyleTexts(
  219. std::unique_ptr<Array<CSS::StyleDeclarationEdit>> edits,
  220. std::unique_ptr<Array<CSS::CSSStyle>>* result) {
  221. auto updated_styles = std::make_unique<Array<CSS::CSSStyle>>();
  222. for (const auto& edit : *edits) {
  223. int node_id;
  224. int stylesheet_id;
  225. std::vector<std::string> ids =
  226. base::SplitString(edit->getStyleSheetId(), "_", base::TRIM_WHITESPACE,
  227. base::SPLIT_WANT_NONEMPTY);
  228. if (ids.size() < 2 || !base::StringToInt(ids[0], &node_id) ||
  229. !base::StringToInt(ids[1], &stylesheet_id))
  230. return Response::ServerError("Invalid stylesheet id");
  231. UIElement* ui_element = dom_agent_->GetElementFromNodeId(node_id);
  232. if (!ui_element)
  233. return Response::ServerError("Node id not found");
  234. // Handle setting properties from metadata for elements which use metadata.
  235. if (!ui_element->SetPropertiesFromString(edit->getText())) {
  236. gfx::Rect updated_bounds;
  237. bool visible = false;
  238. if (!GetPropertiesForUIElement(ui_element, &updated_bounds, &visible))
  239. return NodeNotFoundError(node_id);
  240. Response response(
  241. ParseProperties(edit->getText(), &updated_bounds, &visible));
  242. if (!response.IsSuccess())
  243. return response;
  244. if (!SetPropertiesForUIElement(ui_element, updated_bounds, visible))
  245. return NodeNotFoundError(node_id);
  246. }
  247. updated_styles->emplace_back(BuildCSSStyle(
  248. edit->getStyleSheetId(), GetClassPropertiesWithBounds(ui_element)
  249. .at(stylesheet_id)
  250. .properties_));
  251. }
  252. *result = std::move(updated_styles);
  253. return Response::Success();
  254. }
  255. void CSSAgent::OnElementBoundsChanged(UIElement* ui_element) {
  256. InvalidateStyleSheet(ui_element);
  257. }
  258. void CSSAgent::InvalidateStyleSheet(UIElement* ui_element) {
  259. // The stylesheetId for each node is equivalent to a string of its
  260. // node_id + "_" + index of CSS::RuleMatch in vector.
  261. frontend()->styleSheetChanged(BuildStylesheetUId(
  262. ui_element->node_id(), ui_element->GetBaseStylesheetId()));
  263. }
  264. bool CSSAgent::GetPropertiesForUIElement(UIElement* ui_element,
  265. gfx::Rect* bounds,
  266. bool* visible) {
  267. if (ui_element) {
  268. ui_element->GetBounds(bounds);
  269. if (ui_element->type() != VIEW)
  270. ui_element->GetVisible(visible);
  271. return true;
  272. }
  273. return false;
  274. }
  275. bool CSSAgent::SetPropertiesForUIElement(UIElement* ui_element,
  276. const gfx::Rect& bounds,
  277. bool visible) {
  278. if (ui_element) {
  279. ui_element->SetBounds(bounds);
  280. ui_element->SetVisible(visible);
  281. return true;
  282. }
  283. return false;
  284. }
  285. std::unique_ptr<Array<CSS::RuleMatch>> CSSAgent::BuildMatchedStyles(
  286. UIElement* ui_element) {
  287. auto result = std::make_unique<Array<CSS::RuleMatch>>();
  288. std::vector<UIElement::ClassProperties> properties_vector =
  289. GetClassPropertiesWithBounds(ui_element);
  290. for (size_t i = 0; i < properties_vector.size(); i++) {
  291. result->emplace_back(
  292. CSS::RuleMatch::create()
  293. .setRule(BuildCSSRule(BuildStylesheetUId(ui_element->node_id(), i),
  294. properties_vector[i]))
  295. .setMatchingSelectors(BuildDefaultMatchingSelectors())
  296. .build());
  297. }
  298. if (!ui_element->header_sent()) {
  299. InitStylesheetHeaders(ui_element);
  300. }
  301. return result;
  302. }
  303. void CSSAgent::InitStylesheetHeaders(UIElement* ui_element) {
  304. std::vector<UIElement::Source> sources = ui_element->GetSources();
  305. for (size_t i = 0; i < sources.size(); i++) {
  306. frontend()->styleSheetAdded(BuildObjectForStyleSheetInfo(
  307. BuildStylesheetUId(ui_element->node_id(), i), sources[i].path_,
  308. sources[i].line_));
  309. }
  310. ui_element->set_header_sent();
  311. }
  312. } // namespace ui_devtools