summaryrefslogtreecommitdiffstats
path: root/webkit/glue/devtools/dom_agent_impl.cc
diff options
context:
space:
mode:
Diffstat (limited to 'webkit/glue/devtools/dom_agent_impl.cc')
-rw-r--r--webkit/glue/devtools/dom_agent_impl.cc406
1 files changed, 406 insertions, 0 deletions
diff --git a/webkit/glue/devtools/dom_agent_impl.cc b/webkit/glue/devtools/dom_agent_impl.cc
new file mode 100644
index 0000000..5f7a66a
--- /dev/null
+++ b/webkit/glue/devtools/dom_agent_impl.cc
@@ -0,0 +1,406 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "config.h"
+
+#include "webkit/glue/devtools/dom_agent_impl.h"
+
+#include "AtomicString.h"
+#include "Document.h"
+#include "Event.h"
+#include "EventListener.h"
+#include "EventNames.h"
+#include "EventTarget.h"
+#include "HTMLFrameOwnerElement.h"
+#include "markup.h"
+#include "MutationEvent.h"
+#include "Node.h"
+#include "Text.h"
+#include <wtf/OwnPtr.h>
+#include <wtf/Vector.h>
+#undef LOG
+
+#include "base/values.h"
+#include "webkit/glue/glue_util.h"
+
+using namespace WebCore;
+
+// static
+PassRefPtr<DomAgentImpl::EventListenerWrapper>
+ DomAgentImpl::EventListenerWrapper::Create(
+ DomAgentImpl* dom_agent_impl) {
+ return adoptRef(new EventListenerWrapper(dom_agent_impl));
+}
+
+DomAgentImpl::EventListenerWrapper::EventListenerWrapper(
+ DomAgentImpl* dom_agent_impl) : dom_agent_impl_(dom_agent_impl) {
+}
+
+void DomAgentImpl::EventListenerWrapper::handleEvent(
+ Event* event,
+ bool isWindowEvent) {
+ dom_agent_impl_->handleEvent(event, isWindowEvent);
+}
+
+DomAgentImpl::DomAgentImpl(DomAgentDelegate* delegate)
+ : delegate_(delegate),
+ last_node_id_(1),
+ document_element_requested_(false) {
+ event_listener_ = EventListenerWrapper::Create(this);
+}
+
+DomAgentImpl::~DomAgentImpl() {
+ SetDocument(NULL);
+}
+
+void DomAgentImpl::SetDocument(Document* doc) {
+ DiscardBindings();
+
+ ListHashSet<RefPtr<Document> > copy = documents_;
+ for (ListHashSet<RefPtr<Document> >::iterator it = copy.begin();
+ it != copy.end(); ++it) {
+ StopListening((*it).get());
+ }
+ ASSERT(documents_.size() == 0);
+
+ if (doc) {
+ StartListening(doc);
+ if (document_element_requested_) {
+ GetDocumentElement();
+ document_element_requested_ = false;
+ }
+ }
+}
+
+void DomAgentImpl::StartListening(Document* doc) {
+ if (documents_.find(doc) != documents_.end())
+ return;
+ doc->addEventListener(eventNames().DOMContentLoadedEvent, event_listener_,
+ false);
+ doc->addEventListener(eventNames().DOMNodeInsertedEvent, event_listener_,
+ false);
+ doc->addEventListener(eventNames().DOMNodeRemovedEvent, event_listener_,
+ false);
+ doc->addEventListener(eventNames().DOMNodeRemovedFromDocumentEvent,
+ event_listener_, false);
+ doc->addEventListener(eventNames().DOMAttrModifiedEvent, event_listener_,
+ false);
+ documents_.add(doc);
+}
+
+void DomAgentImpl::StopListening(Document* doc) {
+ doc->removeEventListener(eventNames().DOMContentLoadedEvent,
+ event_listener_.get(), false);
+ doc->removeEventListener(eventNames().DOMNodeInsertedEvent,
+ event_listener_.get(), false);
+ doc->removeEventListener(eventNames().DOMNodeRemovedEvent,
+ event_listener_.get(), false);
+ doc->removeEventListener(eventNames().DOMNodeRemovedFromDocumentEvent,
+ event_listener_.get(), false);
+ doc->removeEventListener(eventNames().DOMAttrModifiedEvent,
+ event_listener_.get(), false);
+ documents_.remove(doc);
+}
+
+int DomAgentImpl::Bind(Node* node) {
+ HashMap<Node*, int>::iterator it = node_to_id_.find(node);
+ if (it != node_to_id_.end())
+ return it->second;
+ int id = last_node_id_++;
+ node_to_id_.set(node, id);
+ id_to_node_.set(id, node);
+ return id;
+}
+
+void DomAgentImpl::Unbind(Node* node) {
+ if (node->isFrameOwnerElement()) {
+ const HTMLFrameOwnerElement* frame_owner =
+ static_cast<const HTMLFrameOwnerElement*>(node);
+ StopListening(frame_owner->contentDocument());
+ }
+
+ HashMap<Node*, int>::iterator it = node_to_id_.find(node);
+ if (it != node_to_id_.end()) {
+ id_to_node_.remove(id_to_node_.find(it->second));
+ children_requested_.remove(children_requested_.find(it->second));
+ node_to_id_.remove(it);
+ }
+}
+
+void DomAgentImpl::DiscardBindings() {
+ node_to_id_.clear();
+ id_to_node_.clear();
+ children_requested_.clear();
+}
+
+Node* DomAgentImpl::GetNodeForId(int id) {
+ HashMap<int, Node*>::iterator it = id_to_node_.find(id);
+ if (it != id_to_node_.end()) {
+ return it->second;
+ }
+ return NULL;
+}
+
+int DomAgentImpl::GetIdForNode(Node* node) {
+ if (node == NULL) {
+ return 0;
+ }
+ HashMap<Node*, int>::iterator it = node_to_id_.find(node);
+ if (it != node_to_id_.end()) {
+ return it->second;
+ }
+ return 0;
+}
+
+void DomAgentImpl::handleEvent(Event* event, bool isWindowEvent) {
+ 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) {
+ int id = GetIdForNode(node);
+ if (!id) {
+ // Node is not mapped yet -> ignore the event.
+ return;
+ }
+ Element* element = static_cast<Element*>(node);
+ OwnPtr<Value> attributesValue(BuildValueForElementAttributes(element));
+ delegate_->AttributesUpdated(id, *attributesValue.get());
+ } else if (type == eventNames().DOMNodeInsertedEvent) {
+ Node* parent = static_cast<MutationEvent*>(event)->relatedNode();
+ int parent_id = GetIdForNode(parent);
+ if (!parent_id) {
+ // Parent is not mapped yet -> ignore the event.
+ return;
+ }
+ HashSet<int>::iterator cit = children_requested_.find(parent_id);
+ if (cit == children_requested_.end()) {
+ // No children are mapped yet -> only notify on changes of hasChildren.
+ delegate_->HasChildrenUpdated(parent_id, true);
+ } else {
+ // Children have been requested -> return value of a new child.
+ int prev_id = GetIdForNode(node->previousSibling());
+ OwnPtr<Value> value(BuildValueForNode(node, 0));
+ delegate_->ChildNodeInserted(parent_id, prev_id, *value.get());
+ }
+ } else if (type == eventNames().DOMNodeRemovedEvent) {
+ Node* parent = static_cast<MutationEvent*>(event)->relatedNode();
+ int parent_id = GetIdForNode(parent);
+ if (!parent_id) {
+ // Parent is not mapped yet -> ignore the event.
+ return;
+ }
+ HashSet<int>::iterator cit = children_requested_.find(parent_id);
+ if (cit == children_requested_.end()) {
+ // No children are mapped yet -> only notify on changes of hasChildren.
+ if (parent->childNodeCount() == 1)
+ delegate_->HasChildrenUpdated(parent_id, false);
+ } else {
+ int id = GetIdForNode(node);
+ delegate_->ChildNodeRemoved(parent_id, id);
+ }
+ } else if (type == eventNames().DOMContentLoadedEvent) {
+ //TODO(pfeldman): handle content load event.
+ }
+}
+
+void DomAgentImpl::GetDocumentElement() {
+ if (documents_.size() > 0) {
+ OwnPtr<Value> value(
+ BuildValueForNode((*documents_.begin())->documentElement(), 0));
+ delegate_->DocumentElementUpdated(*value.get());
+ } else {
+ document_element_requested_ = true;
+ }
+}
+
+void DomAgentImpl::GetChildNodes(int element_id) {
+ Node* node = GetNodeForId(element_id);
+ if (!node || (node->nodeType() != Node::ELEMENT_NODE))
+ return;
+
+ Element* element = static_cast<Element*>(node);
+ OwnPtr<Value> children(BuildValueForElementChildren(element, 1));
+ children_requested_.add(element_id);
+ delegate_->ChildNodesUpdated(element_id, *children.get());
+}
+
+int DomAgentImpl::GetPathToNode(Node* node_to_select) {
+ ASSERT(node_to_select); // Invalid input
+
+ // Return id in case the node is known.
+ int result = GetIdForNode(node_to_select);
+ if (result)
+ return result;
+
+ Element* element = InnerParentElement(node_to_select);
+ ASSERT(element); // Node is detached or is a document itself
+
+ Vector<Element*> path;
+ while (element && !GetIdForNode(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) {
+ element = path.at(i);
+ OwnPtr<Value> children(BuildValueForElementChildren(element, 1));
+ delegate_->ChildNodesUpdated(GetIdForNode(element), *children.get());
+ }
+ return GetIdForNode(node_to_select);
+}
+
+void DomAgentImpl::SetAttribute(
+ int element_id,
+ const String& name,
+ const String& value) {
+ Node* node = GetNodeForId(element_id);
+ if (node && (node->nodeType() == Node::ELEMENT_NODE)) {
+ Element* element = static_cast<Element*>(node);
+ ExceptionCode ec = 0;
+ element->setAttribute(name, value, ec);
+ }
+}
+
+void DomAgentImpl::RemoveAttribute(int element_id, const String& name) {
+ Node* node = GetNodeForId(element_id);
+ if (node && (node->nodeType() == Node::ELEMENT_NODE)) {
+ Element* element = static_cast<Element*>(node);
+ ExceptionCode ec = 0;
+ element->removeAttribute(name, ec);
+ }
+}
+
+void DomAgentImpl::SetTextNodeValue(int element_id, const String& value) {
+ Node* node = GetNodeForId(element_id);
+ if (node && (node->nodeType() == Node::TEXT_NODE)) {
+ Text* text_node = static_cast<Text*>(node);
+ ExceptionCode ec = 0;
+ // TODO(pfeldman): Add error handling
+ text_node->replaceWholeText(value, ec);
+ }
+}
+
+ListValue* DomAgentImpl::BuildValueForNode(Node* node, int depth) {
+ OwnPtr<ListValue> value(new ListValue());
+ int 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->Append(Value::CreateIntegerValue(id));
+ value->Append(Value::CreateIntegerValue(node->nodeType()));
+ value->Append(Value::CreateStringValue(
+ webkit_glue::StringToStdWString(nodeName)));
+ value->Append(Value::CreateStringValue(
+ webkit_glue::StringToStdWString(nodeValue)));
+
+ if (node->nodeType() == Node::ELEMENT_NODE) {
+ Element* element = static_cast<Element*>(node);
+ value->Append(BuildValueForElementAttributes(element));
+ int nodeCount = InnerChildNodeCount(element);
+ value->Append(Value::CreateIntegerValue(nodeCount));
+ OwnPtr<ListValue> children(BuildValueForElementChildren(element, depth));
+ if (children->GetSize() > 0) {
+ value->Append(children.release());
+ }
+ }
+ return value.release();
+}
+
+ListValue* DomAgentImpl::BuildValueForElementAttributes(Element* element) {
+ OwnPtr<ListValue> attributesValue(new ListValue());
+ // Go through all attributes and serialize them.
+ const NamedAttrMap *attrMap = element->attributes(true);
+ if (!attrMap) {
+ return attributesValue.release();
+ }
+ unsigned numAttrs = attrMap->length();
+ for (unsigned i = 0; i < numAttrs; i++) {
+ // Add attribute pair
+ const Attribute *attribute = attrMap->attributeItem(i);
+ OwnPtr<Value> name(Value::CreateStringValue(
+ webkit_glue::StringToStdWString(attribute->name().toString())));
+ OwnPtr<Value> value(Value::CreateStringValue(
+ webkit_glue::StringToStdWString(attribute->value())));
+ attributesValue->Append(name.release());
+ attributesValue->Append(value.release());
+ }
+ return attributesValue.release();
+}
+
+ListValue* DomAgentImpl::BuildValueForElementChildren(
+ Element* element,
+ int depth) {
+ OwnPtr<ListValue> children(new ListValue());
+ if (depth == 0) {
+ // Special case the_only text child.
+ if (element->childNodeCount() == 1) {
+ Node *child = element->firstChild();
+ if (child->nodeType() == Node::TEXT_NODE) {
+ children->Append(BuildValueForNode(child, 0));
+ }
+ }
+ return children.release();
+ } else if (depth > 0) {
+ depth--;
+ }
+
+ for (Node *child = InnerFirstChild(element); child != NULL;
+ child = child->nextSibling()) {
+ children->Append(BuildValueForNode(child, depth));
+ }
+ return children.release();
+}
+
+Node* DomAgentImpl::InnerFirstChild(Node* node) {
+ if (node->isFrameOwnerElement()) {
+ HTMLFrameOwnerElement* frame_owner =
+ static_cast<HTMLFrameOwnerElement*>(node);
+ Document* doc = frame_owner->contentDocument();
+ StartListening(doc);
+ return doc->firstChild();
+ } else {
+ return node->firstChild();
+ }
+}
+
+int DomAgentImpl::InnerChildNodeCount(Node* node) {
+ if (node->isFrameOwnerElement()) {
+ return 1;
+ } else {
+ return node->childNodeCount();
+ }
+}
+
+Element* DomAgentImpl::InnerParentElement(Node* node) {
+ Element* element = node->parentElement();
+ if (!element) {
+ return node->ownerDocument()->ownerElement();
+ }
+ return element;
+}