Chromium Code Reviews| 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 |