/* * Copyright (C) 2009 Apple Inc. All rights reserved. * Copyright (C) 2009 Google Inc. All rights reserved. * Copyright (C) 2009 Joseph Pecoraro * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "InspectorDOMAgent.h" #if ENABLE(INSPECTOR) #include "AtomicString.h" #include "ContainerNode.h" #include "Cookie.h" #include "CookieJar.h" #include "DOMWindow.h" #include "Document.h" #include "Event.h" #include "EventListener.h" #include "EventNames.h" #include "EventTarget.h" #include "HTMLFrameOwnerElement.h" #include "InspectorFrontend.h" #include "markup.h" #include "MutationEvent.h" #include "Node.h" #include "NodeList.h" #include "PlatformString.h" #include "ScriptEventListener.h" #include "ScriptObject.h" #include "Text.h" #include #include namespace WebCore { InspectorDOMAgent::InspectorDOMAgent(InspectorFrontend* frontend) : EventListener(InspectorDOMAgentType) , m_frontend(frontend) , m_lastNodeId(1) { } InspectorDOMAgent::~InspectorDOMAgent() { setDocument(0); } void InspectorDOMAgent::setDocument(Document* doc) { if (doc == mainFrameDocument()) return; discardBindings(); ListHashSet > copy = m_documents; for (ListHashSet >::iterator it = copy.begin(); it != copy.end(); ++it) stopListening((*it).get()); ASSERT(!m_documents.size()); if (doc) { startListening(doc); if (doc->documentElement()) { pushDocumentToFrontend(); } } } void InspectorDOMAgent::releaseDanglingNodes() { deleteAllValues(m_danglingNodeToIdMaps); m_danglingNodeToIdMaps.clear(); } void InspectorDOMAgent::startListening(Document* doc) { if (m_documents.contains(doc)) return; doc->addEventListener(eventNames().DOMContentLoadedEvent, this, false); doc->addEventListener(eventNames().DOMNodeInsertedEvent, this, false); doc->addEventListener(eventNames().DOMNodeRemovedEvent, this, false); doc->addEventListener(eventNames().DOMAttrModifiedEvent, this, false); doc->addEventListener(eventNames().loadEvent, this, true); m_documents.add(doc); } void InspectorDOMAgent::stopListening(Document* doc) { if (!m_documents.contains(doc)) return; doc->removeEventListener(eventNames().DOMContentLoadedEvent, this, false); doc->removeEventListener(eventNames().DOMNodeInsertedEvent, this, false); doc->removeEventListener(eventNames().DOMNodeRemovedEvent, this, false); doc->removeEventListener(eventNames().DOMAttrModifiedEvent, this, false); doc->removeEventListener(eventNames().loadEvent, this, true); m_documents.remove(doc); } void InspectorDOMAgent::handleEvent(ScriptExecutionContext*, Event* event) { AtomicString type = event->type(); Node* node = event->target()->toNode(); if (type == eventNames().DOMAttrModifiedEvent) { long id = m_documentNodeToIdMap.get(node); // If node is not mapped yet -> ignore the event. if (!id) return; Element* element = static_cast(node); m_frontend->attributesUpdated(id, buildArrayForElementAttributes(element)); } else if (type == eventNames().DOMNodeInsertedEvent) { if (isWhitespace(node)) return; // We could be attaching existing subtree. Forget the bindings. unbind(node, &m_documentNodeToIdMap); Node* parent = static_cast(event)->relatedNode(); long parentId = m_documentNodeToIdMap.get(parent); // Return if parent is not mapped yet. if (!parentId) return; if (!m_childrenRequested.contains(parentId)) { // No children are mapped yet -> only notify on changes of hasChildren. m_frontend->childNodeCountUpdated(parentId, innerChildNodeCount(parent)); } else { // Children have been requested -> return value of a new child. Node* prevSibling = innerPreviousSibling(node); long prevId = prevSibling ? m_documentNodeToIdMap.get(prevSibling) : 0; ScriptObject value = buildObjectForNode(node, 0, &m_documentNodeToIdMap); m_frontend->childNodeInserted(parentId, prevId, value); } } else if (type == eventNames().DOMNodeRemovedEvent) { if (isWhitespace(node)) return; Node* parent = static_cast(event)->relatedNode(); long parentId = m_documentNodeToIdMap.get(parent); // If parent is not mapped yet -> ignore the event. if (!parentId) return; if (!m_childrenRequested.contains(parentId)) { // No children are mapped yet -> only notify on changes of hasChildren. if (innerChildNodeCount(parent) == 1) m_frontend->childNodeCountUpdated(parentId, 0); } else { m_frontend->childNodeRemoved(parentId, m_documentNodeToIdMap.get(node)); } unbind(node, &m_documentNodeToIdMap); } else if (type == eventNames().DOMContentLoadedEvent) { // Re-push document once it is loaded. discardBindings(); pushDocumentToFrontend(); } else if (type == eventNames().loadEvent) { long frameOwnerId = m_documentNodeToIdMap.get(node); if (!frameOwnerId) return; if (!m_childrenRequested.contains(frameOwnerId)) { // No children are mapped yet -> only notify on changes of hasChildren. m_frontend->childNodeCountUpdated(frameOwnerId, innerChildNodeCount(node)); } else { // Re-add frame owner element together with its new children. long parentId = m_documentNodeToIdMap.get(innerParentNode(node)); m_frontend->childNodeRemoved(parentId, frameOwnerId); long prevId = m_documentNodeToIdMap.get(innerPreviousSibling(node)); ScriptObject value = buildObjectForNode(node, 0, &m_documentNodeToIdMap); m_frontend->childNodeInserted(parentId, prevId, value); // Invalidate children requested flag for the element. m_childrenRequested.remove(m_childrenRequested.find(frameOwnerId)); } } } long InspectorDOMAgent::bind(Node* node, NodeToIdMap* nodesMap) { long id = nodesMap->get(node); if (id) return id; id = m_lastNodeId++; nodesMap->set(node, id); m_idToNode.set(id, node); m_idToNodesMap.set(id, nodesMap); return id; } void InspectorDOMAgent::unbind(Node* node, NodeToIdMap* nodesMap) { if (node->isFrameOwnerElement()) { const HTMLFrameOwnerElement* frameOwner = static_cast(node); stopListening(frameOwner->contentDocument()); } int id = nodesMap->get(node); if (!id) return; m_idToNode.remove(id); nodesMap->remove(node); bool childrenRequested = m_childrenRequested.contains(id); if (childrenRequested) { // Unbind subtree known to client recursively. m_childrenRequested.remove(id); Node* child = innerFirstChild(node); while (child) { unbind(child, nodesMap); child = innerNextSibling(child); } } } void InspectorDOMAgent::pushDocumentToFrontend() { Document* document = mainFrameDocument(); if (!m_documentNodeToIdMap.contains(document)) m_frontend->setDocument(buildObjectForNode(document, 2, &m_documentNodeToIdMap)); } void InspectorDOMAgent::pushChildNodesToFrontend(long nodeId) { Node* node = nodeForId(nodeId); if (!node || (node->nodeType() != Node::ELEMENT_NODE && node->nodeType() != Node::DOCUMENT_NODE)) return; if (m_childrenRequested.contains(nodeId)) return; NodeToIdMap* nodeMap = m_idToNodesMap.get(nodeId); ScriptArray children = buildArrayForContainerChildren(node, 1, nodeMap); m_childrenRequested.add(nodeId); m_frontend->setChildNodes(nodeId, children); } void InspectorDOMAgent::discardBindings() { m_documentNodeToIdMap.clear(); m_idToNode.clear(); releaseDanglingNodes(); m_childrenRequested.clear(); } Node* InspectorDOMAgent::nodeForId(long id) { if (!id) return 0; HashMap::iterator it = m_idToNode.find(id); if (it != m_idToNode.end()) return it->second; return 0; } void InspectorDOMAgent::getChildNodes(long callId, long nodeId) { pushChildNodesToFrontend(nodeId); m_frontend->didGetChildNodes(callId); } long InspectorDOMAgent::pushNodePathToFrontend(Node* nodeToPush) { ASSERT(nodeToPush); // Invalid input // If we are sending information to the client that is currently being created. Send root node first. pushDocumentToFrontend(); // Return id in case the node is known. long result = m_documentNodeToIdMap.get(nodeToPush); if (result) return result; Node* node = nodeToPush; Vector path; NodeToIdMap* danglingMap = 0; while (true) { Node* parent = innerParentNode(node); if (!parent) { // Node being pushed is detached -> push subtree root. danglingMap = new NodeToIdMap(); m_danglingNodeToIdMaps.append(danglingMap); m_frontend->setDetachedRoot(buildObjectForNode(node, 0, danglingMap)); break; } else { path.append(parent); if (m_documentNodeToIdMap.get(parent)) break; else node = parent; } } NodeToIdMap* map = danglingMap ? danglingMap : &m_documentNodeToIdMap; for (int i = path.size() - 1; i >= 0; --i) { long nodeId = map->get(path.at(i)); ASSERT(nodeId); pushChildNodesToFrontend(nodeId); } return map->get(nodeToPush); } void InspectorDOMAgent::setAttribute(long callId, long elementId, const String& name, const String& value) { Node* node = nodeForId(elementId); if (node && (node->nodeType() == Node::ELEMENT_NODE)) { Element* element = static_cast(node); ExceptionCode ec = 0; element->setAttribute(name, value, ec); m_frontend->didApplyDomChange(callId, ec == 0); } else { m_frontend->didApplyDomChange(callId, false); } } void InspectorDOMAgent::removeAttribute(long callId, long elementId, const String& name) { Node* node = nodeForId(elementId); if (node && (node->nodeType() == Node::ELEMENT_NODE)) { Element* element = static_cast(node); ExceptionCode ec = 0; element->removeAttribute(name, ec); m_frontend->didApplyDomChange(callId, ec == 0); } else { m_frontend->didApplyDomChange(callId, false); } } void InspectorDOMAgent::setTextNodeValue(long callId, long nodeId, const String& value) { Node* node = nodeForId(nodeId); if (node && (node->nodeType() == Node::TEXT_NODE)) { Text* text_node = static_cast(node); ExceptionCode ec = 0; text_node->replaceWholeText(value, ec); m_frontend->didApplyDomChange(callId, ec == 0); } else { m_frontend->didApplyDomChange(callId, false); } } void InspectorDOMAgent::getEventListenersForNode(long callId, long nodeId) { Node* node = nodeForId(nodeId); ScriptArray listenersArray = m_frontend->newScriptArray(); unsigned counter = 0; EventTargetData* d; // Quick break if a null node or no listeners at all if (!node || !(d = node->eventTargetData())) { m_frontend->didGetEventListenersForNode(callId, nodeId, listenersArray); return; } // Get the list of event types this Node is concerned with Vector eventTypes; const EventListenerMap& listenerMap = d->eventListenerMap; HashMap::const_iterator end = listenerMap.end(); for (HashMap::const_iterator iter = listenerMap.begin(); iter != end; ++iter) eventTypes.append(iter->first); // Quick break if no useful listeners size_t eventTypesLength = eventTypes.size(); if (eventTypesLength == 0) { m_frontend->didGetEventListenersForNode(callId, nodeId, listenersArray); return; } // The Node's Event Ancestors (not including self) Vector > ancestors; node->eventAncestors(ancestors); // Nodes and their Listeners for the concerned event types (order is top to bottom) Vector eventInformation; for (size_t i = ancestors.size(); i; --i) { ContainerNode* ancestor = ancestors[i - 1].get(); for (size_t j = 0; j < eventTypesLength; ++j) { AtomicString& type = eventTypes[j]; if (ancestor->hasEventListeners(type)) eventInformation.append(EventListenerInfo(static_cast(ancestor), type, ancestor->getEventListeners(type))); } } // Insert the Current Node at the end of that list (last in capturing, first in bubbling) for (size_t i = 0; i < eventTypesLength; ++i) { const AtomicString& type = eventTypes[i]; eventInformation.append(EventListenerInfo(node, type, node->getEventListeners(type))); } // Get Capturing Listeners (in this order) size_t eventInformationLength = eventInformation.size(); for (size_t i = 0; i < eventInformationLength; ++i) { const EventListenerInfo& info = eventInformation[i]; const EventListenerVector& vector = info.eventListenerVector; for (size_t j = 0; j < vector.size(); ++j) { const RegisteredEventListener& listener = vector[j]; if (listener.useCapture) listenersArray.set(counter++, buildObjectForEventListener(listener, info.eventType, info.node)); } } // Get Bubbling Listeners (reverse order) for (size_t i = eventInformationLength; i; --i) { const EventListenerInfo& info = eventInformation[i - 1]; const EventListenerVector& vector = info.eventListenerVector; for (size_t j = 0; j < vector.size(); ++j) { const RegisteredEventListener& listener = vector[j]; if (!listener.useCapture) listenersArray.set(counter++, buildObjectForEventListener(listener, info.eventType, info.node)); } } m_frontend->didGetEventListenersForNode(callId, nodeId, listenersArray); } ScriptObject InspectorDOMAgent::buildObjectForNode(Node* node, int depth, NodeToIdMap* nodesMap) { ScriptObject value = m_frontend->newScriptObject(); long id = bind(node, nodesMap); String nodeName; String localName; String nodeValue; switch (node->nodeType()) { case Node::TEXT_NODE: case Node::COMMENT_NODE: nodeValue = node->nodeValue(); break; case Node::ATTRIBUTE_NODE: localName = node->localName(); break; case Node::DOCUMENT_FRAGMENT_NODE: break; case Node::DOCUMENT_NODE: case Node::ELEMENT_NODE: default: nodeName = node->nodeName(); localName = node->localName(); break; } value.set("id", static_cast(id)); value.set("nodeType", node->nodeType()); value.set("nodeName", nodeName); value.set("localName", localName); value.set("nodeValue", nodeValue); if (node->nodeType() == Node::ELEMENT_NODE) { Element* element = static_cast(node); value.set("attributes", buildArrayForElementAttributes(element)); } if (node->nodeType() == Node::ELEMENT_NODE || node->nodeType() == Node::DOCUMENT_NODE) { int nodeCount = innerChildNodeCount(node); value.set("childNodeCount", nodeCount); ScriptArray children = buildArrayForContainerChildren(node, depth, nodesMap); if (children.length() > 0) value.set("children", children); } return value; } ScriptArray InspectorDOMAgent::buildArrayForElementAttributes(Element* element) { ScriptArray attributesValue = m_frontend->newScriptArray(); // Go through all attributes and serialize them. const NamedNodeMap* attrMap = element->attributes(true); if (!attrMap) return attributesValue; unsigned numAttrs = attrMap->length(); int index = 0; for (unsigned i = 0; i < numAttrs; ++i) { // Add attribute pair const Attribute *attribute = attrMap->attributeItem(i); attributesValue.set(index++, attribute->name().toString()); attributesValue.set(index++, attribute->value()); } return attributesValue; } ScriptArray InspectorDOMAgent::buildArrayForContainerChildren(Node* container, int depth, NodeToIdMap* nodesMap) { ScriptArray children = m_frontend->newScriptArray(); if (depth == 0) { int index = 0; // Special case the_only text child. if (innerChildNodeCount(container) == 1) { Node *child = innerFirstChild(container); if (child->nodeType() == Node::TEXT_NODE) children.set(index++, buildObjectForNode(child, 0, nodesMap)); } return children; } else if (depth > 0) { depth--; } int index = 0; for (Node *child = innerFirstChild(container); child; child = innerNextSibling(child)) children.set(index++, buildObjectForNode(child, depth, nodesMap)); return children; } ScriptObject InspectorDOMAgent::buildObjectForEventListener(const RegisteredEventListener& registeredEventListener, const AtomicString& eventType, Node* node) { RefPtr eventListener = registeredEventListener.listener; ScriptObject value = m_frontend->newScriptObject(); value.set("type", eventType); value.set("useCapture", registeredEventListener.useCapture); value.set("isAttribute", eventListener->isAttribute()); value.set("nodeId", static_cast(pushNodePathToFrontend(node))); value.set("listener", getEventListenerHandlerBody(node->document(), m_frontend->scriptState(), eventListener.get())); return value; } Node* InspectorDOMAgent::innerFirstChild(Node* node) { if (node->isFrameOwnerElement()) { HTMLFrameOwnerElement* frameOwner = static_cast(node); Document* doc = frameOwner->contentDocument(); if (doc) { startListening(doc); return doc->firstChild(); } } node = node->firstChild(); while (isWhitespace(node)) node = node->nextSibling(); return node; } Node* InspectorDOMAgent::innerNextSibling(Node* node) { do { node = node->nextSibling(); } while (isWhitespace(node)); return node; } Node* InspectorDOMAgent::innerPreviousSibling(Node* node) { do { node = node->previousSibling(); } while (isWhitespace(node)); return node; } int InspectorDOMAgent::innerChildNodeCount(Node* node) { int count = 0; Node* child = innerFirstChild(node); while (child) { count++; child = innerNextSibling(child); } return count; } Node* InspectorDOMAgent::innerParentNode(Node* node) { Node* parent = node->parentNode(); if (parent && parent->nodeType() == Node::DOCUMENT_NODE) return static_cast(parent)->ownerElement(); return parent; } bool InspectorDOMAgent::isWhitespace(Node* node) { //TODO: pull ignoreWhitespace setting from the frontend and use here. return node && node->nodeType() == Node::TEXT_NODE && node->nodeValue().stripWhiteSpace().length() == 0; } Document* InspectorDOMAgent::mainFrameDocument() const { ListHashSet >::const_iterator it = m_documents.begin(); if (it != m_documents.end()) return it->get(); return 0; } bool InspectorDOMAgent::operator==(const EventListener& listener) { if (const InspectorDOMAgent* inspectorDOMAgentListener = InspectorDOMAgent::cast(&listener)) return mainFrameDocument() == inspectorDOMAgentListener->mainFrameDocument(); return false; } } // namespace WebCore #endif // ENABLE(INSPECTOR)