1 /*
2 * Copyright (C) 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2009 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 * its contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "config.h"
31 #include "InspectorDOMAgent.h"
32
33 #include "AtomicString.h"
34 #include "DOMWindow.h"
35 #include "Document.h"
36 #include "Event.h"
37 #include "EventListener.h"
38 #include "EventNames.h"
39 #include "EventTarget.h"
40 #include "HTMLFrameOwnerElement.h"
41 #include "InspectorFrontend.h"
42 #include "markup.h"
43 #include "MutationEvent.h"
44 #include "Node.h"
45 #include "NodeList.h"
46 #include "PlatformString.h"
47 #include "ScriptObject.h"
48 #include "Text.h"
49
50 #include <wtf/OwnPtr.h>
51 #include <wtf/Vector.h>
52
53 namespace WebCore {
54
InspectorDOMAgent(InspectorFrontend * frontend)55 InspectorDOMAgent::InspectorDOMAgent(InspectorFrontend* frontend)
56 : m_frontend(frontend)
57 , m_lastNodeId(1)
58 {
59 }
60
~InspectorDOMAgent()61 InspectorDOMAgent::~InspectorDOMAgent()
62 {
63 setDocument(0);
64 }
65
setDocument(Document * doc)66 void InspectorDOMAgent::setDocument(Document* doc)
67 {
68 if (doc == mainFrameDocument())
69 return;
70
71 ListHashSet<RefPtr<Document> > copy = m_documents;
72 for (ListHashSet<RefPtr<Document> >::iterator it = copy.begin(); it != copy.end(); ++it)
73 stopListening((*it).get());
74
75 ASSERT(!m_documents.size());
76
77 if (doc) {
78 startListening(doc);
79 if (doc->documentElement()) {
80 pushDocumentElementToFrontend();
81 }
82 } else {
83 discardBindings();
84 }
85 }
86
startListening(Document * doc)87 void InspectorDOMAgent::startListening(Document* doc)
88 {
89 if (m_documents.contains(doc))
90 return;
91
92 doc->addEventListener(eventNames().DOMContentLoadedEvent, this, false);
93 doc->addEventListener(eventNames().DOMNodeInsertedEvent, this, false);
94 doc->addEventListener(eventNames().DOMNodeRemovedEvent, this, false);
95 doc->addEventListener(eventNames().DOMNodeRemovedFromDocumentEvent, this, true);
96 doc->addEventListener(eventNames().DOMAttrModifiedEvent, this, false);
97 m_documents.add(doc);
98 }
99
stopListening(Document * doc)100 void InspectorDOMAgent::stopListening(Document* doc)
101 {
102 if (!m_documents.contains(doc))
103 return;
104
105 doc->removeEventListener(eventNames().DOMContentLoadedEvent, this, false);
106 doc->removeEventListener(eventNames().DOMNodeInsertedEvent, this, false);
107 doc->removeEventListener(eventNames().DOMNodeRemovedEvent, this, false);
108 doc->removeEventListener(eventNames().DOMNodeRemovedFromDocumentEvent, this, true);
109 doc->removeEventListener(eventNames().DOMAttrModifiedEvent, this, false);
110 m_documents.remove(doc);
111 }
112
handleEvent(Event * event,bool)113 void InspectorDOMAgent::handleEvent(Event* event, bool)
114 {
115 AtomicString type = event->type();
116 Node* node = event->target()->toNode();
117
118 // Remove mapping entry if necessary.
119 if (type == eventNames().DOMNodeRemovedFromDocumentEvent) {
120 unbind(node);
121 return;
122 }
123
124 if (type == eventNames().DOMAttrModifiedEvent) {
125 long id = idForNode(node);
126 // If node is not mapped yet -> ignore the event.
127 if (!id)
128 return;
129
130 Element* element = static_cast<Element*>(node);
131 m_frontend->attributesUpdated(id, buildArrayForElementAttributes(element));
132 } else if (type == eventNames().DOMNodeInsertedEvent) {
133 if (isWhitespace(node))
134 return;
135
136 Node* parent = static_cast<MutationEvent*>(event)->relatedNode();
137 long parentId = idForNode(parent);
138 // Return if parent is not mapped yet.
139 if (!parentId)
140 return;
141
142 if (!m_childrenRequested.contains(parentId)) {
143 // No children are mapped yet -> only notify on changes of hasChildren.
144 m_frontend->hasChildrenUpdated(parentId, true);
145 } else {
146 // Children have been requested -> return value of a new child.
147 long prevId = idForNode(innerPreviousSibling(node));
148
149 ScriptObject value = buildObjectForNode(node, 0);
150 m_frontend->childNodeInserted(parentId, prevId, value);
151 }
152 } else if (type == eventNames().DOMNodeRemovedEvent) {
153 if (isWhitespace(node))
154 return;
155
156 Node* parent = static_cast<MutationEvent*>(event)->relatedNode();
157 long parentId = idForNode(parent);
158 // If parent is not mapped yet -> ignore the event.
159 if (!parentId)
160 return;
161
162 if (!m_childrenRequested.contains(parentId)) {
163 // No children are mapped yet -> only notify on changes of hasChildren.
164 if (innerChildNodeCount(parent) == 1)
165 m_frontend->hasChildrenUpdated(parentId, false);
166 } else {
167 m_frontend->childNodeRemoved(parentId, idForNode(node));
168 }
169 } else if (type == eventNames().DOMContentLoadedEvent) {
170 // Re-push document once it is loaded.
171 discardBindings();
172 pushDocumentElementToFrontend();
173 }
174 }
175
bind(Node * node)176 long InspectorDOMAgent::bind(Node* node)
177 {
178 HashMap<Node*, long>::iterator it = m_nodeToId.find(node);
179 if (it != m_nodeToId.end())
180 return it->second;
181 long id = m_lastNodeId++;
182 m_nodeToId.set(node, id);
183 m_idToNode.set(id, node);
184 return id;
185 }
186
unbind(Node * node)187 void InspectorDOMAgent::unbind(Node* node)
188 {
189 if (node->isFrameOwnerElement()) {
190 const HTMLFrameOwnerElement* frameOwner = static_cast<const HTMLFrameOwnerElement*>(node);
191 stopListening(frameOwner->contentDocument());
192 }
193
194 HashMap<Node*, long>::iterator it = m_nodeToId.find(node);
195 if (it != m_nodeToId.end()) {
196 m_idToNode.remove(m_idToNode.find(it->second));
197 m_childrenRequested.remove(m_childrenRequested.find(it->second));
198 m_nodeToId.remove(it);
199 }
200 }
201
pushDocumentElementToFrontend()202 void InspectorDOMAgent::pushDocumentElementToFrontend()
203 {
204 Element* docElem = mainFrameDocument()->documentElement();
205 if (!m_nodeToId.contains(docElem))
206 m_frontend->setDocumentElement(buildObjectForNode(docElem, 0));
207 }
208
pushChildNodesToFrontend(long elementId)209 void InspectorDOMAgent::pushChildNodesToFrontend(long elementId)
210 {
211 Node* node = nodeForId(elementId);
212 if (!node || (node->nodeType() != Node::ELEMENT_NODE))
213 return;
214 if (m_childrenRequested.contains(elementId))
215 return;
216
217 Element* element = static_cast<Element*>(node);
218 ScriptArray children = buildArrayForElementChildren(element, 1);
219 m_childrenRequested.add(elementId);
220 m_frontend->setChildNodes(elementId, children);
221 }
222
discardBindings()223 void InspectorDOMAgent::discardBindings()
224 {
225 m_nodeToId.clear();
226 m_idToNode.clear();
227 m_childrenRequested.clear();
228 }
229
nodeForId(long id)230 Node* InspectorDOMAgent::nodeForId(long id)
231 {
232 HashMap<long, Node*>::iterator it = m_idToNode.find(id);
233 if (it != m_idToNode.end())
234 return it->second;
235 return 0;
236 }
237
idForNode(Node * node)238 long InspectorDOMAgent::idForNode(Node* node)
239 {
240 if (!node)
241 return 0;
242 HashMap<Node*, long>::iterator it = m_nodeToId.find(node);
243 if (it != m_nodeToId.end())
244 return it->second;
245 return 0;
246 }
247
getChildNodes(long callId,long elementId)248 void InspectorDOMAgent::getChildNodes(long callId, long elementId)
249 {
250 pushChildNodesToFrontend(elementId);
251 m_frontend->didGetChildNodes(callId);
252 }
253
pushNodePathToFrontend(Node * nodeToPush)254 long InspectorDOMAgent::pushNodePathToFrontend(Node* nodeToPush)
255 {
256 ASSERT(nodeToPush); // Invalid input
257
258 // If we are sending information to the client that is currently being created. Send root node first.
259 pushDocumentElementToFrontend();
260
261 // Return id in case the node is known.
262 long result = idForNode(nodeToPush);
263 if (result)
264 return result;
265
266 Element* element = innerParentElement(nodeToPush);
267 ASSERT(element); // Node is detached or is a document itself
268
269 Vector<Element*> path;
270 while (element && !idForNode(element)) {
271 path.append(element);
272 element = innerParentElement(element);
273 }
274
275 // element is known to the client
276 ASSERT(element);
277 path.append(element);
278 for (int i = path.size() - 1; i >= 0; --i) {
279 long nodeId = idForNode(path.at(i));
280 ASSERT(nodeId);
281 pushChildNodesToFrontend(nodeId);
282 }
283 return idForNode(nodeToPush);
284 }
285
setAttribute(long callId,long elementId,const String & name,const String & value)286 void InspectorDOMAgent::setAttribute(long callId, long elementId, const String& name, const String& value)
287 {
288 Node* node = nodeForId(elementId);
289 if (node && (node->nodeType() == Node::ELEMENT_NODE)) {
290 Element* element = static_cast<Element*>(node);
291 ExceptionCode ec = 0;
292 element->setAttribute(name, value, ec);
293 m_frontend->didApplyDomChange(callId, ec == 0);
294 } else {
295 m_frontend->didApplyDomChange(callId, false);
296 }
297 }
298
removeAttribute(long callId,long elementId,const String & name)299 void InspectorDOMAgent::removeAttribute(long callId, long elementId, const String& name)
300 {
301 Node* node = nodeForId(elementId);
302 if (node && (node->nodeType() == Node::ELEMENT_NODE)) {
303 Element* element = static_cast<Element*>(node);
304 ExceptionCode ec = 0;
305 element->removeAttribute(name, ec);
306 m_frontend->didApplyDomChange(callId, ec == 0);
307 } else {
308 m_frontend->didApplyDomChange(callId, false);
309 }
310 }
311
setTextNodeValue(long callId,long elementId,const String & value)312 void InspectorDOMAgent::setTextNodeValue(long callId, long elementId, const String& value)
313 {
314 Node* node = nodeForId(elementId);
315 if (node && (node->nodeType() == Node::TEXT_NODE)) {
316 Text* text_node = static_cast<Text*>(node);
317 ExceptionCode ec = 0;
318 text_node->replaceWholeText(value, ec);
319 m_frontend->didApplyDomChange(callId, ec == 0);
320 } else {
321 m_frontend->didApplyDomChange(callId, false);
322 }
323 }
324
buildObjectForNode(Node * node,int depth)325 ScriptObject InspectorDOMAgent::buildObjectForNode(Node* node, int depth)
326 {
327 ScriptObject value = m_frontend->newScriptObject();
328
329 long id = bind(node);
330 String nodeName;
331 String nodeValue;
332
333 switch (node->nodeType()) {
334 case Node::TEXT_NODE:
335 case Node::COMMENT_NODE:
336 nodeValue = node->nodeValue();
337 break;
338 case Node::ATTRIBUTE_NODE:
339 case Node::DOCUMENT_NODE:
340 case Node::DOCUMENT_FRAGMENT_NODE:
341 break;
342 case Node::ELEMENT_NODE:
343 default:
344 nodeName = node->nodeName();
345 break;
346 }
347
348 value.set("id", static_cast<int>(id));
349 value.set("nodeType", node->nodeType());
350 value.set("nodeName", nodeName);
351 value.set("nodeValue", nodeValue);
352
353 if (node->nodeType() == Node::ELEMENT_NODE) {
354 Element* element = static_cast<Element*>(node);
355 value.set("attributes", buildArrayForElementAttributes(element));
356 int nodeCount = innerChildNodeCount(element);
357 value.set("childNodeCount", nodeCount);
358
359 ScriptArray children = buildArrayForElementChildren(element, depth);
360 if (children.length() > 0)
361 value.set("children", children);
362 }
363 return value;
364 }
365
buildArrayForElementAttributes(Element * element)366 ScriptArray InspectorDOMAgent::buildArrayForElementAttributes(Element* element)
367 {
368 ScriptArray attributesValue = m_frontend->newScriptArray();
369 // Go through all attributes and serialize them.
370 const NamedNodeMap* attrMap = element->attributes(true);
371 if (!attrMap)
372 return attributesValue;
373 unsigned numAttrs = attrMap->length();
374 int index = 0;
375 for (unsigned i = 0; i < numAttrs; ++i) {
376 // Add attribute pair
377 const Attribute *attribute = attrMap->attributeItem(i);
378 attributesValue.set(index++, attribute->name().toString());
379 attributesValue.set(index++, attribute->value());
380 }
381 return attributesValue;
382 }
383
buildArrayForElementChildren(Element * element,int depth)384 ScriptArray InspectorDOMAgent::buildArrayForElementChildren(Element* element, int depth)
385 {
386 ScriptArray children = m_frontend->newScriptArray();
387 if (depth == 0) {
388 int index = 0;
389 // Special case the_only text child.
390 if (innerChildNodeCount(element) == 1) {
391 Node *child = innerFirstChild(element);
392 if (child->nodeType() == Node::TEXT_NODE)
393 children.set(index++, buildObjectForNode(child, 0));
394 }
395 return children;
396 } else if (depth > 0) {
397 depth--;
398 }
399
400 int index = 0;
401 for (Node *child = innerFirstChild(element); child; child = innerNextSibling(child))
402 children.set(index++, buildObjectForNode(child, depth));
403 return children;
404 }
405
innerFirstChild(Node * node)406 Node* InspectorDOMAgent::innerFirstChild(Node* node)
407 {
408 if (node->isFrameOwnerElement()) {
409 HTMLFrameOwnerElement* frameOwner = static_cast<HTMLFrameOwnerElement*>(node);
410 Document* doc = frameOwner->contentDocument();
411 if (doc) {
412 startListening(doc);
413 return doc->firstChild();
414 }
415 }
416 node = node->firstChild();
417 while (isWhitespace(node))
418 node = node->nextSibling();
419 return node;
420 }
421
innerNextSibling(Node * node)422 Node* InspectorDOMAgent::innerNextSibling(Node* node)
423 {
424 do {
425 node = node->nextSibling();
426 } while (isWhitespace(node));
427 return node;
428 }
429
innerPreviousSibling(Node * node)430 Node* InspectorDOMAgent::innerPreviousSibling(Node* node)
431 {
432 do {
433 node = node->previousSibling();
434 } while (isWhitespace(node));
435 return node;
436 }
437
innerChildNodeCount(Node * node)438 int InspectorDOMAgent::innerChildNodeCount(Node* node)
439 {
440 int count = 0;
441 Node* child = innerFirstChild(node);
442 while (child) {
443 count++;
444 child = innerNextSibling(child);
445 }
446 return count;
447 }
448
innerParentElement(Node * node)449 Element* InspectorDOMAgent::innerParentElement(Node* node)
450 {
451 Element* element = node->parentElement();
452 if (!element)
453 return node->ownerDocument()->ownerElement();
454 return element;
455 }
456
isWhitespace(Node * node)457 bool InspectorDOMAgent::isWhitespace(Node* node)
458 {
459 //TODO: pull ignoreWhitespace setting from the frontend and use here.
460 return node && node->nodeType() == Node::TEXT_NODE && node->nodeValue().stripWhiteSpace().length() == 0;
461 }
462
mainFrameDocument()463 Document* InspectorDOMAgent::mainFrameDocument()
464 {
465 ListHashSet<RefPtr<Document> >::iterator it = m_documents.begin();
466 if (it != m_documents.end())
467 return it->get();
468 return 0;
469 }
470
471 } // namespace WebCore
472