Index: third_party/WebKit/Source/core/inspector/InspectorDOMSnapshotAgent.cpp |
diff --git a/third_party/WebKit/Source/core/inspector/InspectorDOMSnapshotAgent.cpp b/third_party/WebKit/Source/core/inspector/InspectorDOMSnapshotAgent.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..1af39aa6f262b8b35933c9cb5b44b8ce7ccf6a1b |
--- /dev/null |
+++ b/third_party/WebKit/Source/core/inspector/InspectorDOMSnapshotAgent.cpp |
@@ -0,0 +1,406 @@ |
+// Copyright 2017 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 "core/inspector/InspectorDOMSnapshotAgent.h" |
+ |
+#include "core/css/CSSComputedStyleDeclaration.h" |
+#include "core/dom/Attribute.h" |
+#include "core/dom/AttributeCollection.h" |
+#include "core/dom/Document.h" |
+#include "core/dom/DocumentType.h" |
+#include "core/dom/Element.h" |
+#include "core/dom/Node.h" |
+#include "core/dom/PseudoElement.h" |
+#include "core/dom/QualifiedName.h" |
+#include "core/frame/LocalFrame.h" |
+#include "core/html/HTMLFrameOwnerElement.h" |
+#include "core/html/HTMLLinkElement.h" |
+#include "core/html/HTMLTemplateElement.h" |
+#include "core/inspector/IdentifiersFactory.h" |
+#include "core/inspector/InspectedFrames.h" |
+#include "core/inspector/InspectorDOMAgent.h" |
+#include "core/layout/LayoutObject.h" |
+#include "core/layout/LayoutText.h" |
+#include "core/layout/line/InlineTextBox.h" |
+#include "platform/wtf/PtrUtil.h" |
+ |
+namespace blink { |
+ |
+using protocol::Maybe; |
+using protocol::Response; |
+ |
+namespace { |
+ |
+const size_t kMaxTextSize = 10000; |
+const UChar kEllipsisUChar[] = {0x2026, 0}; |
+ |
+std::unique_ptr<protocol::DOM::Rect> BuildRectForFloatRect( |
+ const FloatRect& rect) { |
+ return protocol::DOM::Rect::create() |
+ .setX(rect.X()) |
+ .setY(rect.Y()) |
+ .setWidth(rect.Width()) |
+ .setHeight(rect.Height()) |
+ .build(); |
+} |
+ |
+} // namespace |
+ |
+struct InspectorDOMSnapshotAgent::VectorStringHashTraits |
+ : public WTF::GenericHashTraits<Vector<String>> { |
+ static unsigned GetHash(const Vector<String>& vec) { |
+ unsigned h = DefaultHash<size_t>::Hash::GetHash(vec.size()); |
+ for (size_t i = 0; i < vec.size(); i++) { |
+ h = WTF::HashInts(h, DefaultHash<String>::Hash::GetHash(vec[i])); |
+ } |
+ return h; |
+ } |
+ |
+ static bool Equal(const Vector<String>& a, const Vector<String>& b) { |
+ if (a.size() != b.size()) |
+ return false; |
+ for (size_t i = 0; i < a.size(); i++) { |
+ if (a[i] != b[i]) |
+ return false; |
+ } |
+ return true; |
+ } |
+ |
+ static void ConstructDeletedValue(Vector<String>& vec, bool) { |
+ vec.clear(); |
+ vec.push_back(String(WTF::kHashTableDeletedValue)); |
+ } |
+ |
+ static bool IsDeletedValue(const Vector<String>& vec) { |
+ return !vec.IsEmpty() && vec[0].IsHashTableDeletedValue(); |
+ } |
+ |
+ static bool IsEmptyValue(const Vector<String>& vec) { return vec.IsEmpty(); } |
+ |
+ static const bool kEmptyValueIsZero = false; |
+ static const bool safe_to_compare_to_empty_or_deleted = false; |
+ static const bool kHasIsEmptyValueFunction = true; |
+}; |
+ |
+InspectorDOMSnapshotAgent::InspectorDOMSnapshotAgent( |
+ InspectedFrames* inspected_frames) |
+ : inspected_frames_(inspected_frames) {} |
+ |
+InspectorDOMSnapshotAgent::~InspectorDOMSnapshotAgent() {} |
+ |
+Response InspectorDOMSnapshotAgent::getSnapshot( |
+ std::unique_ptr<protocol::Array<String>> style_whitelist, |
+ protocol::Maybe<int> depth, |
+ protocol::Maybe<bool> pierce, |
+ std::unique_ptr<protocol::Array<protocol::DOMSnapshot::DOMNode>>* dom_nodes, |
+ std::unique_ptr<protocol::Array<protocol::DOMSnapshot::LayoutTreeNode>>* |
+ layout_tree_nodes, |
+ std::unique_ptr<protocol::Array<protocol::DOMSnapshot::ComputedStyle>>* |
+ computed_styles) { |
+ DCHECK(!dom_nodes_ && !layout_tree_nodes_ && !computed_styles_); |
+ |
+ Document* document = inspected_frames_->Root()->GetDocument(); |
+ if (!document) |
+ return Response::Error("Document is not available"); |
+ |
+ // Setup snapshot. |
+ dom_nodes_ = protocol::Array<protocol::DOMSnapshot::DOMNode>::create(); |
+ layout_tree_nodes_ = |
+ protocol::Array<protocol::DOMSnapshot::LayoutTreeNode>::create(); |
+ computed_styles_ = |
+ protocol::Array<protocol::DOMSnapshot::ComputedStyle>::create(); |
+ pierce_ = pierce.fromMaybe(false); |
+ int sanitized_depth = depth.fromMaybe(-1); |
+ if (sanitized_depth == -1) |
+ sanitized_depth = INT_MAX; |
+ computed_styles_map_ = WTF::MakeUnique<ComputedStylesMap>(); |
+ css_property_whitelist_ = WTF::MakeUnique<CSSPropertyWhitelist>(); |
+ |
+ // Look up the CSSPropertyIDs for each entry in |style_whitelist|. |
+ for (size_t i = 0; i < style_whitelist->length(); i++) { |
+ CSSPropertyID property_id = cssPropertyID(style_whitelist->get(i)); |
+ if (property_id == CSSPropertyInvalid) |
+ continue; |
+ css_property_whitelist_->push_back( |
+ std::make_pair(style_whitelist->get(i), property_id)); |
+ } |
+ |
+ // Actual traversal. |
+ VisitNode(document, sanitized_depth); |
+ |
+ // Extract results from state and reset. |
+ *dom_nodes = std::move(dom_nodes_); |
+ *layout_tree_nodes = std::move(layout_tree_nodes_); |
+ *computed_styles = std::move(computed_styles_); |
+ computed_styles_map_.reset(); |
+ css_property_whitelist_.reset(); |
+ return Response::OK(); |
+} |
+ |
+int InspectorDOMSnapshotAgent::VisitNode(Node* node, int depth) { |
+ if (node->IsDocumentNode()) { |
+ // Update layout tree before traversal of document so that we inspect a |
+ // current and consistent state of all trees. |
+ node->GetDocument().UpdateStyleAndLayoutTree(); |
+ } |
+ |
+ String node_value; |
+ switch (node->getNodeType()) { |
+ case Node::kTextNode: |
+ case Node::kAttributeNode: |
+ case Node::kCommentNode: |
+ case Node::kCdataSectionNode: |
+ node_value = node->nodeValue(); |
+ 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
|
+ node_value = node_value.Left(kMaxTextSize) + kEllipsisUChar; |
+ break; |
+ default: |
+ break; |
+ } |
+ |
+ // Create DOMNode object and add it to the result array before traversing |
+ // children, so that parents appear before their children in the array. |
+ std::unique_ptr<protocol::DOMSnapshot::DOMNode> owned_value = |
+ protocol::DOMSnapshot::DOMNode::create() |
+ .setNodeType(static_cast<int>(node->getNodeType())) |
+ .setNodeName(node->nodeName()) |
+ .setNodeValue(node_value) |
+ .build(); |
+ protocol::DOMSnapshot::DOMNode* value = owned_value.get(); |
+ int index = dom_nodes_->length(); |
+ dom_nodes_->addItem(std::move(owned_value)); |
+ |
+ int layoutNodeIndex = VisitLayoutTreeNode(node, index); |
+ if (layoutNodeIndex != -1) |
+ value->setLayoutNodeIndex(layoutNodeIndex); |
+ |
+ if (node->IsSVGElement()) |
+ value->setIsSVG(true); |
+ |
+ if (node->IsElementNode()) { |
+ Element* element = ToElement(node); |
+ value->setAttributes(BuildArrayForElementAttributes(element)); |
+ |
+ if (node->IsFrameOwnerElement()) { |
+ HTMLFrameOwnerElement* frame_owner = ToHTMLFrameOwnerElement(node); |
+ if (LocalFrame* frame = |
+ frame_owner->ContentFrame() && |
+ frame_owner->ContentFrame()->IsLocalFrame() |
+ ? ToLocalFrame(frame_owner->ContentFrame()) |
+ : nullptr) |
+ value->setFrameId(IdentifiersFactory::FrameId(frame)); |
+ if (Document* doc = frame_owner->contentDocument()) { |
+ value->setContentDocumentIndex(VisitNode(doc, pierce_ ? depth : 0)); |
+ } |
+ } |
+ |
+ if (node->parentNode() && node->parentNode()->IsDocumentNode()) { |
+ LocalFrame* frame = node->GetDocument().GetFrame(); |
+ if (frame) |
+ value->setFrameId(IdentifiersFactory::FrameId(frame)); |
+ } |
+ |
+ if (isHTMLLinkElement(*element)) { |
+ HTMLLinkElement& link_element = toHTMLLinkElement(*element); |
+ if (link_element.IsImport() && link_element.import() && |
+ InspectorDOMAgent::InnerParentNode(link_element.import()) == |
+ link_element) { |
+ value->setImportedDocumentIndex( |
+ VisitNode(link_element.import(), pierce_ ? depth : 0)); |
+ } |
+ } |
+ |
+ if (isHTMLTemplateElement(*element)) { |
+ value->setTemplateContentIndex(VisitNode( |
+ toHTMLTemplateElement(*element).content(), pierce_ ? depth : 0)); |
+ } |
+ |
+ if (element->GetPseudoId()) { |
+ protocol::DOM::PseudoType pseudo_type; |
+ if (InspectorDOMAgent::GetPseudoElementType(element->GetPseudoId(), |
+ &pseudo_type)) |
+ value->setPseudoType(pseudo_type); |
+ } else { |
+ value->setPseudoElementIndexes(VisitPseudoElements(element, depth)); |
+ } |
+ } else if (node->IsDocumentNode()) { |
+ Document* document = ToDocument(node); |
+ value->setDocumentURL(InspectorDOMAgent::DocumentURLString(document)); |
+ value->setBaseURL(InspectorDOMAgent::DocumentBaseURLString(document)); |
+ } else if (node->IsDocumentTypeNode()) { |
+ DocumentType* doc_type = ToDocumentType(node); |
+ value->setPublicId(doc_type->publicId()); |
+ value->setSystemId(doc_type->systemId()); |
+ } |
+ |
+ if (node->IsContainerNode()) |
+ value->setChildNodeIndexes(VisitContainerChildren(node, depth)); |
+ |
+ return index; |
+} |
+ |
+std::unique_ptr<protocol::Array<int>> |
+InspectorDOMSnapshotAgent::VisitContainerChildren(Node* container, int depth) { |
+ auto children = protocol::Array<int>::create(); |
+ |
+ if (!FlatTreeTraversal::HasChildren(*container)) |
+ return nullptr; |
+ |
+ if (depth == 0) { |
+ // 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.
|
+ Node* first_child = FlatTreeTraversal::FirstChild(*container); |
+ if (first_child && first_child->getNodeType() == Node::kTextNode && |
+ !FlatTreeTraversal::NextSibling(*first_child)) { |
+ children->addItem(VisitNode(first_child, 0)); |
+ return children; |
+ } |
+ return nullptr; |
+ } |
+ |
+ depth--; |
+ Node* child = FlatTreeTraversal::FirstChild(*container); |
+ while (child) { |
+ children->addItem(VisitNode(child, depth)); |
+ child = FlatTreeTraversal::NextSibling(*child); |
+ } |
+ |
+ return children; |
+} |
+ |
+std::unique_ptr<protocol::Array<int>> |
+InspectorDOMSnapshotAgent::VisitPseudoElements(Element* parent, int depth) { |
+ if (depth == 0) |
+ return nullptr; |
+ |
+ if (!parent->GetPseudoElement(kPseudoIdBefore) && |
+ !parent->GetPseudoElement(kPseudoIdAfter)) { |
+ return nullptr; |
+ } |
+ |
+ auto pseudo_elements = protocol::Array<int>::create(); |
+ |
+ 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.
|
+ if (parent->GetPseudoElement(kPseudoIdBefore)) { |
+ pseudo_elements->addItem( |
+ VisitNode(parent->GetPseudoElement(kPseudoIdBefore), depth)); |
+ } |
+ if (parent->GetPseudoElement(kPseudoIdAfter)) { |
+ pseudo_elements->addItem( |
+ VisitNode(parent->GetPseudoElement(kPseudoIdAfter), depth)); |
+ } |
+ |
+ return pseudo_elements; |
+} |
+ |
+std::unique_ptr<protocol::Array<protocol::DOMSnapshot::Attribute>> |
+InspectorDOMSnapshotAgent::BuildArrayForElementAttributes(Element* element) { |
+ auto attributes_value = |
+ protocol::Array<protocol::DOMSnapshot::Attribute>::create(); |
+ AttributeCollection attributes = element->Attributes(); |
+ for (const auto& attribute : attributes) { |
+ attributes_value->addItem(protocol::DOMSnapshot::Attribute::create() |
+ .setName(attribute.GetName().ToString()) |
+ .setValue(attribute.Value()) |
+ .build()); |
+ } |
+ return attributes_value; |
+} |
+ |
+int InspectorDOMSnapshotAgent::VisitLayoutTreeNode(Node* node, int node_index) { |
+ LayoutObject* layout_object = node->GetLayoutObject(); |
+ if (!layout_object) |
+ return -1; |
+ |
+ auto layout_tree_node = |
+ protocol::DOMSnapshot::LayoutTreeNode::create() |
+ .setDomNodeIndex(node_index) |
+ .setBoundingBox(BuildRectForFloatRect( |
+ node->IsElementNode() |
+ ? FloatRect(ToElement(node)->BoundsInViewport()) |
+ : layout_object->AbsoluteBoundingBoxRect())) |
+ .build(); |
+ |
+ int style_index = GetStyleIndexForNode(node); |
+ if (style_index != -1) |
+ layout_tree_node->setStyleIndex(style_index); |
+ |
+ if (layout_object->IsText()) { |
+ LayoutText* layout_text = ToLayoutText(layout_object); |
+ layout_tree_node->setLayoutText(layout_text->GetText()); |
+ if (layout_text->HasTextBoxes()) { |
+ std::unique_ptr<protocol::Array<protocol::CSS::InlineTextBox>> |
+ inline_text_nodes = |
+ protocol::Array<protocol::CSS::InlineTextBox>::create(); |
+ for (const InlineTextBox* text_box = layout_text->FirstTextBox(); |
+ text_box; text_box = text_box->NextTextBox()) { |
+ FloatRect local_coords_text_box_rect(text_box->FrameRect()); |
+ FloatRect absolute_coords_text_box_rect = |
+ layout_object->LocalToAbsoluteQuad(local_coords_text_box_rect) |
+ .BoundingBox(); |
+ inline_text_nodes->addItem( |
+ protocol::CSS::InlineTextBox::create() |
+ .setStartCharacterIndex(text_box->Start()) |
+ .setNumCharacters(text_box->Len()) |
+ .setBoundingBox( |
+ BuildRectForFloatRect(absolute_coords_text_box_rect)) |
+ .build()); |
+ } |
+ layout_tree_node->setInlineTextNodes(std::move(inline_text_nodes)); |
+ } |
+ } |
+ |
+ int index = layout_tree_nodes_->length(); |
+ layout_tree_nodes_->addItem(std::move(layout_tree_node)); |
+ return index; |
+} |
+ |
+int InspectorDOMSnapshotAgent::GetStyleIndexForNode(Node* node) { |
+ CSSComputedStyleDeclaration* computed_style_info = |
+ CSSComputedStyleDeclaration::Create(node, true); |
+ |
+ Vector<String> style; |
+ bool all_properties_empty = true; |
+ for (const auto& pair : *css_property_whitelist_) { |
+ String value = computed_style_info->GetPropertyValue(pair.second); |
+ if (!value.IsEmpty()) |
+ all_properties_empty = false; |
+ style.push_back(value); |
+ } |
+ |
+ // -1 means an empty style. |
+ if (all_properties_empty) |
+ return -1; |
+ |
+ ComputedStylesMap::iterator it = computed_styles_map_->find(style); |
+ if (it != computed_styles_map_->end()) |
+ return it->value; |
+ |
+ // It's a distinct style, so append to |computedStyles|. |
+ std::unique_ptr<protocol::Array<protocol::CSS::CSSComputedStyleProperty>> |
+ style_properties = |
+ protocol::Array<protocol::CSS::CSSComputedStyleProperty>::create(); |
+ |
+ for (size_t i = 0; i < style.size(); i++) { |
+ if (style[i].IsEmpty()) |
+ continue; |
+ style_properties->addItem(protocol::CSS::CSSComputedStyleProperty::create() |
+ .setName((*css_property_whitelist_)[i].first) |
+ .setValue(style[i]) |
+ .build()); |
+ } |
+ |
+ size_t index = computed_styles_->length(); |
+ computed_styles_->addItem(protocol::DOMSnapshot::ComputedStyle::create() |
+ .setProperties(std::move(style_properties)) |
+ .build()); |
+ computed_styles_map_->insert(std::move(style), index); |
+ return index; |
+} |
+ |
+DEFINE_TRACE(InspectorDOMSnapshotAgent) { |
+ visitor->Trace(inspected_frames_); |
+ InspectorBaseAgent::Trace(visitor); |
+} |
+ |
+} // namespace blink |