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 |