Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2017 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 | |
| 5 #include "core/inspector/InspectorDOMSnapshotAgent.h" | |
| 6 | |
| 7 #include "core/css/CSSComputedStyleDeclaration.h" | |
| 8 #include "core/dom/Attribute.h" | |
| 9 #include "core/dom/AttributeCollection.h" | |
| 10 #include "core/dom/Document.h" | |
| 11 #include "core/dom/DocumentType.h" | |
| 12 #include "core/dom/Element.h" | |
| 13 #include "core/dom/Node.h" | |
| 14 #include "core/dom/PseudoElement.h" | |
| 15 #include "core/dom/QualifiedName.h" | |
| 16 #include "core/frame/LocalFrame.h" | |
| 17 #include "core/html/HTMLFrameOwnerElement.h" | |
| 18 #include "core/html/HTMLLinkElement.h" | |
| 19 #include "core/html/HTMLTemplateElement.h" | |
| 20 #include "core/inspector/IdentifiersFactory.h" | |
| 21 #include "core/inspector/InspectedFrames.h" | |
| 22 #include "core/inspector/InspectorDOMAgent.h" | |
| 23 #include "core/layout/LayoutObject.h" | |
| 24 #include "core/layout/LayoutText.h" | |
| 25 #include "core/layout/line/InlineTextBox.h" | |
| 26 #include "platform/wtf/PtrUtil.h" | |
| 27 | |
| 28 namespace blink { | |
| 29 | |
| 30 using protocol::Maybe; | |
| 31 using protocol::Response; | |
| 32 | |
| 33 namespace { | |
| 34 | |
| 35 const size_t kMaxTextSize = 10000; | |
| 36 const UChar kEllipsisUChar[] = {0x2026, 0}; | |
| 37 | |
| 38 std::unique_ptr<protocol::DOM::Rect> BuildRectForFloatRect( | |
| 39 const FloatRect& rect) { | |
| 40 return protocol::DOM::Rect::create() | |
| 41 .setX(rect.X()) | |
| 42 .setY(rect.Y()) | |
| 43 .setWidth(rect.Width()) | |
| 44 .setHeight(rect.Height()) | |
| 45 .build(); | |
| 46 } | |
| 47 | |
| 48 } // namespace | |
| 49 | |
| 50 struct InspectorDOMSnapshotAgent::VectorStringHashTraits | |
| 51 : public WTF::GenericHashTraits<Vector<String>> { | |
| 52 static unsigned GetHash(const Vector<String>& vec) { | |
| 53 unsigned h = DefaultHash<size_t>::Hash::GetHash(vec.size()); | |
| 54 for (size_t i = 0; i < vec.size(); i++) { | |
| 55 h = WTF::HashInts(h, DefaultHash<String>::Hash::GetHash(vec[i])); | |
| 56 } | |
| 57 return h; | |
| 58 } | |
| 59 | |
| 60 static bool Equal(const Vector<String>& a, const Vector<String>& b) { | |
| 61 if (a.size() != b.size()) | |
| 62 return false; | |
| 63 for (size_t i = 0; i < a.size(); i++) { | |
| 64 if (a[i] != b[i]) | |
| 65 return false; | |
| 66 } | |
| 67 return true; | |
| 68 } | |
| 69 | |
| 70 static void ConstructDeletedValue(Vector<String>& vec, bool) { | |
| 71 vec.clear(); | |
| 72 vec.push_back(String(WTF::kHashTableDeletedValue)); | |
| 73 } | |
| 74 | |
| 75 static bool IsDeletedValue(const Vector<String>& vec) { | |
| 76 return !vec.IsEmpty() && vec[0].IsHashTableDeletedValue(); | |
| 77 } | |
| 78 | |
| 79 static bool IsEmptyValue(const Vector<String>& vec) { return vec.IsEmpty(); } | |
| 80 | |
| 81 static const bool kEmptyValueIsZero = false; | |
| 82 static const bool safe_to_compare_to_empty_or_deleted = false; | |
| 83 static const bool kHasIsEmptyValueFunction = true; | |
| 84 }; | |
| 85 | |
| 86 InspectorDOMSnapshotAgent::InspectorDOMSnapshotAgent( | |
| 87 InspectedFrames* inspected_frames) | |
| 88 : inspected_frames_(inspected_frames) {} | |
| 89 | |
| 90 InspectorDOMSnapshotAgent::~InspectorDOMSnapshotAgent() {} | |
| 91 | |
| 92 Response InspectorDOMSnapshotAgent::getSnapshot( | |
| 93 std::unique_ptr<protocol::Array<String>> style_whitelist, | |
| 94 protocol::Maybe<int> depth, | |
| 95 protocol::Maybe<bool> pierce, | |
| 96 std::unique_ptr<protocol::Array<protocol::DOMSnapshot::DOMNode>>* dom_nodes, | |
| 97 std::unique_ptr<protocol::Array<protocol::DOMSnapshot::LayoutTreeNode>>* | |
| 98 layout_tree_nodes, | |
| 99 std::unique_ptr<protocol::Array<protocol::DOMSnapshot::ComputedStyle>>* | |
| 100 computed_styles) { | |
| 101 DCHECK(!dom_nodes_ && !layout_tree_nodes_ && !computed_styles_); | |
| 102 | |
| 103 Document* document = inspected_frames_->Root()->GetDocument(); | |
| 104 if (!document) | |
| 105 return Response::Error("Document is not available"); | |
| 106 | |
| 107 // Setup snapshot. | |
| 108 dom_nodes_ = protocol::Array<protocol::DOMSnapshot::DOMNode>::create(); | |
| 109 layout_tree_nodes_ = | |
| 110 protocol::Array<protocol::DOMSnapshot::LayoutTreeNode>::create(); | |
| 111 computed_styles_ = | |
| 112 protocol::Array<protocol::DOMSnapshot::ComputedStyle>::create(); | |
| 113 pierce_ = pierce.fromMaybe(false); | |
| 114 int sanitized_depth = depth.fromMaybe(-1); | |
| 115 if (sanitized_depth == -1) | |
| 116 sanitized_depth = INT_MAX; | |
| 117 computed_styles_map_ = WTF::MakeUnique<ComputedStylesMap>(); | |
| 118 css_property_whitelist_ = WTF::MakeUnique<CSSPropertyWhitelist>(); | |
| 119 | |
| 120 // Look up the CSSPropertyIDs for each entry in |style_whitelist|. | |
| 121 for (size_t i = 0; i < style_whitelist->length(); i++) { | |
| 122 CSSPropertyID property_id = cssPropertyID(style_whitelist->get(i)); | |
| 123 if (property_id == CSSPropertyInvalid) | |
| 124 continue; | |
| 125 css_property_whitelist_->push_back( | |
| 126 std::make_pair(style_whitelist->get(i), property_id)); | |
| 127 } | |
| 128 | |
| 129 // Actual traversal. | |
| 130 VisitNode(document, sanitized_depth); | |
| 131 | |
| 132 // Extract results from state and reset. | |
| 133 *dom_nodes = std::move(dom_nodes_); | |
| 134 *layout_tree_nodes = std::move(layout_tree_nodes_); | |
| 135 *computed_styles = std::move(computed_styles_); | |
| 136 computed_styles_map_.reset(); | |
| 137 css_property_whitelist_.reset(); | |
| 138 return Response::OK(); | |
| 139 } | |
| 140 | |
| 141 int InspectorDOMSnapshotAgent::VisitNode(Node* node, int depth) { | |
| 142 if (node->IsDocumentNode()) { | |
| 143 // Update layout tree before traversal of document so that we inspect a | |
| 144 // current and consistent state of all trees. | |
| 145 node->GetDocument().UpdateStyleAndLayoutTree(); | |
| 146 } | |
| 147 | |
| 148 String node_value; | |
| 149 switch (node->getNodeType()) { | |
| 150 case Node::kTextNode: | |
| 151 case Node::kAttributeNode: | |
| 152 case Node::kCommentNode: | |
| 153 case Node::kCdataSectionNode: | |
| 154 node_value = node->nodeValue(); | |
| 155 if (node_value.length() > kMaxTextSize) | |
|
pfeldman
2017/06/06 22:54:29
They want to get a snapshot, give them a snapshot?
Eric Seckler
2017/06/07 10:23:37
Ah, I thought this might be b/c of a protocol limi
| |
| 156 node_value = node_value.Left(kMaxTextSize) + kEllipsisUChar; | |
| 157 break; | |
| 158 default: | |
| 159 break; | |
| 160 } | |
| 161 | |
| 162 // Create DOMNode object and add it to the result array before traversing | |
| 163 // children, so that parents appear before their children in the array. | |
| 164 std::unique_ptr<protocol::DOMSnapshot::DOMNode> owned_value = | |
| 165 protocol::DOMSnapshot::DOMNode::create() | |
| 166 .setNodeType(static_cast<int>(node->getNodeType())) | |
| 167 .setNodeName(node->nodeName()) | |
| 168 .setNodeValue(node_value) | |
| 169 .build(); | |
| 170 protocol::DOMSnapshot::DOMNode* value = owned_value.get(); | |
| 171 int index = dom_nodes_->length(); | |
| 172 dom_nodes_->addItem(std::move(owned_value)); | |
| 173 | |
| 174 int layoutNodeIndex = VisitLayoutTreeNode(node, index); | |
| 175 if (layoutNodeIndex != -1) | |
| 176 value->setLayoutNodeIndex(layoutNodeIndex); | |
| 177 | |
| 178 if (node->IsSVGElement()) | |
| 179 value->setIsSVG(true); | |
| 180 | |
| 181 if (node->IsElementNode()) { | |
| 182 Element* element = ToElement(node); | |
| 183 value->setAttributes(BuildArrayForElementAttributes(element)); | |
| 184 | |
| 185 if (node->IsFrameOwnerElement()) { | |
| 186 HTMLFrameOwnerElement* frame_owner = ToHTMLFrameOwnerElement(node); | |
| 187 if (LocalFrame* frame = | |
| 188 frame_owner->ContentFrame() && | |
| 189 frame_owner->ContentFrame()->IsLocalFrame() | |
| 190 ? ToLocalFrame(frame_owner->ContentFrame()) | |
| 191 : nullptr) | |
| 192 value->setFrameId(IdentifiersFactory::FrameId(frame)); | |
| 193 if (Document* doc = frame_owner->contentDocument()) { | |
| 194 value->setContentDocumentIndex(VisitNode(doc, pierce_ ? depth : 0)); | |
| 195 } | |
| 196 } | |
| 197 | |
| 198 if (node->parentNode() && node->parentNode()->IsDocumentNode()) { | |
| 199 LocalFrame* frame = node->GetDocument().GetFrame(); | |
| 200 if (frame) | |
| 201 value->setFrameId(IdentifiersFactory::FrameId(frame)); | |
| 202 } | |
| 203 | |
| 204 if (isHTMLLinkElement(*element)) { | |
| 205 HTMLLinkElement& link_element = toHTMLLinkElement(*element); | |
| 206 if (link_element.IsImport() && link_element.import() && | |
| 207 InspectorDOMAgent::InnerParentNode(link_element.import()) == | |
| 208 link_element) { | |
| 209 value->setImportedDocumentIndex( | |
| 210 VisitNode(link_element.import(), pierce_ ? depth : 0)); | |
| 211 } | |
| 212 } | |
| 213 | |
| 214 if (isHTMLTemplateElement(*element)) { | |
| 215 value->setTemplateContentIndex(VisitNode( | |
| 216 toHTMLTemplateElement(*element).content(), pierce_ ? depth : 0)); | |
| 217 } | |
| 218 | |
| 219 if (element->GetPseudoId()) { | |
| 220 protocol::DOM::PseudoType pseudo_type; | |
| 221 if (InspectorDOMAgent::GetPseudoElementType(element->GetPseudoId(), | |
| 222 &pseudo_type)) | |
| 223 value->setPseudoType(pseudo_type); | |
| 224 } else { | |
| 225 value->setPseudoElementIndexes(VisitPseudoElements(element, depth)); | |
| 226 } | |
| 227 } else if (node->IsDocumentNode()) { | |
| 228 Document* document = ToDocument(node); | |
| 229 value->setDocumentURL(InspectorDOMAgent::DocumentURLString(document)); | |
| 230 value->setBaseURL(InspectorDOMAgent::DocumentBaseURLString(document)); | |
| 231 } else if (node->IsDocumentTypeNode()) { | |
| 232 DocumentType* doc_type = ToDocumentType(node); | |
| 233 value->setPublicId(doc_type->publicId()); | |
| 234 value->setSystemId(doc_type->systemId()); | |
| 235 } | |
| 236 | |
| 237 if (node->IsContainerNode()) | |
| 238 value->setChildNodeIndexes(VisitContainerChildren(node, depth)); | |
| 239 | |
| 240 return index; | |
| 241 } | |
| 242 | |
| 243 std::unique_ptr<protocol::Array<int>> | |
| 244 InspectorDOMSnapshotAgent::VisitContainerChildren(Node* container, int depth) { | |
| 245 auto children = protocol::Array<int>::create(); | |
| 246 | |
| 247 if (!FlatTreeTraversal::HasChildren(*container)) | |
| 248 return nullptr; | |
| 249 | |
| 250 if (depth == 0) { | |
| 251 // If node has an only text child, add it anyway. | |
|
pfeldman
2017/06/06 22:54:29
You don't need this logic, it was purely for inspe
Eric Seckler
2017/06/07 10:23:37
Removed.
| |
| 252 Node* first_child = FlatTreeTraversal::FirstChild(*container); | |
| 253 if (first_child && first_child->getNodeType() == Node::kTextNode && | |
| 254 !FlatTreeTraversal::NextSibling(*first_child)) { | |
| 255 children->addItem(VisitNode(first_child, 0)); | |
| 256 return children; | |
| 257 } | |
| 258 return nullptr; | |
| 259 } | |
| 260 | |
| 261 depth--; | |
| 262 Node* child = FlatTreeTraversal::FirstChild(*container); | |
| 263 while (child) { | |
| 264 children->addItem(VisitNode(child, depth)); | |
| 265 child = FlatTreeTraversal::NextSibling(*child); | |
| 266 } | |
| 267 | |
| 268 return children; | |
| 269 } | |
| 270 | |
| 271 std::unique_ptr<protocol::Array<int>> | |
| 272 InspectorDOMSnapshotAgent::VisitPseudoElements(Element* parent, int depth) { | |
| 273 if (depth == 0) | |
| 274 return nullptr; | |
| 275 | |
| 276 if (!parent->GetPseudoElement(kPseudoIdBefore) && | |
| 277 !parent->GetPseudoElement(kPseudoIdAfter)) { | |
| 278 return nullptr; | |
| 279 } | |
| 280 | |
| 281 auto pseudo_elements = protocol::Array<int>::create(); | |
| 282 | |
| 283 depth--; | |
|
pfeldman
2017/06/06 22:54:29
Pseudos should not be depth-aware, they should not
Eric Seckler
2017/06/07 10:23:37
Depth is gone everywhere now.
| |
| 284 if (parent->GetPseudoElement(kPseudoIdBefore)) { | |
| 285 pseudo_elements->addItem( | |
| 286 VisitNode(parent->GetPseudoElement(kPseudoIdBefore), depth)); | |
| 287 } | |
| 288 if (parent->GetPseudoElement(kPseudoIdAfter)) { | |
| 289 pseudo_elements->addItem( | |
| 290 VisitNode(parent->GetPseudoElement(kPseudoIdAfter), depth)); | |
| 291 } | |
| 292 | |
| 293 return pseudo_elements; | |
| 294 } | |
| 295 | |
| 296 std::unique_ptr<protocol::Array<protocol::DOMSnapshot::Attribute>> | |
| 297 InspectorDOMSnapshotAgent::BuildArrayForElementAttributes(Element* element) { | |
| 298 auto attributes_value = | |
| 299 protocol::Array<protocol::DOMSnapshot::Attribute>::create(); | |
| 300 AttributeCollection attributes = element->Attributes(); | |
| 301 for (const auto& attribute : attributes) { | |
| 302 attributes_value->addItem(protocol::DOMSnapshot::Attribute::create() | |
| 303 .setName(attribute.GetName().ToString()) | |
| 304 .setValue(attribute.Value()) | |
| 305 .build()); | |
| 306 } | |
| 307 return attributes_value; | |
| 308 } | |
| 309 | |
| 310 int InspectorDOMSnapshotAgent::VisitLayoutTreeNode(Node* node, int node_index) { | |
| 311 LayoutObject* layout_object = node->GetLayoutObject(); | |
| 312 if (!layout_object) | |
| 313 return -1; | |
| 314 | |
| 315 auto layout_tree_node = | |
| 316 protocol::DOMSnapshot::LayoutTreeNode::create() | |
| 317 .setDomNodeIndex(node_index) | |
| 318 .setBoundingBox(BuildRectForFloatRect( | |
| 319 node->IsElementNode() | |
| 320 ? FloatRect(ToElement(node)->BoundsInViewport()) | |
| 321 : layout_object->AbsoluteBoundingBoxRect())) | |
| 322 .build(); | |
| 323 | |
| 324 int style_index = GetStyleIndexForNode(node); | |
| 325 if (style_index != -1) | |
| 326 layout_tree_node->setStyleIndex(style_index); | |
| 327 | |
| 328 if (layout_object->IsText()) { | |
| 329 LayoutText* layout_text = ToLayoutText(layout_object); | |
| 330 layout_tree_node->setLayoutText(layout_text->GetText()); | |
| 331 if (layout_text->HasTextBoxes()) { | |
| 332 std::unique_ptr<protocol::Array<protocol::CSS::InlineTextBox>> | |
| 333 inline_text_nodes = | |
| 334 protocol::Array<protocol::CSS::InlineTextBox>::create(); | |
| 335 for (const InlineTextBox* text_box = layout_text->FirstTextBox(); | |
| 336 text_box; text_box = text_box->NextTextBox()) { | |
| 337 FloatRect local_coords_text_box_rect(text_box->FrameRect()); | |
| 338 FloatRect absolute_coords_text_box_rect = | |
| 339 layout_object->LocalToAbsoluteQuad(local_coords_text_box_rect) | |
| 340 .BoundingBox(); | |
| 341 inline_text_nodes->addItem( | |
| 342 protocol::CSS::InlineTextBox::create() | |
| 343 .setStartCharacterIndex(text_box->Start()) | |
| 344 .setNumCharacters(text_box->Len()) | |
| 345 .setBoundingBox( | |
| 346 BuildRectForFloatRect(absolute_coords_text_box_rect)) | |
| 347 .build()); | |
| 348 } | |
| 349 layout_tree_node->setInlineTextNodes(std::move(inline_text_nodes)); | |
| 350 } | |
| 351 } | |
| 352 | |
| 353 int index = layout_tree_nodes_->length(); | |
| 354 layout_tree_nodes_->addItem(std::move(layout_tree_node)); | |
| 355 return index; | |
| 356 } | |
| 357 | |
| 358 int InspectorDOMSnapshotAgent::GetStyleIndexForNode(Node* node) { | |
| 359 CSSComputedStyleDeclaration* computed_style_info = | |
| 360 CSSComputedStyleDeclaration::Create(node, true); | |
| 361 | |
| 362 Vector<String> style; | |
| 363 bool all_properties_empty = true; | |
| 364 for (const auto& pair : *css_property_whitelist_) { | |
| 365 String value = computed_style_info->GetPropertyValue(pair.second); | |
| 366 if (!value.IsEmpty()) | |
| 367 all_properties_empty = false; | |
| 368 style.push_back(value); | |
| 369 } | |
| 370 | |
| 371 // -1 means an empty style. | |
| 372 if (all_properties_empty) | |
| 373 return -1; | |
| 374 | |
| 375 ComputedStylesMap::iterator it = computed_styles_map_->find(style); | |
| 376 if (it != computed_styles_map_->end()) | |
| 377 return it->value; | |
| 378 | |
| 379 // It's a distinct style, so append to |computedStyles|. | |
| 380 std::unique_ptr<protocol::Array<protocol::CSS::CSSComputedStyleProperty>> | |
| 381 style_properties = | |
| 382 protocol::Array<protocol::CSS::CSSComputedStyleProperty>::create(); | |
| 383 | |
| 384 for (size_t i = 0; i < style.size(); i++) { | |
| 385 if (style[i].IsEmpty()) | |
| 386 continue; | |
| 387 style_properties->addItem(protocol::CSS::CSSComputedStyleProperty::create() | |
| 388 .setName((*css_property_whitelist_)[i].first) | |
| 389 .setValue(style[i]) | |
| 390 .build()); | |
| 391 } | |
| 392 | |
| 393 size_t index = computed_styles_->length(); | |
| 394 computed_styles_->addItem(protocol::DOMSnapshot::ComputedStyle::create() | |
| 395 .setProperties(std::move(style_properties)) | |
| 396 .build()); | |
| 397 computed_styles_map_->insert(std::move(style), index); | |
| 398 return index; | |
| 399 } | |
| 400 | |
| 401 DEFINE_TRACE(InspectorDOMSnapshotAgent) { | |
| 402 visitor->Trace(inspected_frames_); | |
| 403 InspectorBaseAgent::Trace(visitor); | |
| 404 } | |
| 405 | |
| 406 } // namespace blink | |
| OLD | NEW |