/* * Copyright (C) 2009 Apple Inc. All rights reserved. * Copyright (C) 2009 Google Inc. All rights reserved. * * 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" #include "AtomicString.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 "ScriptObject.h" #include "Text.h" #include #include namespace WebCore { InspectorDOMAgent::InspectorDOMAgent(InspectorFrontend* frontend) : m_frontend(frontend) , m_lastNodeId(1) { } InspectorDOMAgent::~InspectorDOMAgent() { setDocument(0); } void InspectorDOMAgent::setDocument(Document* doc) { if (doc == mainFrameDocument()) return; 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()) { pushDocumentElementToFrontend(); } } else { discardBindings(); } } 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().DOMNodeRemovedFromDocumentEvent, this, true); doc->addEventListener(eventNames().DOMAttrModifiedEvent, this, false); 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().DOMNodeRemovedFromDocumentEvent, this, true); doc->removeEventListener(eventNames().DOMAttrModifiedEvent, this, false); m_documents.remove(doc); } void InspectorDOMAgent::handleEvent(Event* event, bool) { AtomicString type = event->type(); Node* node = event->target()->toNode(); // Remove mapping entry if necessary. if (type == eventNames().DOMNodeRemovedFromDocumentEvent) { unbind(node); return; } if (type == eventNames().DOMAttrModifiedEvent) { long id = idForNode(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; Node* parent = static_cast(event)->relatedNode(); long parentId = idForNode(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->hasChildrenUpdated(parentId, true); } else { // Children have been requested -> return value of a new child. long prevId = idForNode(innerPreviousSibling(node)); ScriptObject value = buildObjectForNode(node, 0); m_frontend->childNodeInserted(parentId, prevId, value); } } else if (type == eventNames().DOMNodeRemovedEvent) { if (isWhitespace(node)) return; Node* parent = static_cast(event)->relatedNode(); long parentId = idForNode(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->hasChildrenUpdated(parentId, false); } else { m_frontend->childNodeRemoved(parentId, idForNode(node)); } } else if (type == eventNames().DOMContentLoadedEvent) { // Re-push document once it is loaded. discardBindings(); pushDocumentElementToFrontend(); } } long InspectorDOMAgent::bind(Node* node) { HashMap::iterator it = m_nodeToId.find(node); if (it != m_nodeToId.end()) return it->second; long id = m_lastNodeId++; m_nodeToId.set(node, id); m_idToNode.set(id, node); return id; } void InspectorDOMAgent::unbind(Node* node) { if (node->isFrameOwnerElement()) { const HTMLFrameOwnerElement* frameOwner = static_cast(node); stopListening(frameOwner->contentDocument()); } HashMap::iterator it = m_nodeToId.find(node); if (it != m_nodeToId.end()) { m_idToNode.remove(m_idToNode.find(it->second)); m_childrenRequested.remove(m_childrenRequested.find(it->second)); m_nodeToId.remove(it); } } void InspectorDOMAgent::pushDocumentElementToFrontend() { Element* docElem = mainFrameDocument()->documentElement(); if (!m_nodeToId.contains(docElem)) m_frontend->setDocumentElement(buildObjectForNode(docElem, 0)); } void InspectorDOMAgent::pushChildNodesToFrontend(long elementId) { Node* node = nodeForId(elementId); if (!node || (node->nodeType() != Node::ELEMENT_NODE)) return; if (m_childrenRequested.contains(elementId)) return; Element* element = static_cast(node); ScriptArray children = buildArrayForElementChildren(element, 1); m_childrenRequested.add(elementId); m_frontend->setChildNodes(elementId, children); } void InspectorDOMAgent::discardBindings() { m_nodeToId.clear(); m_idToNode.clear(); m_childrenRequested.clear(); } Node* InspectorDOMAgent::nodeForId(long id) { HashMap::iterator it = m_idToNode.find(id); if (it != m_idToNode.end()) return it->second; return 0; } long InspectorDOMAgent::idForNode(Node* node) { if (!node) return 0; HashMap::iterator it = m_nodeToId.find(node); if (it != m_nodeToId.end()) return it->second; return 0; } void InspectorDOMAgent::getChildNodes(long callId, long elementId) { pushChildNodesToFrontend(elementId); 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. pushDocumentElementToFrontend(); // Return id in case the node is known. long result = idForNode(nodeToPush); if (result) return result; Element* element = innerParentElement(nodeToPush); ASSERT(element); // Node is detached or is a document itself Vector path; while (element && !idForNode(element)) { path.append(element); element = innerParentElement(element); } // element is known to the client ASSERT(element); path.append(element); for (int i = path.size() - 1; i >= 0; --i) { long nodeId = idForNode(path.at(i)); ASSERT(nodeId); pushChildNodesToFrontend(nodeId); } return idForNode(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 elementId, const String& value) { Node* node = nodeForId(elementId); 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); } } ScriptObject InspectorDOMAgent::buildObjectForNode(Node* node, int depth) { ScriptObject value = m_frontend->newScriptObject(); long id = bind(node); String nodeName; String nodeValue; switch (node->nodeType()) { case Node::TEXT_NODE: case Node::COMMENT_NODE: nodeValue = node->nodeValue(); break; case Node::ATTRIBUTE_NODE: case Node::DOCUMENT_NODE: case Node::DOCUMENT_FRAGMENT_NODE: break; case Node::ELEMENT_NODE: default: nodeName = node->nodeName(); break; } value.set("id", static_cast(id)); value.set("nodeType", node->nodeType()); value.set("nodeName", nodeName); value.set("nodeValue", nodeValue); if (node->nodeType() == Node::ELEMENT_NODE) { Element* element = static_cast(node); value.set("attributes", buildArrayForElementAttributes(element)); int nodeCount = innerChildNodeCount(element); value.set("childNodeCount", nodeCount); ScriptArray children = buildArrayForElementChildren(element, depth); 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::buildArrayForElementChildren(Element* element, int depth) { ScriptArray children = m_frontend->newScriptArray(); if (depth == 0) { int index = 0; // Special case the_only text child. if (innerChildNodeCount(element) == 1) { Node *child = innerFirstChild(element); if (child->nodeType() == Node::TEXT_NODE) children.set(index++, buildObjectForNode(child, 0)); } return children; } else if (depth > 0) { depth--; } int index = 0; for (Node *child = innerFirstChild(element); child; child = innerNextSibling(child)) children.set(index++, buildObjectForNode(child, depth)); return children; } 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; } Element* InspectorDOMAgent::innerParentElement(Node* node) { Element* element = node->parentElement(); if (!element) return node->ownerDocument()->ownerElement(); return element; } 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() { ListHashSet >::iterator it = m_documents.begin(); if (it != m_documents.end()) return it->get(); return 0; } } // namespace WebCore