diff options
Diffstat (limited to 'WebCore/inspector/InspectorDOMAgent.cpp')
| -rw-r--r-- | WebCore/inspector/InspectorDOMAgent.cpp | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/WebCore/inspector/InspectorDOMAgent.cpp b/WebCore/inspector/InspectorDOMAgent.cpp new file mode 100644 index 0000000..f222239 --- /dev/null +++ b/WebCore/inspector/InspectorDOMAgent.cpp @@ -0,0 +1,471 @@ +/* + * 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 <wtf/OwnPtr.h> +#include <wtf/Vector.h> + +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<RefPtr<Document> > copy = m_documents; + for (ListHashSet<RefPtr<Document> >::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<Element*>(node); + m_frontend->attributesUpdated(id, buildArrayForElementAttributes(element)); + } else if (type == eventNames().DOMNodeInsertedEvent) { + if (isWhitespace(node)) + return; + + Node* parent = static_cast<MutationEvent*>(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<MutationEvent*>(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<Node*, long>::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<const HTMLFrameOwnerElement*>(node); + stopListening(frameOwner->contentDocument()); + } + + HashMap<Node*, long>::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<Element*>(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<long, Node*>::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<Node*, long>::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<Element*> 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<Element*>(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<Element*>(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<Text*>(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<int>(id)); + value.set("nodeType", node->nodeType()); + value.set("nodeName", nodeName); + value.set("nodeValue", nodeValue); + + if (node->nodeType() == Node::ELEMENT_NODE) { + Element* element = static_cast<Element*>(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<HTMLFrameOwnerElement*>(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<RefPtr<Document> >::iterator it = m_documents.begin(); + if (it != m_documents.end()) + return it->get(); + return 0; +} + +} // namespace WebCore |
