2011-01-11 Ilya Tikhonovsky <loislo@chromium.org>
[WebKit-https.git] / Source / WebCore / inspector / InspectorDOMAgent.cpp
1 /*
2  * Copyright (C) 2009 Apple Inc. All rights reserved.
3  * Copyright (C) 2009 Google Inc. All rights reserved.
4  * Copyright (C) 2009 Joseph Pecoraro
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1.  Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  * 2.  Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16  *     its contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "InspectorDOMAgent.h"
33
34 #if ENABLE(INSPECTOR)
35
36 #include "Attr.h"
37 #include "CSSComputedStyleDeclaration.h"
38 #include "CSSMutableStyleDeclaration.h"
39 #include "CSSPropertyNames.h"
40 #include "CSSPropertySourceData.h"
41 #include "CSSRule.h"
42 #include "CSSRuleList.h"
43 #include "CSSStyleRule.h"
44 #include "CSSStyleSelector.h"
45 #include "CSSStyleSheet.h"
46 #include "CharacterData.h"
47 #include "ContainerNode.h"
48 #include "Cookie.h"
49 #include "CookieJar.h"
50 #include "DOMWindow.h"
51 #include "Document.h"
52 #include "DocumentType.h"
53 #include "Event.h"
54 #include "EventContext.h"
55 #include "EventListener.h"
56 #include "EventNames.h"
57 #include "EventTarget.h"
58 #include "Frame.h"
59 #include "FrameTree.h"
60 #include "HTMLElement.h"
61 #include "HTMLFrameOwnerElement.h"
62 #include "InspectorFrontend.h"
63 #include "MutationEvent.h"
64 #include "Node.h"
65 #include "NodeList.h"
66 #include "Pasteboard.h"
67 #include "PlatformString.h"
68 #include "RenderStyle.h"
69 #include "RenderStyleConstants.h"
70 #include "ScriptEventListener.h"
71 #include "StyleSheetList.h"
72 #include "Text.h"
73
74 #if ENABLE(XPATH)
75 #include "XPathResult.h"
76 #endif
77
78 #include "markup.h"
79
80 #include <wtf/text/CString.h>
81 #include <wtf/text/StringConcatenate.h>
82 #include <wtf/HashSet.h>
83 #include <wtf/ListHashSet.h>
84 #include <wtf/OwnPtr.h>
85 #include <wtf/Vector.h>
86 #include <wtf/text/AtomicString.h>
87
88 namespace WebCore {
89
90 class MatchJob {
91 public:
92     virtual void match(ListHashSet<Node*>& resultCollector) = 0;
93     virtual ~MatchJob() { }
94
95 protected:
96     MatchJob(Document* document, const String& query)
97         : m_document(document)
98         , m_query(query) { }
99
100     void addNodesToResults(PassRefPtr<NodeList> nodes, ListHashSet<Node*>& resultCollector)
101     {
102         for (unsigned i = 0; nodes && i < nodes->length(); ++i)
103             resultCollector.add(nodes->item(i));
104     }
105
106     RefPtr<Document> m_document;
107     String m_query;
108 };
109
110 namespace {
111
112 class MatchExactIdJob : public WebCore::MatchJob {
113 public:
114     MatchExactIdJob(Document* document, const String& query) : WebCore::MatchJob(document, query) { }
115     virtual ~MatchExactIdJob() { }
116
117 protected:
118     virtual void match(ListHashSet<Node*>& resultCollector)
119     {
120         if (m_query.isEmpty())
121             return;
122
123         Element* element = m_document->getElementById(m_query);
124         if (element)
125             resultCollector.add(element);
126     }
127 };
128
129 class MatchExactClassNamesJob : public WebCore::MatchJob {
130 public:
131     MatchExactClassNamesJob(Document* document, const String& query) : WebCore::MatchJob(document, query) { }
132     virtual ~MatchExactClassNamesJob() { }
133
134     virtual void match(ListHashSet<Node*>& resultCollector)
135     {
136         if (!m_query.isEmpty())
137             addNodesToResults(m_document->getElementsByClassName(m_query), resultCollector);
138     }
139 };
140
141 class MatchExactTagNamesJob : public WebCore::MatchJob {
142 public:
143     MatchExactTagNamesJob(Document* document, const String& query) : WebCore::MatchJob(document, query) { }
144     virtual ~MatchExactTagNamesJob() { }
145
146     virtual void match(ListHashSet<Node*>& resultCollector)
147     {
148         if (!m_query.isEmpty())
149             addNodesToResults(m_document->getElementsByName(m_query), resultCollector);
150     }
151 };
152
153 class MatchQuerySelectorAllJob : public WebCore::MatchJob {
154 public:
155     MatchQuerySelectorAllJob(Document* document, const String& query) : WebCore::MatchJob(document, query) { }
156     virtual ~MatchQuerySelectorAllJob() { }
157
158     virtual void match(ListHashSet<Node*>& resultCollector)
159     {
160         if (m_query.isEmpty())
161             return;
162
163         ExceptionCode ec = 0;
164         RefPtr<NodeList> list = m_document->querySelectorAll(m_query, ec);
165         if (!ec)
166             addNodesToResults(list, resultCollector);
167     }
168 };
169
170 class MatchXPathJob : public WebCore::MatchJob {
171 public:
172     MatchXPathJob(Document* document, const String& query) : WebCore::MatchJob(document, query) { }
173     virtual ~MatchXPathJob() { }
174
175     virtual void match(ListHashSet<Node*>& resultCollector)
176     {
177 #if ENABLE(XPATH)
178         if (m_query.isEmpty())
179             return;
180
181         ExceptionCode ec = 0;
182         RefPtr<XPathResult> result = m_document->evaluate(m_query, m_document.get(), 0, XPathResult::ORDERED_NODE_SNAPSHOT_TYPE, 0, ec);
183         if (ec || !result)
184             return;
185
186         unsigned long size = result->snapshotLength(ec);
187         for (unsigned long i = 0; !ec && i < size; ++i) {
188             Node* node = result->snapshotItem(i, ec);
189             if (!ec)
190                 resultCollector.add(node);
191         }
192 #else
193         UNUSED_PARAM(resultCollector);
194 #endif
195     }
196 };
197
198 class MatchPlainTextJob : public MatchXPathJob {
199 public:
200     MatchPlainTextJob(Document* document, const String& query) : MatchXPathJob(document, query)
201     {
202         m_query = "//text()[contains(., '" + m_query + "')] | //comment()[contains(., '" + m_query + "')]";
203     }
204     virtual ~MatchPlainTextJob() { }
205 };
206
207 enum DOMBreakpointType {
208     SubtreeModified = 0,
209     AttributeModified,
210     NodeRemoved,
211     DOMBreakpointTypesCount
212 };
213
214 const uint32_t inheritableDOMBreakpointTypesMask = (1 << SubtreeModified);
215 const int domBreakpointDerivedTypeShift = 16;
216
217 }
218
219 InspectorDOMAgent::InspectorDOMAgent(InspectorFrontend* frontend)
220     : EventListener(InspectorDOMAgentType)
221     , m_frontend(frontend)
222     , m_domListener(0)
223     , m_lastNodeId(1)
224     , m_matchJobsTimer(this, &InspectorDOMAgent::onMatchJobsTimer)
225 {
226 }
227
228 InspectorDOMAgent::~InspectorDOMAgent()
229 {
230     reset();
231 }
232
233 void InspectorDOMAgent::reset()
234 {
235     searchCanceled();
236     discardBindings();
237
238     ListHashSet<RefPtr<Document> > copy = m_documents;
239     for (ListHashSet<RefPtr<Document> >::iterator it = copy.begin(); it != copy.end(); ++it)
240         stopListening((*it).get());
241
242     ASSERT(!m_documents.size());
243 }
244
245 void InspectorDOMAgent::setDOMListener(DOMListener* listener)
246 {
247     m_domListener = listener;
248 }
249
250 void InspectorDOMAgent::setDocument(Document* doc)
251 {
252     if (doc == mainFrameDocument())
253         return;
254
255     reset();
256
257     if (doc) {
258         startListening(doc);
259         if (doc->documentElement())
260             pushDocumentToFrontend();
261     } else
262         m_frontend->setDocument(InspectorValue::null());
263 }
264
265 void InspectorDOMAgent::releaseDanglingNodes()
266 {
267     deleteAllValues(m_danglingNodeToIdMaps);
268     m_danglingNodeToIdMaps.clear();
269 }
270
271 void InspectorDOMAgent::startListeningFrameDocument(Node* frameOwnerNode)
272 {
273     ASSERT(frameOwnerNode->isFrameOwnerElement());
274     HTMLFrameOwnerElement* frameOwner = static_cast<HTMLFrameOwnerElement*>(frameOwnerNode);
275     Document* doc = frameOwner->contentDocument();
276     if (doc)
277         startListening(doc);
278 }
279
280 void InspectorDOMAgent::startListening(Document* doc)
281 {
282     if (m_documents.contains(doc))
283         return;
284
285     doc->addEventListener(eventNames().DOMContentLoadedEvent, this, false);
286     doc->addEventListener(eventNames().loadEvent, this, true);
287     m_documents.add(doc);
288 }
289
290 void InspectorDOMAgent::stopListening(Document* doc)
291 {
292     if (!m_documents.contains(doc))
293         return;
294
295     doc->removeEventListener(eventNames().DOMContentLoadedEvent, this, false);
296     doc->removeEventListener(eventNames().loadEvent, this, true);
297     m_documents.remove(doc);
298 }
299
300 void InspectorDOMAgent::handleEvent(ScriptExecutionContext*, Event* event)
301 {
302     AtomicString type = event->type();
303     Node* node = event->target()->toNode();
304
305     if (type == eventNames().DOMContentLoadedEvent) {
306         // Re-push document once it is loaded.
307         discardBindings();
308         pushDocumentToFrontend();
309     } else if (type == eventNames().loadEvent) {
310         long frameOwnerId = m_documentNodeToIdMap.get(node);
311         if (!frameOwnerId)
312             return;
313
314         if (!m_childrenRequested.contains(frameOwnerId)) {
315             // No children are mapped yet -> only notify on changes of hasChildren.
316             m_frontend->childNodeCountUpdated(frameOwnerId, innerChildNodeCount(node));
317         } else {
318             // Re-add frame owner element together with its new children.
319             long parentId = m_documentNodeToIdMap.get(innerParentNode(node));
320             m_frontend->childNodeRemoved(parentId, frameOwnerId);
321             RefPtr<InspectorObject> value = buildObjectForNode(node, 0, &m_documentNodeToIdMap);
322             Node* previousSibling = innerPreviousSibling(node);
323             long prevId = previousSibling ? m_documentNodeToIdMap.get(previousSibling) : 0;
324             m_frontend->childNodeInserted(parentId, prevId, value.release());
325             // Invalidate children requested flag for the element.
326             m_childrenRequested.remove(m_childrenRequested.find(frameOwnerId));
327         }
328     }
329 }
330
331 long InspectorDOMAgent::bind(Node* node, NodeToIdMap* nodesMap)
332 {
333     long id = nodesMap->get(node);
334     if (id)
335         return id;
336     id = m_lastNodeId++;
337     nodesMap->set(node, id);
338     m_idToNode.set(id, node);
339     m_idToNodesMap.set(id, nodesMap);
340     return id;
341 }
342
343 void InspectorDOMAgent::unbind(Node* node, NodeToIdMap* nodesMap)
344 {
345     if (node->isFrameOwnerElement()) {
346         const HTMLFrameOwnerElement* frameOwner = static_cast<const HTMLFrameOwnerElement*>(node);
347         stopListening(frameOwner->contentDocument());
348         if (m_domListener)
349             m_domListener->didRemoveDocument(frameOwner->contentDocument());
350     }
351
352     long id = nodesMap->get(node);
353     if (!id)
354         return;
355     m_idToNode.remove(id);
356     nodesMap->remove(node);
357     bool childrenRequested = m_childrenRequested.contains(id);
358     if (childrenRequested) {
359         // Unbind subtree known to client recursively.
360         m_childrenRequested.remove(id);
361         Node* child = innerFirstChild(node);
362         while (child) {
363             unbind(child, nodesMap);
364             child = innerNextSibling(child);
365         }
366     }
367 }
368
369 bool InspectorDOMAgent::pushDocumentToFrontend()
370 {
371     Document* document = mainFrameDocument();
372     if (!document)
373         return false;
374     if (!m_documentNodeToIdMap.contains(document))
375         m_frontend->setDocument(buildObjectForNode(document, 2, &m_documentNodeToIdMap));
376     return true;
377 }
378
379 void InspectorDOMAgent::pushChildNodesToFrontend(long nodeId)
380 {
381     Node* node = nodeForId(nodeId);
382     if (!node || (node->nodeType() != Node::ELEMENT_NODE && node->nodeType() != Node::DOCUMENT_NODE && node->nodeType() != Node::DOCUMENT_FRAGMENT_NODE))
383         return;
384     if (m_childrenRequested.contains(nodeId))
385         return;
386
387     NodeToIdMap* nodeMap = m_idToNodesMap.get(nodeId);
388     RefPtr<InspectorArray> children = buildArrayForContainerChildren(node, 1, nodeMap);
389     m_childrenRequested.add(nodeId);
390     m_frontend->setChildNodes(nodeId, children.release());
391 }
392
393 long InspectorDOMAgent::inspectedNode(unsigned long num)
394 {
395     if (num < m_inspectedNodes.size())
396         return m_inspectedNodes[num];
397     return 0;
398 }
399
400 void InspectorDOMAgent::discardBindings()
401 {
402     m_documentNodeToIdMap.clear();
403     m_idToNode.clear();
404     releaseDanglingNodes();
405     m_childrenRequested.clear();
406     m_inspectedNodes.clear();
407     m_breakpoints.clear();
408 }
409
410 Node* InspectorDOMAgent::nodeForId(long id)
411 {
412     if (!id)
413         return 0;
414
415     HashMap<long, Node*>::iterator it = m_idToNode.find(id);
416     if (it != m_idToNode.end())
417         return it->second;
418     return 0;
419 }
420
421 void InspectorDOMAgent::getChildNodes(long nodeId)
422 {
423     pushChildNodesToFrontend(nodeId);
424 }
425
426 long InspectorDOMAgent::pushNodePathToFrontend(Node* nodeToPush)
427 {
428     ASSERT(nodeToPush);  // Invalid input
429
430     // If we are sending information to the client that is currently being created. Send root node first.
431     if (!pushDocumentToFrontend())
432         return 0;
433
434     // Return id in case the node is known.
435     long result = m_documentNodeToIdMap.get(nodeToPush);
436     if (result)
437         return result;
438
439     Node* node = nodeToPush;
440     Vector<Node*> path;
441     NodeToIdMap* danglingMap = 0;
442     while (true) {
443         Node* parent = innerParentNode(node);
444         if (!parent) {
445             // Node being pushed is detached -> push subtree root.
446             danglingMap = new NodeToIdMap();
447             m_danglingNodeToIdMaps.append(danglingMap);
448             m_frontend->setDetachedRoot(buildObjectForNode(node, 0, danglingMap));
449             break;
450         } else {
451             path.append(parent);
452             if (m_documentNodeToIdMap.get(parent))
453                 break;
454             else
455                 node = parent;
456         }
457     }
458
459     NodeToIdMap* map = danglingMap ? danglingMap : &m_documentNodeToIdMap;
460     for (int i = path.size() - 1; i >= 0; --i) {
461         long nodeId = map->get(path.at(i));
462         ASSERT(nodeId);
463         pushChildNodesToFrontend(nodeId);
464     }
465     return map->get(nodeToPush);
466 }
467
468 void InspectorDOMAgent::setAttribute(long elementId, const String& name, const String& value, bool* success)
469 {
470     Node* node = nodeForId(elementId);
471     if (node && (node->nodeType() == Node::ELEMENT_NODE)) {
472         Element* element = static_cast<Element*>(node);
473         ExceptionCode ec = 0;
474         element->setAttribute(name, value, ec);
475         *success = !ec;
476     }
477 }
478
479 void InspectorDOMAgent::removeAttribute(long elementId, const String& name, bool* success)
480 {
481     Node* node = nodeForId(elementId);
482     if (node && (node->nodeType() == Node::ELEMENT_NODE)) {
483         Element* element = static_cast<Element*>(node);
484         ExceptionCode ec = 0;
485         element->removeAttribute(name, ec);
486         *success = !ec;
487     }
488 }
489
490 void InspectorDOMAgent::removeNode(long nodeId, long* outNodeId)
491 {
492     Node* node = nodeForId(nodeId);
493     if (!node)
494         return;
495
496     ContainerNode* parentNode = node->parentNode();
497     if (!parentNode)
498         return;
499
500     ExceptionCode ec = 0;
501     parentNode->removeChild(node, ec);
502     if (ec)
503         return;
504
505     *outNodeId = nodeId;
506 }
507
508 void InspectorDOMAgent::changeTagName(long nodeId, const String& tagName, long* newId)
509 {
510     Node* oldNode = nodeForId(nodeId);
511     if (!oldNode || !oldNode->isElementNode())
512         return;
513
514     ExceptionCode ec = 0;
515     RefPtr<Element> newElem = oldNode->document()->createElement(tagName, ec);
516     if (ec)
517         return;
518
519     // Copy over the original node's attributes.
520     Element* oldElem = static_cast<Element*>(oldNode);
521     newElem->copyNonAttributeProperties(oldElem);
522     if (oldElem->attributes())
523         newElem->attributes()->setAttributes(*(oldElem->attributes(true)));
524
525     // Copy over the original node's children.
526     Node* child;
527     while ((child = oldNode->firstChild()))
528         newElem->appendChild(child, ec);
529
530     // Replace the old node with the new node
531     ContainerNode* parent = oldNode->parentNode();
532     parent->insertBefore(newElem, oldNode->nextSibling(), ec);
533     parent->removeChild(oldNode, ec);
534
535     if (ec)
536         return;
537
538     *newId = pushNodePathToFrontend(newElem.get());
539     if (m_childrenRequested.contains(nodeId))
540         pushChildNodesToFrontend(*newId);
541 }
542
543 void InspectorDOMAgent::getOuterHTML(long nodeId, WTF::String* outerHTML)
544 {
545     Node* node = nodeForId(nodeId);
546     if (!node || !node->isHTMLElement())
547         return;
548
549     *outerHTML = static_cast<HTMLElement*>(node)->outerHTML();
550 }
551
552 void InspectorDOMAgent::setOuterHTML(long nodeId, const String& outerHTML, long* newId)
553 {
554     Node* node = nodeForId(nodeId);
555     if (!node || !node->isHTMLElement())
556         return;
557
558     bool requiresTotalUpdate = node->nodeName() == "HTML" || node->nodeName() == "BODY" || node->nodeName() == "HEAD";
559
560     bool childrenRequested = m_childrenRequested.contains(nodeId);
561     Node* previousSibling = node->previousSibling();
562     ContainerNode* parentNode = node->parentNode();
563
564     HTMLElement* htmlElement = static_cast<HTMLElement*>(node);
565     ExceptionCode ec = 0;
566     htmlElement->setOuterHTML(outerHTML, ec);
567     if (ec)
568         return;
569
570     if (requiresTotalUpdate) {
571         Document* document = mainFrameDocument();
572         reset();
573         setDocument(document);
574         *newId = 0;
575         return;
576     }
577
578     Node* newNode = previousSibling ? previousSibling->nextSibling() : parentNode->firstChild();
579     if (!newNode) {
580         // The only child node has been deleted.
581         *newId = 0;
582         return;
583     }
584
585     *newId = pushNodePathToFrontend(newNode);
586     if (childrenRequested)
587         pushChildNodesToFrontend(*newId);
588 }
589
590 void InspectorDOMAgent::setTextNodeValue(long nodeId, const String& value, bool* success)
591 {
592     Node* node = nodeForId(nodeId);
593     if (node && (node->nodeType() == Node::TEXT_NODE)) {
594         Text* text_node = static_cast<Text*>(node);
595         ExceptionCode ec = 0;
596         text_node->replaceWholeText(value, ec);
597         *success = !ec;
598     }
599 }
600
601 void InspectorDOMAgent::getEventListenersForNode(long nodeId, long* outNodeId, RefPtr<InspectorArray>* listenersArray)
602 {
603     Node* node = nodeForId(nodeId);
604     *outNodeId = nodeId;
605     EventTargetData* d;
606
607     // Quick break if a null node or no listeners at all
608     if (!node || !(d = node->eventTargetData()))
609         return;
610
611     // Get the list of event types this Node is concerned with
612     Vector<AtomicString> eventTypes;
613     const EventListenerMap& listenerMap = d->eventListenerMap;
614     EventListenerMap::const_iterator end = listenerMap.end();
615     for (EventListenerMap::const_iterator iter = listenerMap.begin(); iter != end; ++iter)
616         eventTypes.append(iter->first);
617
618     // Quick break if no useful listeners
619     size_t eventTypesLength = eventTypes.size();
620     if (!eventTypesLength)
621         return;
622
623     // The Node's Event Ancestors (not including self)
624     Vector<EventContext> ancestors;
625     node->getEventAncestors(ancestors, node);
626
627     // Nodes and their Listeners for the concerned event types (order is top to bottom)
628     Vector<EventListenerInfo> eventInformation;
629     for (size_t i = ancestors.size(); i; --i) {
630         Node* ancestor = ancestors[i - 1].node();
631         for (size_t j = 0; j < eventTypesLength; ++j) {
632             AtomicString& type = eventTypes[j];
633             if (ancestor->hasEventListeners(type))
634                 eventInformation.append(EventListenerInfo(static_cast<Node*>(ancestor), type, ancestor->getEventListeners(type)));
635         }
636     }
637
638     // Insert the Current Node at the end of that list (last in capturing, first in bubbling)
639     for (size_t i = 0; i < eventTypesLength; ++i) {
640         const AtomicString& type = eventTypes[i];
641         eventInformation.append(EventListenerInfo(node, type, node->getEventListeners(type)));
642     }
643
644     // Get Capturing Listeners (in this order)
645     size_t eventInformationLength = eventInformation.size();
646     for (size_t i = 0; i < eventInformationLength; ++i) {
647         const EventListenerInfo& info = eventInformation[i];
648         const EventListenerVector& vector = info.eventListenerVector;
649         for (size_t j = 0; j < vector.size(); ++j) {
650             const RegisteredEventListener& listener = vector[j];
651             if (listener.useCapture)
652                 (*listenersArray)->pushObject(buildObjectForEventListener(listener, info.eventType, info.node));
653         }
654     }
655
656     // Get Bubbling Listeners (reverse order)
657     for (size_t i = eventInformationLength; i; --i) {
658         const EventListenerInfo& info = eventInformation[i - 1];
659         const EventListenerVector& vector = info.eventListenerVector;
660         for (size_t j = 0; j < vector.size(); ++j) {
661             const RegisteredEventListener& listener = vector[j];
662             if (!listener.useCapture)
663                 (*listenersArray)->pushObject(buildObjectForEventListener(listener, info.eventType, info.node));
664         }
665     }
666 }
667
668 void InspectorDOMAgent::addInspectedNode(long nodeId)
669 {
670     m_inspectedNodes.prepend(nodeId);
671     while (m_inspectedNodes.size() > 5)
672         m_inspectedNodes.removeLast();
673 }
674
675 void InspectorDOMAgent::performSearch(const String& whitespaceTrimmedQuery, bool runSynchronously)
676 {
677     // FIXME: Few things are missing here:
678     // 1) Search works with node granularity - number of matches within node is not calculated.
679     // 2) There is no need to push all search results to the front-end at a time, pushing next / previous result
680     //    is sufficient.
681
682     unsigned queryLength = whitespaceTrimmedQuery.length();
683     bool startTagFound = !whitespaceTrimmedQuery.find('<');
684     bool endTagFound = whitespaceTrimmedQuery.reverseFind('>') + 1 == queryLength;
685
686     String tagNameQuery = whitespaceTrimmedQuery;
687     if (startTagFound || endTagFound)
688         tagNameQuery = tagNameQuery.substring(startTagFound ? 1 : 0, endTagFound ? queryLength - 1 : queryLength);
689     if (!Document::isValidName(tagNameQuery))
690         tagNameQuery = "";
691
692     String attributeNameQuery = whitespaceTrimmedQuery;
693     if (!Document::isValidName(attributeNameQuery))
694         attributeNameQuery = "";
695
696     String escapedQuery = whitespaceTrimmedQuery;
697     escapedQuery.replace("'", "\\'");
698     String escapedTagNameQuery = tagNameQuery;
699     escapedTagNameQuery.replace("'", "\\'");
700
701     // Clear pending jobs.
702     searchCanceled();
703
704     // Find all frames, iframes and object elements to search their documents.
705     for (Frame* frame = mainFrameDocument()->frame(); frame; frame = frame->tree()->traverseNext()) {
706         Document* document = frame->document();
707         if (!document)
708             continue;
709
710         if (!tagNameQuery.isEmpty() && startTagFound && endTagFound) {
711             m_pendingMatchJobs.append(new MatchExactTagNamesJob(document, tagNameQuery));
712             m_pendingMatchJobs.append(new MatchPlainTextJob(document, escapedQuery));
713             continue;
714         }
715
716         if (!tagNameQuery.isEmpty() && startTagFound) {
717             m_pendingMatchJobs.append(new MatchXPathJob(document, "//*[starts-with(name(), '" + escapedTagNameQuery + "')]"));
718             m_pendingMatchJobs.append(new MatchPlainTextJob(document, escapedQuery));
719             continue;
720         }
721
722         if (!tagNameQuery.isEmpty() && endTagFound) {
723             // FIXME: we should have a matchEndOfTagNames search function if endTagFound is true but not startTagFound.
724             // This requires ends-with() support in XPath, WebKit only supports starts-with() and contains().
725             m_pendingMatchJobs.append(new MatchXPathJob(document, "//*[contains(name(), '" + escapedTagNameQuery + "')]"));
726             m_pendingMatchJobs.append(new MatchPlainTextJob(document, escapedQuery));
727             continue;
728         }
729
730         bool matchesEveryNode = whitespaceTrimmedQuery == "//*" || whitespaceTrimmedQuery == "*";
731         if (matchesEveryNode) {
732             // These queries will match every node. Matching everything isn't useful and can be slow for large pages,
733             // so limit the search functions list to plain text and attribute matching for these.
734             m_pendingMatchJobs.append(new MatchXPathJob(document, "//*[contains(@*, '" + escapedQuery + "')]"));
735             m_pendingMatchJobs.append(new MatchPlainTextJob(document, escapedQuery));
736             continue;
737         }
738
739         m_pendingMatchJobs.append(new MatchExactIdJob(document, whitespaceTrimmedQuery));
740         m_pendingMatchJobs.append(new MatchExactClassNamesJob(document, whitespaceTrimmedQuery));
741         m_pendingMatchJobs.append(new MatchExactTagNamesJob(document, tagNameQuery));
742         m_pendingMatchJobs.append(new MatchQuerySelectorAllJob(document, "[" + attributeNameQuery + "]"));
743         m_pendingMatchJobs.append(new MatchQuerySelectorAllJob(document, whitespaceTrimmedQuery));
744         m_pendingMatchJobs.append(new MatchXPathJob(document, "//*[contains(@*, '" + escapedQuery + "')]"));
745         if (!tagNameQuery.isEmpty())
746             m_pendingMatchJobs.append(new MatchXPathJob(document, "//*[contains(name(), '" + escapedTagNameQuery + "')]"));
747         m_pendingMatchJobs.append(new MatchPlainTextJob(document, escapedQuery));
748         m_pendingMatchJobs.append(new MatchXPathJob(document, whitespaceTrimmedQuery));
749     }
750
751     if (runSynchronously) {
752         // For tests.
753         ListHashSet<Node*> resultCollector;
754         for (Deque<MatchJob*>::iterator it = m_pendingMatchJobs.begin(); it != m_pendingMatchJobs.end(); ++it)
755             (*it)->match(resultCollector);
756         reportNodesAsSearchResults(resultCollector);
757         searchCanceled();
758         return;
759     }
760     m_matchJobsTimer.startOneShot(0);
761 }
762
763 void InspectorDOMAgent::searchCanceled()
764 {
765     if (m_matchJobsTimer.isActive())
766         m_matchJobsTimer.stop();
767     deleteAllValues(m_pendingMatchJobs);
768     m_pendingMatchJobs.clear();
769     m_searchResults.clear();
770 }
771
772 void InspectorDOMAgent::setDOMBreakpoint(long nodeId, long type)
773 {
774     Node* node = nodeForId(nodeId);
775     if (!node)
776         return;
777
778     uint32_t rootBit = 1 << type;
779     m_breakpoints.set(node, m_breakpoints.get(node) | rootBit);
780     if (rootBit & inheritableDOMBreakpointTypesMask) {
781         for (Node* child = innerFirstChild(node); child; child = innerNextSibling(child))
782             updateSubtreeBreakpoints(child, rootBit, true);
783     }
784 }
785
786 void InspectorDOMAgent::removeDOMBreakpoint(long nodeId, long type)
787 {
788     Node* node = nodeForId(nodeId);
789     if (!node)
790         return;
791
792     uint32_t rootBit = 1 << type;
793     uint32_t mask = m_breakpoints.get(node) & ~rootBit;
794     if (mask)
795         m_breakpoints.set(node, mask);
796     else
797         m_breakpoints.remove(node);
798
799     if ((rootBit & inheritableDOMBreakpointTypesMask) && !(mask & (rootBit << domBreakpointDerivedTypeShift))) {
800         for (Node* child = innerFirstChild(node); child; child = innerNextSibling(child))
801             updateSubtreeBreakpoints(child, rootBit, false);
802     }
803 }
804
805 bool InspectorDOMAgent::shouldBreakOnNodeInsertion(Node*, Node* parent, PassRefPtr<InspectorObject> details)
806 {
807     if (hasBreakpoint(parent, SubtreeModified)) {
808         descriptionForDOMEvent(parent, SubtreeModified, true, details);
809         return true;
810     }
811     return false;
812 }
813
814 bool InspectorDOMAgent::shouldBreakOnNodeRemoval(Node* node, PassRefPtr<InspectorObject> details)
815 {
816     if (hasBreakpoint(node, NodeRemoved)) {
817         descriptionForDOMEvent(node, NodeRemoved, false, details);
818         return true;
819     }
820     if (hasBreakpoint(innerParentNode(node), SubtreeModified)) {
821         descriptionForDOMEvent(node, SubtreeModified, false, details);
822         return true;
823     }
824     return false;
825 }
826
827 bool InspectorDOMAgent::shouldBreakOnAttributeModification(Element* element, PassRefPtr<InspectorObject> details)
828 {
829     if (hasBreakpoint(element, AttributeModified)) {
830         descriptionForDOMEvent(element, AttributeModified, false, details);
831         return true;
832     }
833     return false;
834 }
835
836 void InspectorDOMAgent::descriptionForDOMEvent(Node* target, long breakpointType, bool insertion, PassRefPtr<InspectorObject> description)
837 {
838     ASSERT(hasBreakpoint(target, breakpointType));
839
840     Node* breakpointOwner = target;
841     if ((1 << breakpointType) & inheritableDOMBreakpointTypesMask) {
842         // For inheritable breakpoint types, target node isn't always the same as the node that owns a breakpoint.
843         // Target node may be unknown to frontend, so we need to push it first.
844         long targetNodeId = pushNodePathToFrontend(target);
845         ASSERT(targetNodeId);
846         description->setNumber("targetNodeId", targetNodeId);
847
848         // Find breakpoint owner node.
849         if (!insertion)
850             breakpointOwner = innerParentNode(target);
851         ASSERT(breakpointOwner);
852         while (!(m_breakpoints.get(breakpointOwner) & (1 << breakpointType))) {
853             breakpointOwner = innerParentNode(breakpointOwner);
854             ASSERT(breakpointOwner);
855         }
856
857         if (breakpointType == SubtreeModified)
858             description->setBoolean("insertion", insertion);
859     }
860
861     long breakpointOwnerNodeId = m_documentNodeToIdMap.get(breakpointOwner);
862     ASSERT(breakpointOwnerNodeId);
863     description->setNumber("nodeId", breakpointOwnerNodeId);
864     description->setNumber("type", breakpointType);
865 }
866
867 String InspectorDOMAgent::documentURLString(Document* document) const
868 {
869     if (!document || document->url().isNull())
870         return "";
871     return document->url().string();
872 }
873
874 PassRefPtr<InspectorObject> InspectorDOMAgent::buildObjectForNode(Node* node, int depth, NodeToIdMap* nodesMap)
875 {
876     RefPtr<InspectorObject> value = InspectorObject::create();
877
878     long id = bind(node, nodesMap);
879     String nodeName;
880     String localName;
881     String nodeValue;
882
883     switch (node->nodeType()) {
884         case Node::TEXT_NODE:
885         case Node::COMMENT_NODE:
886         case Node::CDATA_SECTION_NODE:
887             nodeValue = node->nodeValue();
888             break;
889         case Node::ATTRIBUTE_NODE:
890             localName = node->localName();
891             break;
892         case Node::DOCUMENT_FRAGMENT_NODE:
893             break;
894         case Node::DOCUMENT_NODE:
895         case Node::ELEMENT_NODE:
896         default:
897             nodeName = node->nodeName();
898             localName = node->localName();
899             break;
900     }
901
902     value->setNumber("id", id);
903     value->setNumber("nodeType", node->nodeType());
904     value->setString("nodeName", nodeName);
905     value->setString("localName", localName);
906     value->setString("nodeValue", nodeValue);
907
908     if (node->nodeType() == Node::ELEMENT_NODE || node->nodeType() == Node::DOCUMENT_NODE || node->nodeType() == Node::DOCUMENT_FRAGMENT_NODE) {
909         int nodeCount = innerChildNodeCount(node);
910         value->setNumber("childNodeCount", nodeCount);
911         RefPtr<InspectorArray> children = buildArrayForContainerChildren(node, depth, nodesMap);
912         if (children->length() > 0)
913             value->setArray("children", children.release());
914
915         if (node->nodeType() == Node::ELEMENT_NODE) {
916             Element* element = static_cast<Element*>(node);
917             value->setArray("attributes", buildArrayForElementAttributes(element));
918             if (node->isFrameOwnerElement()) {
919                 HTMLFrameOwnerElement* frameOwner = static_cast<HTMLFrameOwnerElement*>(node);
920                 value->setString("documentURL", documentURLString(frameOwner->contentDocument()));
921             }
922         } else if (node->nodeType() == Node::DOCUMENT_NODE) {
923             Document* document = static_cast<Document*>(node);
924             value->setString("documentURL", documentURLString(document));
925         }
926     } else if (node->nodeType() == Node::DOCUMENT_TYPE_NODE) {
927         DocumentType* docType = static_cast<DocumentType*>(node);
928         value->setString("publicId", docType->publicId());
929         value->setString("systemId", docType->systemId());
930         value->setString("internalSubset", docType->internalSubset());
931     } else if (node->nodeType() == Node::ATTRIBUTE_NODE) {
932         Attr* attribute = static_cast<Attr*>(node);
933         value->setString("name", attribute->name());
934         value->setString("value", attribute->value());
935     }
936     return value.release();
937 }
938
939 PassRefPtr<InspectorArray> InspectorDOMAgent::buildArrayForElementAttributes(Element* element)
940 {
941     RefPtr<InspectorArray> attributesValue = InspectorArray::create();
942     // Go through all attributes and serialize them.
943     const NamedNodeMap* attrMap = element->attributes(true);
944     if (!attrMap)
945         return attributesValue.release();
946     unsigned numAttrs = attrMap->length();
947     for (unsigned i = 0; i < numAttrs; ++i) {
948         // Add attribute pair
949         const Attribute *attribute = attrMap->attributeItem(i);
950         attributesValue->pushString(attribute->name().toString());
951         attributesValue->pushString(attribute->value());
952     }
953     return attributesValue.release();
954 }
955
956 PassRefPtr<InspectorArray> InspectorDOMAgent::buildArrayForContainerChildren(Node* container, int depth, NodeToIdMap* nodesMap)
957 {
958     RefPtr<InspectorArray> children = InspectorArray::create();
959     Node* child = innerFirstChild(container);
960
961     if (depth == 0) {
962         // Special case the_only text child.
963         if (child && child->nodeType() == Node::TEXT_NODE && !innerNextSibling(child))
964             children->pushObject(buildObjectForNode(child, 0, nodesMap));
965         return children.release();
966     } else if (depth > 0) {
967         depth--;
968     }
969
970     if (container->isFrameOwnerElement())
971         startListeningFrameDocument(container);
972
973     while (child) {
974         children->pushObject(buildObjectForNode(child, depth, nodesMap));
975         child = innerNextSibling(child);
976     }
977     return children.release();
978 }
979
980 PassRefPtr<InspectorObject> InspectorDOMAgent::buildObjectForEventListener(const RegisteredEventListener& registeredEventListener, const AtomicString& eventType, Node* node)
981 {
982     RefPtr<EventListener> eventListener = registeredEventListener.listener;
983     RefPtr<InspectorObject> value = InspectorObject::create();
984     value->setString("type", eventType);
985     value->setBoolean("useCapture", registeredEventListener.useCapture);
986     value->setBoolean("isAttribute", eventListener->isAttribute());
987     value->setNumber("nodeId", pushNodePathToFrontend(node));
988     value->setString("listenerBody", eventListenerHandlerBody(node->document(), eventListener.get()));
989     String sourceName;
990     int lineNumber;
991     if (eventListenerHandlerLocation(node->document(), eventListener.get(), sourceName, lineNumber)) {
992         value->setString("sourceName", sourceName);
993         value->setNumber("lineNumber", lineNumber);
994     }
995     return value.release();
996 }
997
998 Node* InspectorDOMAgent::innerFirstChild(Node* node)
999 {
1000     if (node->isFrameOwnerElement()) {
1001         HTMLFrameOwnerElement* frameOwner = static_cast<HTMLFrameOwnerElement*>(node);
1002         Document* doc = frameOwner->contentDocument();
1003         if (doc)
1004             return doc->firstChild();
1005     }
1006     node = node->firstChild();
1007     while (isWhitespace(node))
1008         node = node->nextSibling();
1009     return node;
1010 }
1011
1012 Node* InspectorDOMAgent::innerNextSibling(Node* node)
1013 {
1014     do {
1015         node = node->nextSibling();
1016     } while (isWhitespace(node));
1017     return node;
1018 }
1019
1020 Node* InspectorDOMAgent::innerPreviousSibling(Node* node)
1021 {
1022     do {
1023         node = node->previousSibling();
1024     } while (isWhitespace(node));
1025     return node;
1026 }
1027
1028 unsigned InspectorDOMAgent::innerChildNodeCount(Node* node)
1029 {
1030     unsigned count = 0;
1031     Node* child = innerFirstChild(node);
1032     while (child) {
1033         count++;
1034         child = innerNextSibling(child);
1035     }
1036     return count;
1037 }
1038
1039 Node* InspectorDOMAgent::innerParentNode(Node* node)
1040 {
1041     ContainerNode* parent = node->parentNode();
1042     if (parent && parent->isDocumentNode())
1043         return static_cast<Document*>(parent)->ownerElement();
1044     return parent;
1045 }
1046
1047 bool InspectorDOMAgent::isWhitespace(Node* node)
1048 {
1049     //TODO: pull ignoreWhitespace setting from the frontend and use here.
1050     return node && node->nodeType() == Node::TEXT_NODE && node->nodeValue().stripWhiteSpace().length() == 0;
1051 }
1052
1053 Document* InspectorDOMAgent::mainFrameDocument() const
1054 {
1055     ListHashSet<RefPtr<Document> >::const_iterator it = m_documents.begin();
1056     if (it != m_documents.end())
1057         return it->get();
1058     return 0;
1059 }
1060
1061 bool InspectorDOMAgent::operator==(const EventListener& listener)
1062 {
1063     if (const InspectorDOMAgent* inspectorDOMAgentListener = InspectorDOMAgent::cast(&listener))
1064         return mainFrameDocument() == inspectorDOMAgentListener->mainFrameDocument();
1065     return false;
1066 }
1067
1068 void InspectorDOMAgent::didInsertDOMNode(Node* node)
1069 {
1070     if (isWhitespace(node))
1071         return;
1072
1073     if (m_breakpoints.size()) {
1074         uint32_t mask = m_breakpoints.get(innerParentNode(node));
1075         uint32_t inheritableTypesMask = (mask | (mask >> domBreakpointDerivedTypeShift)) & inheritableDOMBreakpointTypesMask;
1076         if (inheritableTypesMask)
1077             updateSubtreeBreakpoints(node, inheritableTypesMask, true);
1078     }
1079
1080     // We could be attaching existing subtree. Forget the bindings.
1081     unbind(node, &m_documentNodeToIdMap);
1082
1083     ContainerNode* parent = node->parentNode();
1084     long parentId = m_documentNodeToIdMap.get(parent);
1085     // Return if parent is not mapped yet.
1086     if (!parentId)
1087         return;
1088
1089     if (!m_childrenRequested.contains(parentId)) {
1090         // No children are mapped yet -> only notify on changes of hasChildren.
1091         m_frontend->childNodeCountUpdated(parentId, innerChildNodeCount(parent));
1092     } else {
1093         // Children have been requested -> return value of a new child.
1094         Node* prevSibling = innerPreviousSibling(node);
1095         long prevId = prevSibling ? m_documentNodeToIdMap.get(prevSibling) : 0;
1096         RefPtr<InspectorObject> value = buildObjectForNode(node, 0, &m_documentNodeToIdMap);
1097         m_frontend->childNodeInserted(parentId, prevId, value.release());
1098     }
1099 }
1100
1101 void InspectorDOMAgent::didRemoveDOMNode(Node* node)
1102 {
1103     if (isWhitespace(node))
1104         return;
1105
1106     if (m_breakpoints.size()) {
1107         // Remove subtree breakpoints.
1108         m_breakpoints.remove(node);
1109         Vector<Node*> stack(1, innerFirstChild(node));
1110         do {
1111             Node* node = stack.last();
1112             stack.removeLast();
1113             if (!node)
1114                 continue;
1115             m_breakpoints.remove(node);
1116             stack.append(innerFirstChild(node));
1117             stack.append(innerNextSibling(node));
1118         } while (!stack.isEmpty());
1119     }
1120
1121     ContainerNode* parent = node->parentNode();
1122     long parentId = m_documentNodeToIdMap.get(parent);
1123     // If parent is not mapped yet -> ignore the event.
1124     if (!parentId)
1125         return;
1126
1127     if (m_domListener)
1128         m_domListener->didRemoveDOMNode(node);
1129
1130     if (!m_childrenRequested.contains(parentId)) {
1131         // No children are mapped yet -> only notify on changes of hasChildren.
1132         if (innerChildNodeCount(parent) == 1)
1133             m_frontend->childNodeCountUpdated(parentId, 0);
1134     } else
1135         m_frontend->childNodeRemoved(parentId, m_documentNodeToIdMap.get(node));
1136     unbind(node, &m_documentNodeToIdMap);
1137 }
1138
1139 void InspectorDOMAgent::didModifyDOMAttr(Element* element)
1140 {
1141     long id = m_documentNodeToIdMap.get(element);
1142     // If node is not mapped yet -> ignore the event.
1143     if (!id)
1144         return;
1145
1146     if (m_domListener)
1147         m_domListener->didModifyDOMAttr(element);
1148
1149     m_frontend->attributesUpdated(id, buildArrayForElementAttributes(element));
1150 }
1151
1152 void InspectorDOMAgent::characterDataModified(CharacterData* characterData)
1153 {
1154     long id = m_documentNodeToIdMap.get(characterData);
1155     if (!id)
1156         return;
1157     m_frontend->characterDataModified(id, characterData->data());
1158 }
1159
1160 bool InspectorDOMAgent::hasBreakpoint(Node* node, long type)
1161 {
1162     uint32_t rootBit = 1 << type;
1163     uint32_t derivedBit = rootBit << domBreakpointDerivedTypeShift;
1164     return m_breakpoints.get(node) & (rootBit | derivedBit);
1165 }
1166
1167 void InspectorDOMAgent::updateSubtreeBreakpoints(Node* node, uint32_t rootMask, bool set)
1168 {
1169     uint32_t oldMask = m_breakpoints.get(node);
1170     uint32_t derivedMask = rootMask << domBreakpointDerivedTypeShift;
1171     uint32_t newMask = set ? oldMask | derivedMask : oldMask & ~derivedMask;
1172     if (newMask)
1173         m_breakpoints.set(node, newMask);
1174     else
1175         m_breakpoints.remove(node);
1176
1177     uint32_t newRootMask = rootMask & ~newMask;
1178     if (!newRootMask)
1179         return;
1180
1181     for (Node* child = innerFirstChild(node); child; child = innerNextSibling(child))
1182         updateSubtreeBreakpoints(child, newRootMask, set);
1183 }
1184
1185 Node* InspectorDOMAgent::nodeForPath(const String& path)
1186 {
1187     // The path is of form "1,HTML,2,BODY,1,DIV"
1188     Node* node = mainFrameDocument();
1189     if (!node)
1190         return 0;
1191
1192     Vector<String> pathTokens;
1193     path.split(",", false, pathTokens);
1194     if (!pathTokens.size())
1195         return 0;
1196     for (size_t i = 0; i < pathTokens.size() - 1; i += 2) {
1197         bool success = true;
1198         unsigned childNumber = pathTokens[i].toUInt(&success);
1199         if (!success)
1200             return 0;
1201         if (childNumber >= innerChildNodeCount(node))
1202             return 0;
1203
1204         Node* child = innerFirstChild(node);
1205         String childName = pathTokens[i + 1];
1206         for (size_t j = 0; child && j < childNumber; ++j)
1207             child = innerNextSibling(child);
1208
1209         if (!child || child->nodeName() != childName)
1210             return 0;
1211         node = child;
1212     }
1213     return node;
1214 }
1215
1216 PassRefPtr<InspectorArray> InspectorDOMAgent::toArray(const Vector<String>& data)
1217 {
1218     RefPtr<InspectorArray> result = InspectorArray::create();
1219     for (unsigned i = 0; i < data.size(); ++i)
1220         result->pushString(data[i]);
1221     return result.release();
1222 }
1223
1224 void InspectorDOMAgent::onMatchJobsTimer(Timer<InspectorDOMAgent>*)
1225 {
1226     if (!m_pendingMatchJobs.size()) {
1227         searchCanceled();
1228         return;
1229     }
1230
1231     ListHashSet<Node*> resultCollector;
1232     MatchJob* job = m_pendingMatchJobs.takeFirst();
1233     job->match(resultCollector);
1234     delete job;
1235
1236     reportNodesAsSearchResults(resultCollector);
1237
1238     m_matchJobsTimer.startOneShot(0.025);
1239 }
1240
1241 void InspectorDOMAgent::reportNodesAsSearchResults(ListHashSet<Node*>& resultCollector)
1242 {
1243     RefPtr<InspectorArray> nodeIds = InspectorArray::create();
1244     for (ListHashSet<Node*>::iterator it = resultCollector.begin(); it != resultCollector.end(); ++it) {
1245         if (m_searchResults.contains(*it))
1246             continue;
1247         m_searchResults.add(*it);
1248         nodeIds->pushNumber(static_cast<long long>(pushNodePathToFrontend(*it)));
1249     }
1250     m_frontend->addNodesToSearchResult(nodeIds.release());
1251 }
1252
1253 void InspectorDOMAgent::copyNode(long nodeId)
1254 {
1255     Node* node = nodeForId(nodeId);
1256     if (!node)
1257         return;
1258     String markup = createMarkup(node);
1259     Pasteboard::generalPasteboard()->writePlainText(markup);
1260 }
1261
1262 void InspectorDOMAgent::pushNodeByPathToFrontend(const String& path, long* nodeId)
1263 {
1264     if (Node* node = nodeForPath(path))
1265         *nodeId = pushNodePathToFrontend(node);
1266 }
1267
1268 } // namespace WebCore
1269
1270 #endif // ENABLE(INSPECTOR)