• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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