InspectorDOMAgent.cpp [plain text]
#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 "DocumentType.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 <wtf/OwnPtr.h>
#include <wtf/Vector.h>
namespace WebCore {
InspectorDOMAgent::InspectorDOMAgent(InspectorFrontend* frontend)
: EventListener(InspectorDOMAgentType)
, m_frontend(frontend)
, m_lastNodeId(1)
{
}
InspectorDOMAgent::~InspectorDOMAgent()
{
reset();
}
void InspectorDOMAgent::reset()
{
discardBindings();
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());
}
void InspectorDOMAgent::setDocument(Document* doc)
{
if (doc == mainFrameDocument())
return;
reset();
if (doc) {
startListening(doc);
if (doc->documentElement())
pushDocumentToFrontend();
} else
m_frontend->setDocument(ScriptObject());
}
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().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().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().DOMContentLoadedEvent) {
discardBindings();
pushDocumentToFrontend();
} else if (type == eventNames().loadEvent) {
long frameOwnerId = m_documentNodeToIdMap.get(node);
if (!frameOwnerId)
return;
if (!m_childrenRequested.contains(frameOwnerId)) {
m_frontend->childNodeCountUpdated(frameOwnerId, innerChildNodeCount(node));
} else {
long parentId = m_documentNodeToIdMap.get(innerParentNode(node));
m_frontend->childNodeRemoved(parentId, frameOwnerId);
ScriptObject value = buildObjectForNode(node, 0, &m_documentNodeToIdMap);
Node* previousSibling = innerPreviousSibling(node);
long prevId = previousSibling ? m_documentNodeToIdMap.get(previousSibling) : 0;
m_frontend->childNodeInserted(parentId, prevId, value);
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<const HTMLFrameOwnerElement*>(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) {
m_childrenRequested.remove(id);
Node* child = innerFirstChild(node);
while (child) {
unbind(child, nodesMap);
child = innerNextSibling(child);
}
}
}
bool InspectorDOMAgent::pushDocumentToFrontend()
{
Document* document = mainFrameDocument();
if (!document)
return false;
if (!m_documentNodeToIdMap.contains(document))
m_frontend->setDocument(buildObjectForNode(document, 2, &m_documentNodeToIdMap));
return true;
}
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<long, Node*>::iterator it = m_idToNode.find(id);
if (it != m_idToNode.end())
return it->second;
return 0;
}
Node* InspectorDOMAgent::nodeForPath(const String& path)
{
Node* node = mainFrameDocument();
if (!node)
return 0;
Vector<String> pathTokens;
path.split(",", false, pathTokens);
for (size_t i = 0; i < pathTokens.size() - 1; i += 2) {
bool success = true;
unsigned childNumber = pathTokens[i].toUInt(&success);
if (!success)
return 0;
if (childNumber >= innerChildNodeCount(node))
return 0;
Node* child = innerFirstChild(node);
String childName = pathTokens[i + 1];
for (size_t j = 0; child && j < childNumber; ++j)
child = innerNextSibling(child);
if (!child || child->nodeName() != childName)
return 0;
node = child;
}
return node;
}
void InspectorDOMAgent::getChildNodes(long callId, long nodeId)
{
pushChildNodesToFrontend(nodeId);
m_frontend->didGetChildNodes(callId);
}
long InspectorDOMAgent::pushNodePathToFrontend(Node* nodeToPush)
{
ASSERT(nodeToPush);
if (!pushDocumentToFrontend())
return 0;
long result = m_documentNodeToIdMap.get(nodeToPush);
if (result)
return result;
Node* node = nodeToPush;
Vector<Node*> path;
NodeToIdMap* danglingMap = 0;
while (true) {
Node* parent = innerParentNode(node);
if (!parent) {
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<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 nodeId, const String& value)
{
Node* node = nodeForId(nodeId);
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);
}
}
void InspectorDOMAgent::getEventListenersForNode(long callId, long nodeId)
{
Node* node = nodeForId(nodeId);
ScriptArray listenersArray = m_frontend->newScriptArray();
unsigned counter = 0;
EventTargetData* d;
if (!node || !(d = node->eventTargetData())) {
m_frontend->didGetEventListenersForNode(callId, nodeId, listenersArray);
return;
}
Vector<AtomicString> eventTypes;
const EventListenerMap& listenerMap = d->eventListenerMap;
EventListenerMap::const_iterator end = listenerMap.end();
for (EventListenerMap::const_iterator iter = listenerMap.begin(); iter != end; ++iter)
eventTypes.append(iter->first);
size_t eventTypesLength = eventTypes.size();
if (eventTypesLength == 0) {
m_frontend->didGetEventListenersForNode(callId, nodeId, listenersArray);
return;
}
Vector<RefPtr<ContainerNode> > ancestors;
node->eventAncestors(ancestors);
Vector<EventListenerInfo> 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<Node*>(ancestor), type, ancestor->getEventListeners(type)));
}
}
for (size_t i = 0; i < eventTypesLength; ++i) {
const AtomicString& type = eventTypes[i];
eventInformation.append(EventListenerInfo(node, type, node->getEventListeners(type)));
}
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));
}
}
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);
}
String InspectorDOMAgent::documentURLString(Document* document) const
{
if (!document || document->url().isNull())
return "";
return document->url().string();
}
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", id);
value.set("nodeType", node->nodeType());
value.set("nodeName", nodeName);
value.set("localName", localName);
value.set("nodeValue", nodeValue);
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);
if (node->nodeType() == Node::ELEMENT_NODE) {
Element* element = static_cast<Element*>(node);
value.set("attributes", buildArrayForElementAttributes(element));
if (node->isFrameOwnerElement()) {
HTMLFrameOwnerElement* frameOwner = static_cast<HTMLFrameOwnerElement*>(node);
value.set("documentURL", documentURLString(frameOwner->contentDocument()));
}
} else {
Document* document = static_cast<Document*>(node);
value.set("documentURL", documentURLString(document));
}
} else if (node->nodeType() == Node::DOCUMENT_TYPE_NODE) {
DocumentType* docType = static_cast<DocumentType*>(node);
value.set("publicId", docType->publicId());
value.set("systemId", docType->systemId());
value.set("internalSubset", docType->internalSubset());
}
return value;
}
ScriptArray InspectorDOMAgent::buildArrayForElementAttributes(Element* element)
{
ScriptArray attributesValue = m_frontend->newScriptArray();
const NamedNodeMap* attrMap = element->attributes(true);
if (!attrMap)
return attributesValue;
unsigned numAttrs = attrMap->length();
int index = 0;
for (unsigned i = 0; i < numAttrs; ++i) {
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;
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> 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", 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<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;
}
unsigned InspectorDOMAgent::innerChildNodeCount(Node* node)
{
unsigned 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<Document*>(parent)->ownerElement();
return parent;
}
bool InspectorDOMAgent::isWhitespace(Node* node)
{
return node && node->nodeType() == Node::TEXT_NODE && node->nodeValue().stripWhiteSpace().length() == 0;
}
Document* InspectorDOMAgent::mainFrameDocument() const
{
ListHashSet<RefPtr<Document> >::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;
}
void InspectorDOMAgent::didInsertDOMNode(Node* node)
{
if (isWhitespace(node))
return;
unbind(node, &m_documentNodeToIdMap);
Node* parent = node->parentNode();
long parentId = m_documentNodeToIdMap.get(parent);
if (!parentId)
return;
if (!m_childrenRequested.contains(parentId)) {
m_frontend->childNodeCountUpdated(parentId, innerChildNodeCount(parent));
} else {
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);
}
}
void InspectorDOMAgent::didRemoveDOMNode(Node* node)
{
if (isWhitespace(node))
return;
Node* parent = node->parentNode();
long parentId = m_documentNodeToIdMap.get(parent);
if (!parentId)
return;
if (!m_childrenRequested.contains(parentId)) {
if (innerChildNodeCount(parent) == 1)
m_frontend->childNodeCountUpdated(parentId, 0);
} else
m_frontend->childNodeRemoved(parentId, m_documentNodeToIdMap.get(node));
unbind(node, &m_documentNodeToIdMap);
}
void InspectorDOMAgent::didModifyDOMAttr(Element* element)
{
long id = m_documentNodeToIdMap.get(element);
if (!id)
return;
m_frontend->attributesUpdated(id, buildArrayForElementAttributes(element));
}
}
#endif // ENABLE(INSPECTOR)