blob: 23c22aa22fc11cbd6dd89d04924d062542e97747 [file] [log] [blame]
/*
* 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 "Attr.h"
#include "CSSComputedStyleDeclaration.h"
#include "CSSMutableStyleDeclaration.h"
#include "CSSPropertyNames.h"
#include "CSSRule.h"
#include "CSSRuleList.h"
#include "CSSStyleRule.h"
#include "CSSStyleSelector.h"
#include "CSSStyleSheet.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 "Frame.h"
#include "FrameTree.h"
#include "HTMLElement.h"
#include "HTMLFrameOwnerElement.h"
#include "InspectorFrontend.h"
#include "MutationEvent.h"
#include "Node.h"
#include "NodeList.h"
#include "Pasteboard.h"
#include "PlatformString.h"
#include "RenderStyle.h"
#include "RenderStyleConstants.h"
#include "ScriptEventListener.h"
#include "StyleSheetList.h"
#include "Text.h"
#if ENABLE(XPATH)
#include "XPathResult.h"
#endif
#include "markup.h"
#include <wtf/text/CString.h>
#include <wtf/HashSet.h>
#include <wtf/ListHashSet.h>
#include <wtf/OwnPtr.h>
#include <wtf/Vector.h>
#include <wtf/text/AtomicString.h>
namespace WebCore {
class MatchJob {
public:
virtual void match(ListHashSet<Node*>& resultCollector) = 0;
virtual ~MatchJob() { }
protected:
MatchJob(Document* document, const String& query)
: m_document(document)
, m_query(query) { }
void addNodesToResults(PassRefPtr<NodeList> nodes, ListHashSet<Node*>& resultCollector)
{
for (unsigned i = 0; nodes && i < nodes->length(); ++i)
resultCollector.add(nodes->item(i));
}
RefPtr<Document> m_document;
String m_query;
};
namespace {
class MatchExactIdJob : public WebCore::MatchJob {
public:
MatchExactIdJob(Document* document, const String& query) : WebCore::MatchJob(document, query) { }
virtual ~MatchExactIdJob() { }
protected:
virtual void match(ListHashSet<Node*>& resultCollector)
{
if (m_query.isEmpty())
return;
Element* element = m_document->getElementById(m_query);
if (element)
resultCollector.add(element);
}
};
class MatchExactClassNamesJob : public WebCore::MatchJob {
public:
MatchExactClassNamesJob(Document* document, const String& query) : WebCore::MatchJob(document, query) { }
virtual ~MatchExactClassNamesJob() { }
virtual void match(ListHashSet<Node*>& resultCollector)
{
if (!m_query.isEmpty())
addNodesToResults(m_document->getElementsByClassName(m_query), resultCollector);
}
};
class MatchExactTagNamesJob : public WebCore::MatchJob {
public:
MatchExactTagNamesJob(Document* document, const String& query) : WebCore::MatchJob(document, query) { }
virtual ~MatchExactTagNamesJob() { }
virtual void match(ListHashSet<Node*>& resultCollector)
{
if (!m_query.isEmpty())
addNodesToResults(m_document->getElementsByName(m_query), resultCollector);
}
};
class MatchQuerySelectorAllJob : public WebCore::MatchJob {
public:
MatchQuerySelectorAllJob(Document* document, const String& query) : WebCore::MatchJob(document, query) { }
virtual ~MatchQuerySelectorAllJob() { }
virtual void match(ListHashSet<Node*>& resultCollector)
{
if (m_query.isEmpty())
return;
ExceptionCode ec = 0;
RefPtr<NodeList> list = m_document->querySelectorAll(m_query, ec);
if (!ec)
addNodesToResults(list, resultCollector);
}
};
class MatchXPathJob : public WebCore::MatchJob {
public:
MatchXPathJob(Document* document, const String& query) : WebCore::MatchJob(document, query) { }
virtual ~MatchXPathJob() { }
virtual void match(ListHashSet<Node*>& resultCollector)
{
#if ENABLE(XPATH)
if (m_query.isEmpty())
return;
ExceptionCode ec = 0;
RefPtr<XPathResult> result = m_document->evaluate(m_query, m_document.get(), 0, XPathResult::ORDERED_NODE_SNAPSHOT_TYPE, 0, ec);
if (ec || !result)
return;
unsigned long size = result->snapshotLength(ec);
for (unsigned long i = 0; !ec && i < size; ++i) {
Node* node = result->snapshotItem(i, ec);
if (!ec)
resultCollector.add(node);
}
#endif
}
};
class MatchPlainTextJob : public MatchXPathJob {
public:
MatchPlainTextJob(Document* document, const String& query) : MatchXPathJob(document, query)
{
m_query = "//text()[contains(., '" + m_query + "')] | //comment()[contains(., '" + m_query + "')]";
}
virtual ~MatchPlainTextJob() { }
};
enum DOMBreakpointType {
SubtreeModified = 0,
AttributeModified,
NodeRemoved
};
const uint32_t inheritableDOMBreakpointTypesMask = (1 << SubtreeModified);
const int domBreakpointDerivedTypeShift = 16;
}
InspectorDOMAgent::InspectorDOMAgent(InspectorCSSStore* cssStore, InspectorFrontend* frontend)
: EventListener(InspectorDOMAgentType)
, m_cssStore(cssStore)
, m_frontend(frontend)
, m_lastNodeId(1)
, m_matchJobsTimer(this, &InspectorDOMAgent::onMatchJobsTimer)
{
}
InspectorDOMAgent::~InspectorDOMAgent()
{
reset();
}
void InspectorDOMAgent::reset()
{
searchCanceled();
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(InspectorValue::null());
}
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) {
// 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);
RefPtr<InspectorObject> value = buildObjectForNode(node, 0, &m_documentNodeToIdMap);
Node* previousSibling = innerPreviousSibling(node);
long prevId = previousSibling ? m_documentNodeToIdMap.get(previousSibling) : 0;
m_frontend->childNodeInserted(parentId, prevId, value.release());
// 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<const HTMLFrameOwnerElement*>(node);
stopListening(frameOwner->contentDocument());
cssStore()->removeDocument(frameOwner->contentDocument());
}
long 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);
}
}
}
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 && node->nodeType() != Node::DOCUMENT_FRAGMENT_NODE))
return;
if (m_childrenRequested.contains(nodeId))
return;
NodeToIdMap* nodeMap = m_idToNodesMap.get(nodeId);
RefPtr<InspectorArray> children = buildArrayForContainerChildren(node, 1, nodeMap);
m_childrenRequested.add(nodeId);
m_frontend->setChildNodes(nodeId, children.release());
}
long InspectorDOMAgent::inspectedNode(unsigned long num)
{
if (num < m_inspectedNodes.size())
return m_inspectedNodes[num];
return 0;
}
void InspectorDOMAgent::discardBindings()
{
m_documentNodeToIdMap.clear();
m_idToNode.clear();
releaseDanglingNodes();
m_childrenRequested.clear();
m_inspectedNodes.clear();
m_breakpoints.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;
}
void InspectorDOMAgent::getChildNodes(long nodeId)
{
pushChildNodesToFrontend(nodeId);
}
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.
if (!pushDocumentToFrontend())
return 0;
// Return id in case the node is known.
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) {
// 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 elementId, const String& name, const String& value, bool* success)
{
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);
*success = !ec;
}
}
void InspectorDOMAgent::removeAttribute(long elementId, const String& name, bool* success)
{
Node* node = nodeForId(elementId);
if (node && (node->nodeType() == Node::ELEMENT_NODE)) {
Element* element = static_cast<Element*>(node);
ExceptionCode ec = 0;
element->removeAttribute(name, ec);
*success = !ec;
}
}
void InspectorDOMAgent::removeNode(long nodeId, long* outNodeId)
{
Node* node = nodeForId(nodeId);
if (!node)
return;
Node* parentNode = node->parentNode();
if (!parentNode)
return;
ExceptionCode ec = 0;
parentNode->removeChild(node, ec);
if (ec)
return;
*outNodeId = nodeId;
}
void InspectorDOMAgent::changeTagName(long nodeId, const String& tagName, long* newId)
{
Node* oldNode = nodeForId(nodeId);
if (!oldNode || !oldNode->isElementNode())
return;
ExceptionCode ec = 0;
RefPtr<Element> newElem = oldNode->document()->createElement(tagName, ec);
if (ec)
return;
// Copy over the original node's attributes.
Element* oldElem = static_cast<Element*>(oldNode);
newElem->copyNonAttributeProperties(oldElem);
if (oldElem->attributes())
newElem->attributes()->setAttributes(*(oldElem->attributes(true)));
// Copy over the original node's children.
Node* child;
while ((child = oldNode->firstChild()))
newElem->appendChild(child, ec);
// Replace the old node with the new node
Node* parent = oldNode->parentNode();
parent->insertBefore(newElem, oldNode->nextSibling(), ec);
parent->removeChild(oldNode, ec);
if (ec)
return;
*newId = pushNodePathToFrontend(newElem.get());
if (m_childrenRequested.contains(nodeId))
pushChildNodesToFrontend(*newId);
}
void InspectorDOMAgent::getOuterHTML(long nodeId, WTF::String* outerHTML)
{
Node* node = nodeForId(nodeId);
if (!node || !node->isHTMLElement())
return;
*outerHTML = static_cast<HTMLElement*>(node)->outerHTML();
}
void InspectorDOMAgent::setOuterHTML(long nodeId, const String& outerHTML, long* newId)
{
Node* node = nodeForId(nodeId);
if (!node || !node->isHTMLElement())
return;
bool requiresTotalUpdate = node->nodeName() == "HTML" || node->nodeName() == "BODY" || node->nodeName() == "HEAD";
bool childrenRequested = m_childrenRequested.contains(nodeId);
Node* previousSibling = node->previousSibling();
Node* parentNode = node->parentNode();
HTMLElement* htmlElement = static_cast<HTMLElement*>(node);
ExceptionCode ec = 0;
htmlElement->setOuterHTML(outerHTML, ec);
if (ec)
return;
if (requiresTotalUpdate) {
Document* document = mainFrameDocument();
reset();
setDocument(document);
*newId = 0;
return;
}
Node* newNode = previousSibling ? previousSibling->nextSibling() : parentNode->firstChild();
*newId = pushNodePathToFrontend(newNode);
if (childrenRequested)
pushChildNodesToFrontend(*newId);
}
void InspectorDOMAgent::setTextNodeValue(long nodeId, const String& value, bool* success)
{
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);
*success = !ec;
}
}
void InspectorDOMAgent::getEventListenersForNode(long nodeId, long* outNodeId, RefPtr<InspectorArray>* listenersArray)
{
Node* node = nodeForId(nodeId);
*outNodeId = nodeId;
EventTargetData* d;
// Quick break if a null node or no listeners at all
if (!node || !(d = node->eventTargetData()))
return;
// Get the list of event types this Node is concerned with
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);
// Quick break if no useful listeners
size_t eventTypesLength = eventTypes.size();
if (!eventTypesLength)
return;
// The Node's Event Ancestors (not including self)
Vector<RefPtr<ContainerNode> > ancestors;
node->eventAncestors(ancestors);
// Nodes and their Listeners for the concerned event types (order is top to bottom)
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)));
}
}
// 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)->pushObject(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)->pushObject(buildObjectForEventListener(listener, info.eventType, info.node));
}
}
}
void InspectorDOMAgent::addInspectedNode(long nodeId)
{
m_inspectedNodes.prepend(nodeId);
while (m_inspectedNodes.size() > 5)
m_inspectedNodes.removeLast();
}
void InspectorDOMAgent::performSearch(const String& whitespaceTrimmedQuery, bool runSynchronously)
{
// FIXME: Few things are missing here:
// 1) Search works with node granularity - number of matches within node is not calculated.
// 2) There is no need to push all search results to the front-end at a time, pushing next / previous result
// is sufficient.
unsigned queryLength = whitespaceTrimmedQuery.length();
bool startTagFound = !whitespaceTrimmedQuery.find('<');
bool endTagFound = whitespaceTrimmedQuery.reverseFind('>') + 1 == queryLength;
String tagNameQuery = whitespaceTrimmedQuery;
if (startTagFound || endTagFound)
tagNameQuery = tagNameQuery.substring(startTagFound ? 1 : 0, endTagFound ? queryLength - 1 : queryLength);
if (!Document::isValidName(tagNameQuery))
tagNameQuery = "";
String attributeNameQuery = whitespaceTrimmedQuery;
if (!Document::isValidName(attributeNameQuery))
attributeNameQuery = "";
String escapedQuery = whitespaceTrimmedQuery;
escapedQuery.replace("'", "\\'");
String escapedTagNameQuery = tagNameQuery;
escapedTagNameQuery.replace("'", "\\'");
// Clear pending jobs.
searchCanceled();
// Find all frames, iframes and object elements to search their documents.
for (Frame* frame = mainFrameDocument()->frame(); frame; frame = frame->tree()->traverseNext()) {
Document* document = frame->document();
if (!document)
continue;
if (!tagNameQuery.isEmpty() && startTagFound && endTagFound) {
m_pendingMatchJobs.append(new MatchExactTagNamesJob(document, tagNameQuery));
m_pendingMatchJobs.append(new MatchPlainTextJob(document, escapedQuery));
continue;
}
if (!tagNameQuery.isEmpty() && startTagFound) {
m_pendingMatchJobs.append(new MatchXPathJob(document, "//*[starts-with(name(), '" + escapedTagNameQuery + "')]"));
m_pendingMatchJobs.append(new MatchPlainTextJob(document, escapedQuery));
continue;
}
if (!tagNameQuery.isEmpty() && endTagFound) {
// FIXME: we should have a matchEndOfTagNames search function if endTagFound is true but not startTagFound.
// This requires ends-with() support in XPath, WebKit only supports starts-with() and contains().
m_pendingMatchJobs.append(new MatchXPathJob(document, "//*[contains(name(), '" + escapedTagNameQuery + "')]"));
m_pendingMatchJobs.append(new MatchPlainTextJob(document, escapedQuery));
continue;
}
bool matchesEveryNode = whitespaceTrimmedQuery == "//*" || whitespaceTrimmedQuery == "*";
if (matchesEveryNode) {
// These queries will match every node. Matching everything isn't useful and can be slow for large pages,
// so limit the search functions list to plain text and attribute matching for these.
m_pendingMatchJobs.append(new MatchXPathJob(document, "//*[contains(@*, '" + escapedQuery + "')]"));
m_pendingMatchJobs.append(new MatchPlainTextJob(document, escapedQuery));
continue;
}
m_pendingMatchJobs.append(new MatchExactIdJob(document, whitespaceTrimmedQuery));
m_pendingMatchJobs.append(new MatchExactClassNamesJob(document, whitespaceTrimmedQuery));
m_pendingMatchJobs.append(new MatchExactTagNamesJob(document, tagNameQuery));
m_pendingMatchJobs.append(new MatchQuerySelectorAllJob(document, "[" + attributeNameQuery + "]"));
m_pendingMatchJobs.append(new MatchQuerySelectorAllJob(document, whitespaceTrimmedQuery));
m_pendingMatchJobs.append(new MatchXPathJob(document, "//*[contains(@*, '" + escapedQuery + "')]"));
if (!tagNameQuery.isEmpty())
m_pendingMatchJobs.append(new MatchXPathJob(document, "//*[contains(name(), '" + escapedTagNameQuery + "')]"));
m_pendingMatchJobs.append(new MatchPlainTextJob(document, escapedQuery));
m_pendingMatchJobs.append(new MatchXPathJob(document, whitespaceTrimmedQuery));
}
if (runSynchronously) {
// For tests.
ListHashSet<Node*> resultCollector;
for (Deque<MatchJob*>::iterator it = m_pendingMatchJobs.begin(); it != m_pendingMatchJobs.end(); ++it)
(*it)->match(resultCollector);
reportNodesAsSearchResults(resultCollector);
searchCanceled();
return;
}
m_matchJobsTimer.startOneShot(0);
}
void InspectorDOMAgent::searchCanceled()
{
if (m_matchJobsTimer.isActive())
m_matchJobsTimer.stop();
deleteAllValues(m_pendingMatchJobs);
m_pendingMatchJobs.clear();
m_searchResults.clear();
}
void InspectorDOMAgent::setDOMBreakpoint(long nodeId, long type)
{
Node* node = nodeForId(nodeId);
if (!node)
return;
uint32_t rootBit = 1 << type;
m_breakpoints.set(node, m_breakpoints.get(node) | rootBit);
if (rootBit & inheritableDOMBreakpointTypesMask) {
for (Node* child = innerFirstChild(node); child; child = innerNextSibling(child))
updateSubtreeBreakpoints(child, rootBit, true);
}
}
void InspectorDOMAgent::removeDOMBreakpoint(long nodeId, long type)
{
Node* node = nodeForId(nodeId);
if (!node)
return;
uint32_t rootBit = 1 << type;
uint32_t mask = m_breakpoints.get(node) & ~rootBit;
if (mask)
m_breakpoints.set(node, mask);
else
m_breakpoints.remove(node);
if ((rootBit & inheritableDOMBreakpointTypesMask) && !(mask & (rootBit << domBreakpointDerivedTypeShift))) {
for (Node* child = innerFirstChild(node); child; child = innerNextSibling(child))
updateSubtreeBreakpoints(child, rootBit, false);
}
}
bool InspectorDOMAgent::shouldBreakOnNodeInsertion(Node*, Node* parent, PassRefPtr<InspectorValue>* details)
{
if (!hasBreakpoint(parent, SubtreeModified))
return false;
RefPtr<InspectorObject> detailsObject = InspectorObject::create();
detailsObject->setObject("breakpoint", createBreakpoint(parent, SubtreeModified));
*details = detailsObject;
return true;
}
bool InspectorDOMAgent::shouldBreakOnNodeRemoval(Node* node, PassRefPtr<InspectorValue>* details)
{
bool hasNodeRemovedBreakpoint = hasBreakpoint(node, NodeRemoved);
bool hasAnyBreakpoint = hasNodeRemovedBreakpoint || hasBreakpoint(innerParentNode(node), SubtreeModified);
if (!hasAnyBreakpoint)
return false;
RefPtr<InspectorObject> detailsObject = InspectorObject::create();
if (hasNodeRemovedBreakpoint)
detailsObject->setObject("breakpoint", createBreakpoint(node, NodeRemoved));
else
detailsObject->setObject("breakpoint", createBreakpoint(innerParentNode(node), SubtreeModified));
*details = detailsObject;
return true;
}
bool InspectorDOMAgent::shouldBreakOnAttributeModification(Element* element, PassRefPtr<InspectorValue>* details)
{
if (!hasBreakpoint(element, AttributeModified))
return false;
RefPtr<InspectorObject> detailsObject = InspectorObject::create();
detailsObject->setObject("breakpoint", createBreakpoint(element, AttributeModified));
*details = detailsObject;
return true;
}
String InspectorDOMAgent::documentURLString(Document* document) const
{
if (!document || document->url().isNull())
return "";
return document->url().string();
}
PassRefPtr<InspectorObject> InspectorDOMAgent::buildObjectForNode(Node* node, int depth, NodeToIdMap* nodesMap)
{
RefPtr<InspectorObject> value = InspectorObject::create();
long id = bind(node, nodesMap);
String nodeName;
String localName;
String nodeValue;
switch (node->nodeType()) {
case Node::TEXT_NODE:
case Node::COMMENT_NODE:
case Node::CDATA_SECTION_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->setNumber("id", id);
value->setNumber("nodeType", node->nodeType());
value->setString("nodeName", nodeName);
value->setString("localName", localName);
value->setString("nodeValue", nodeValue);
if (node->nodeType() == Node::ELEMENT_NODE || node->nodeType() == Node::DOCUMENT_NODE || node->nodeType() == Node::DOCUMENT_FRAGMENT_NODE) {
int nodeCount = innerChildNodeCount(node);
value->setNumber("childNodeCount", nodeCount);
RefPtr<InspectorArray> children = buildArrayForContainerChildren(node, depth, nodesMap);
if (children->length() > 0)
value->setArray("children", children.release());
if (node->nodeType() == Node::ELEMENT_NODE) {
Element* element = static_cast<Element*>(node);
value->setArray("attributes", buildArrayForElementAttributes(element));
if (node->isFrameOwnerElement()) {
HTMLFrameOwnerElement* frameOwner = static_cast<HTMLFrameOwnerElement*>(node);
value->setString("documentURL", documentURLString(frameOwner->contentDocument()));
}
} else if (node->nodeType() == Node::DOCUMENT_NODE) {
Document* document = static_cast<Document*>(node);
value->setString("documentURL", documentURLString(document));
}
} else if (node->nodeType() == Node::DOCUMENT_TYPE_NODE) {
DocumentType* docType = static_cast<DocumentType*>(node);
value->setString("publicId", docType->publicId());
value->setString("systemId", docType->systemId());
value->setString("internalSubset", docType->internalSubset());
} else if (node->nodeType() == Node::ATTRIBUTE_NODE) {
Attr* attribute = static_cast<Attr*>(node);
value->setString("name", attribute->name());
value->setString("value", attribute->value());
}
return value.release();
}
PassRefPtr<InspectorArray> InspectorDOMAgent::buildArrayForElementAttributes(Element* element)
{
RefPtr<InspectorArray> attributesValue = InspectorArray::create();
// Go through all attributes and serialize them.
const NamedNodeMap* 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);
attributesValue->pushString(attribute->name().toString());
attributesValue->pushString(attribute->value());
}
return attributesValue.release();
}
PassRefPtr<InspectorArray> InspectorDOMAgent::buildArrayForContainerChildren(Node* container, int depth, NodeToIdMap* nodesMap)
{
RefPtr<InspectorArray> children = InspectorArray::create();
if (depth == 0) {
// Special case the_only text child.
if (innerChildNodeCount(container) == 1) {
Node *child = innerFirstChild(container);
if (child->nodeType() == Node::TEXT_NODE)
children->pushObject(buildObjectForNode(child, 0, nodesMap));
}
return children.release();
} else if (depth > 0) {
depth--;
}
for (Node *child = innerFirstChild(container); child; child = innerNextSibling(child))
children->pushObject(buildObjectForNode(child, depth, nodesMap));
return children.release();
}
PassRefPtr<InspectorObject> InspectorDOMAgent::buildObjectForEventListener(const RegisteredEventListener& registeredEventListener, const AtomicString& eventType, Node* node)
{
RefPtr<EventListener> eventListener = registeredEventListener.listener;
RefPtr<InspectorObject> value = InspectorObject::create();
value->setString("type", eventType);
value->setBoolean("useCapture", registeredEventListener.useCapture);
value->setBoolean("isAttribute", eventListener->isAttribute());
value->setNumber("nodeId", pushNodePathToFrontend(node));
value->setString("listenerBody", eventListenerHandlerBody(node->document(), eventListener.get()));
String sourceName;
int lineNumber;
if (eventListenerHandlerLocation(node->document(), eventListener.get(), sourceName, lineNumber)) {
value->setString("sourceName", sourceName);
value->setNumber("lineNumber", lineNumber);
}
return value.release();
}
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)
{
//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<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;
if (m_breakpoints.size()) {
uint32_t mask = m_breakpoints.get(innerParentNode(node));
uint32_t inheritableTypesMask = (mask | (mask >> domBreakpointDerivedTypeShift)) & inheritableDOMBreakpointTypesMask;
if (inheritableTypesMask)
updateSubtreeBreakpoints(node, inheritableTypesMask, true);
}
// We could be attaching existing subtree. Forget the bindings.
unbind(node, &m_documentNodeToIdMap);
Node* parent = node->parentNode();
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;
RefPtr<InspectorObject> value = buildObjectForNode(node, 0, &m_documentNodeToIdMap);
m_frontend->childNodeInserted(parentId, prevId, value.release());
}
}
void InspectorDOMAgent::didRemoveDOMNode(Node* node)
{
if (isWhitespace(node))
return;
if (m_breakpoints.size()) {
// Remove subtree breakpoints.
m_breakpoints.remove(node);
Vector<Node*> stack(1, innerFirstChild(node));
do {
Node* node = stack.last();
stack.removeLast();
if (!node)
continue;
m_breakpoints.remove(node);
stack.append(innerFirstChild(node));
stack.append(innerNextSibling(node));
} while (!stack.isEmpty());
}
Node* parent = node->parentNode();
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);
}
void InspectorDOMAgent::didModifyDOMAttr(Element* element)
{
long id = m_documentNodeToIdMap.get(element);
// If node is not mapped yet -> ignore the event.
if (!id)
return;
m_frontend->attributesUpdated(id, buildArrayForElementAttributes(element));
}
PassRefPtr<InspectorObject> InspectorDOMAgent::createBreakpoint(Node* node, long type)
{
RefPtr<InspectorObject> breakpoint = InspectorObject::create();
// Find breakpoint owner.
while (!(m_breakpoints.get(node) & (1 << type))) {
node = innerParentNode(node);
ASSERT(node);
}
long nodeId = m_documentNodeToIdMap.get(node);
ASSERT(nodeId);
breakpoint->setNumber("nodeId", nodeId);
breakpoint->setNumber("type", type);
return breakpoint.release();
}
bool InspectorDOMAgent::hasBreakpoint(Node* node, long type)
{
uint32_t rootBit = 1 << type;
uint32_t derivedBit = rootBit << domBreakpointDerivedTypeShift;
return m_breakpoints.get(node) & (rootBit | derivedBit);
}
void InspectorDOMAgent::updateSubtreeBreakpoints(Node* node, uint32_t rootMask, bool set)
{
uint32_t oldMask = m_breakpoints.get(node);
uint32_t derivedMask = rootMask << domBreakpointDerivedTypeShift;
uint32_t newMask = set ? oldMask | derivedMask : oldMask & ~derivedMask;
if (newMask)
m_breakpoints.set(node, newMask);
else
m_breakpoints.remove(node);
uint32_t newRootMask = rootMask & ~newMask;
if (!newRootMask)
return;
for (Node* child = innerFirstChild(node); child; child = innerNextSibling(child))
updateSubtreeBreakpoints(child, newRootMask, set);
}
void InspectorDOMAgent::getStyles(long nodeId, bool authorOnly, RefPtr<InspectorValue>* styles)
{
Node* node = nodeForId(nodeId);
if (!node || node->nodeType() != Node::ELEMENT_NODE)
return;
DOMWindow* defaultView = node->ownerDocument()->defaultView();
if (!defaultView)
return;
Element* element = static_cast<Element*>(node);
RefPtr<CSSComputedStyleDeclaration> computedStyleInfo = computedStyle(node, true); // Support the viewing of :visited information in computed style.
RefPtr<InspectorObject> result = InspectorObject::create();
if (element->style())
result->setObject("inlineStyle", buildObjectForStyle(element->style(), true));
result->setObject("computedStyle", buildObjectForStyle(computedStyleInfo.get(), false));
CSSStyleSelector* selector = element->ownerDocument()->styleSelector();
RefPtr<CSSRuleList> matchedRules = selector->styleRulesForElement(element, authorOnly);
result->setArray("matchedCSSRules", buildArrayForCSSRules(node->ownerDocument(), matchedRules.get()));
result->setObject("styleAttributes", buildObjectForAttributeStyles(element));
result->setArray("pseudoElements", buildArrayForPseudoElements(element, authorOnly));
RefPtr<InspectorObject> currentStyle = result;
Element* parentElement = element->parentElement();
while (parentElement) {
RefPtr<InspectorObject> parentStyle = InspectorObject::create();
currentStyle->setObject("parent", parentStyle);
if (parentElement->style() && parentElement->style()->length())
parentStyle->setObject("inlineStyle", buildObjectForStyle(parentElement->style(), true));
CSSStyleSelector* parentSelector = parentElement->ownerDocument()->styleSelector();
RefPtr<CSSRuleList> parentMatchedRules = parentSelector->styleRulesForElement(parentElement, authorOnly);
parentStyle->setArray("matchedCSSRules", buildArrayForCSSRules(parentElement->ownerDocument(), parentMatchedRules.get()));
parentElement = parentElement->parentElement();
currentStyle = parentStyle;
}
*styles = result.release();
}
void InspectorDOMAgent::getAllStyles(RefPtr<InspectorArray>* styles)
{
for (ListHashSet<RefPtr<Document> >::iterator it = m_documents.begin(); it != m_documents.end(); ++it) {
StyleSheetList* list = (*it)->styleSheets();
for (unsigned i = 0; i < list->length(); ++i) {
StyleSheet* styleSheet = list->item(i);
if (styleSheet->isCSSStyleSheet())
(*styles)->pushObject(buildObjectForStyleSheet((*it).get(), static_cast<CSSStyleSheet*>(styleSheet)));
}
}
}
void InspectorDOMAgent::getStyleSheet(long styleSheetId, RefPtr<InspectorObject>* styleSheetObject)
{
CSSStyleSheet* styleSheet = cssStore()->styleSheetForId(styleSheetId);
if (styleSheet && styleSheet->document())
*styleSheetObject = buildObjectForStyleSheet(styleSheet->document(), styleSheet);
}
void InspectorDOMAgent::getRuleRanges(long styleSheetId, RefPtr<InspectorValue>* ruleRange)
{
CSSStyleSheet* styleSheet = cssStore()->styleSheetForId(styleSheetId);
if (styleSheet && styleSheet->document()) {
HashMap<long, SourceRange> ruleRanges = cssStore()->getRuleRanges(styleSheet);
if (!ruleRanges.size())
return;
RefPtr<InspectorObject> result = InspectorObject::create();
for (HashMap<long, SourceRange>::iterator it = ruleRanges.begin(); it != ruleRanges.end(); ++it) {
if (it->second.second) {
RefPtr<InspectorObject> ruleRange = InspectorObject::create();
result->setObject(String::number(it->first).utf8().data(), ruleRange);
RefPtr<InspectorObject> bodyRange = InspectorObject::create();
ruleRange->setObject("bodyRange", bodyRange);
bodyRange->setNumber("start", it->second.first);
bodyRange->setNumber("end", it->second.second);
}
}
*ruleRange = result.release();
}
}
void InspectorDOMAgent::getInlineStyle(long nodeId, RefPtr<InspectorValue>* style)
{
Node* node = nodeForId(nodeId);
if (!node || node->nodeType() != Node::ELEMENT_NODE)
return;
*style = buildObjectForStyle(static_cast<Element*>(node)->style(), true);
}
void InspectorDOMAgent::getComputedStyle(long nodeId, RefPtr<InspectorValue>* style)
{
Node* node = nodeForId(nodeId);
if (!node || node->nodeType() != Node::ELEMENT_NODE)
return;
DOMWindow* defaultView = node->ownerDocument()->defaultView();
if (!defaultView)
return;
Element* element = static_cast<Element*>(node);
*style = buildObjectForStyle(defaultView->getComputedStyle(element, "").get(), false);
}
PassRefPtr<InspectorObject> InspectorDOMAgent::buildObjectForAttributeStyles(Element* element)
{
RefPtr<InspectorObject> styleAttributes = InspectorObject::create();
NamedNodeMap* attributes = element->attributes();
for (unsigned i = 0; attributes && i < attributes->length(); ++i) {
Attribute* attribute = attributes->attributeItem(i);
if (attribute->style()) {
String attributeName = attribute->localName();
styleAttributes->setObject(attributeName.utf8().data(), buildObjectForStyle(attribute->style(), true));
}
}
return styleAttributes;
}
PassRefPtr<InspectorArray> InspectorDOMAgent::buildArrayForCSSRules(Document* ownerDocument, CSSRuleList* matchedRules)
{
RefPtr<InspectorArray> matchedCSSRules = InspectorArray::create();
for (unsigned i = 0; matchedRules && i < matchedRules->length(); ++i) {
CSSRule* rule = matchedRules->item(i);
if (rule->type() == CSSRule::STYLE_RULE)
matchedCSSRules->pushObject(buildObjectForRule(ownerDocument, static_cast<CSSStyleRule*>(rule)));
}
return matchedCSSRules.release();
}
PassRefPtr<InspectorArray> InspectorDOMAgent::buildArrayForPseudoElements(Element* element, bool authorOnly)
{
RefPtr<InspectorArray> result = InspectorArray::create();
CSSStyleSelector* selector = element->ownerDocument()->styleSelector();
RefPtr<RenderStyle> renderStyle = element->styleForRenderer();
for (PseudoId pseudoId = FIRST_PUBLIC_PSEUDOID; pseudoId < AFTER_LAST_INTERNAL_PSEUDOID; pseudoId = static_cast<PseudoId>(pseudoId + 1)) {
RefPtr<CSSRuleList> matchedRules = selector->pseudoStyleRulesForElement(element, pseudoId, authorOnly);
if (matchedRules && matchedRules->length()) {
RefPtr<InspectorObject> pseudoStyles = InspectorObject::create();
pseudoStyles->setNumber("pseudoId", static_cast<int>(pseudoId));
pseudoStyles->setArray("rules", buildArrayForCSSRules(element->ownerDocument(), matchedRules.get()));
result->pushObject(pseudoStyles.release());
}
}
return result.release();
}
void InspectorDOMAgent::applyStyleText(long styleId, const String& styleText, const String& propertyName, bool* success, RefPtr<InspectorValue>* styleObject, RefPtr<InspectorArray>* changedPropertiesArray)
{
CSSStyleDeclaration* style = cssStore()->styleForId(styleId);
if (!style)
return;
// Remove disabled property entry for property with given name.
DisabledStyleDeclaration* disabledStyle = cssStore()->disabledStyleForId(styleId, false);
if (disabledStyle)
disabledStyle->remove(propertyName);
int styleTextLength = styleText.length();
RefPtr<CSSMutableStyleDeclaration> tempMutableStyle = CSSMutableStyleDeclaration::create();
tempMutableStyle->parseDeclaration(styleText);
CSSStyleDeclaration* tempStyle = static_cast<CSSStyleDeclaration*>(tempMutableStyle.get());
if (tempStyle->length() || !styleTextLength) {
ExceptionCode ec = 0;
// The input was parsable or the user deleted everything, so remove the
// original property from the real style declaration. If this represents
// a shorthand remove all the longhand properties.
if (style->getPropertyShorthand(propertyName).isEmpty()) {
Vector<String> longhandProps = longhandProperties(style, propertyName);
for (unsigned i = 0; !ec && i < longhandProps.size(); ++i)
style->removeProperty(longhandProps[i], ec);
}
// Explicitly delete properties with no shorthands as well as shorthands themselves.
if (!ec)
style->removeProperty(propertyName, ec);
if (ec)
return;
}
// Notify caller that the property was successfully deleted.
if (!styleTextLength) {
(*changedPropertiesArray)->pushString(propertyName);
*success = true;
return;
}
if (!tempStyle->length())
return;
// Iterate of the properties on the test element's style declaration and
// add them to the real style declaration. We take care to move shorthands.
HashSet<String> foundShorthands;
Vector<String> changedProperties;
for (unsigned i = 0; i < tempStyle->length(); ++i) {
String name = tempStyle->item(i);
String shorthand = tempStyle->getPropertyShorthand(name);
if (!shorthand.isEmpty() && foundShorthands.contains(shorthand))
continue;
String value;
String priority;
if (!shorthand.isEmpty()) {
value = shorthandValue(tempStyle, shorthand);
priority = shorthandPriority(tempStyle, shorthand);
foundShorthands.add(shorthand);
name = shorthand;
} else {
value = tempStyle->getPropertyValue(name);
priority = tempStyle->getPropertyPriority(name);
}
// Set the property on the real style declaration.
ExceptionCode ec = 0;
style->setProperty(name, value, priority, ec);
// Remove disabled property entry for property with this name.
if (disabledStyle)
disabledStyle->remove(name);
changedProperties.append(name);
}
*success = true;
*styleObject = buildObjectForStyle(style, true);
*changedPropertiesArray = toArray(changedProperties);
}
void InspectorDOMAgent::setStyleText(long styleId, const String& cssText, bool* success)
{
CSSStyleDeclaration* style = cssStore()->styleForId(styleId);
if (!style)
return;
ExceptionCode ec = 0;
style->setCssText(cssText, ec);
*success = !ec;
}
void InspectorDOMAgent::setStyleProperty(long styleId, const String& name, const String& value, bool* success)
{
CSSStyleDeclaration* style = cssStore()->styleForId(styleId);
if (!style)
return;
ExceptionCode ec = 0;
style->setProperty(name, value, ec);
*success = !ec;
}
void InspectorDOMAgent::toggleStyleEnabled(long styleId, const String& propertyName, bool disabled, RefPtr<InspectorValue>* styleObject)
{
CSSStyleDeclaration* style = cssStore()->styleForId(styleId);
if (!style)
return;
DisabledStyleDeclaration* disabledStyle = cssStore()->disabledStyleForId(styleId, true);
// TODO: make sure this works with shorthands right.
ExceptionCode ec = 0;
if (disabled) {
disabledStyle->set(propertyName, std::make_pair(style->getPropertyValue(propertyName), style->getPropertyPriority(propertyName)));
if (!ec)
style->removeProperty(propertyName, ec);
} else if (disabledStyle->contains(propertyName)) {
PropertyValueAndPriority valueAndPriority = disabledStyle->get(propertyName);
style->setProperty(propertyName, valueAndPriority.first, valueAndPriority.second, ec);
if (!ec)
disabledStyle->remove(propertyName);
}
if (ec)
return;
*styleObject = buildObjectForStyle(style, true);
}
void InspectorDOMAgent::setRuleSelector(long ruleId, const String& selector, long selectedNodeId, RefPtr<InspectorValue>* ruleObject, bool* selectorAffectsNode)
{
CSSStyleRule* rule = cssStore()->ruleForId(ruleId);
if (!rule)
return;
Node* node = nodeForId(selectedNodeId);
CSSStyleSheet* styleSheet = rule->parentStyleSheet();
ExceptionCode ec = 0;
styleSheet->addRule(selector, rule->style()->cssText(), ec);
if (ec)
return;
CSSStyleRule* newRule = static_cast<CSSStyleRule*>(styleSheet->item(styleSheet->length() - 1));
for (unsigned i = 0; i < styleSheet->length(); ++i) {
if (styleSheet->item(i) == rule) {
styleSheet->deleteRule(i, ec);
break;
}
}
if (ec)
return;
*selectorAffectsNode = ruleAffectsNode(newRule, node);
*ruleObject = buildObjectForRule(node->ownerDocument(), newRule);
}
void InspectorDOMAgent::addRule(const String& selector, long selectedNodeId, RefPtr<InspectorValue>* ruleObject, bool* selectorAffectsNode)
{
Node* node = nodeForId(selectedNodeId);
if (!node)
return;
CSSStyleSheet* styleSheet = cssStore()->inspectorStyleSheet(node->ownerDocument(), true);
if (!styleSheet)
return; // could not add a stylesheet to the ownerDocument
ExceptionCode ec = 0;
styleSheet->addRule(selector, "", ec);
if (ec)
return;
CSSStyleRule* newRule = static_cast<CSSStyleRule*>(styleSheet->item(styleSheet->length() - 1));
*selectorAffectsNode = ruleAffectsNode(newRule, node);
*ruleObject = buildObjectForRule(node->ownerDocument(), newRule);
}
void InspectorDOMAgent::getSupportedCSSProperties(RefPtr<InspectorArray>* cssProperties)
{
RefPtr<InspectorArray> properties = InspectorArray::create();
for (int i = 0; i < numCSSProperties; ++i)
properties->pushString(propertyNameStrings[i]);
*cssProperties = properties.release();
}
PassRefPtr<InspectorObject> InspectorDOMAgent::buildObjectForStyle(CSSStyleDeclaration* style, bool bind)
{
RefPtr<InspectorObject> result = InspectorObject::create();
if (bind) {
long styleId = cssStore()->bindStyle(style);
result->setNumber("id", styleId);
CSSStyleSheet* parentStyleSheet = getParentStyleSheet(style);
if (parentStyleSheet)
result->setNumber("parentStyleSheetId", cssStore()->bindStyleSheet(parentStyleSheet));
DisabledStyleDeclaration* disabledStyle = cssStore()->disabledStyleForId(styleId, false);
if (disabledStyle)
result->setArray("disabled", buildArrayForDisabledStyleProperties(disabledStyle));
}
result->setString("width", style->getPropertyValue("width"));
result->setString("height", style->getPropertyValue("height"));
populateObjectWithStyleProperties(style, result.get());
return result.release();
}
void InspectorDOMAgent::populateObjectWithStyleProperties(CSSStyleDeclaration* style, InspectorObject* result)
{
RefPtr<InspectorArray> properties = InspectorArray::create();
RefPtr<InspectorObject> shorthandValues = InspectorObject::create();
HashSet<String> foundShorthands;
for (unsigned i = 0; i < style->length(); ++i) {
RefPtr<InspectorObject> property = InspectorObject::create();
String name = style->item(i);
property->setString("name", name);
property->setString("priority", style->getPropertyPriority(name));
property->setBoolean("implicit", style->isPropertyImplicit(name));
String shorthand = style->getPropertyShorthand(name);
property->setString("shorthand", shorthand);
if (!shorthand.isEmpty() && !foundShorthands.contains(shorthand)) {
foundShorthands.add(shorthand);
shorthandValues->setString(shorthand, shorthandValue(style, shorthand));
}
property->setString("value", style->getPropertyValue(name));
properties->pushObject(property.release());
}
result->setArray("properties", properties);
result->setObject("shorthandValues", shorthandValues);
}
PassRefPtr<InspectorArray> InspectorDOMAgent::buildArrayForDisabledStyleProperties(DisabledStyleDeclaration* declaration)
{
RefPtr<InspectorArray> properties = InspectorArray::create();
for (DisabledStyleDeclaration::iterator it = declaration->begin(); it != declaration->end(); ++it) {
RefPtr<InspectorObject> property = InspectorObject::create();
property->setString("name", it->first);
property->setString("value", it->second.first);
property->setString("priority", it->second.second);
properties->pushObject(property.release());
}
return properties.release();
}
PassRefPtr<InspectorObject> InspectorDOMAgent::buildObjectForStyleSheet(Document* ownerDocument, CSSStyleSheet* styleSheet)
{
RefPtr<InspectorObject> result = InspectorObject::create();
long id = cssStore()->bindStyleSheet(styleSheet);
result->setNumber("id", id);
result->setBoolean("disabled", styleSheet->disabled());
result->setString("href", styleSheet->href());
result->setString("title", styleSheet->title());
result->setNumber("documentElementId", m_documentNodeToIdMap.get(styleSheet->document()));
RefPtr<InspectorArray> cssRules = InspectorArray::create();
PassRefPtr<CSSRuleList> cssRuleList = CSSRuleList::create(styleSheet, true);
if (cssRuleList) {
for (unsigned i = 0; i < cssRuleList->length(); ++i) {
CSSRule* rule = cssRuleList->item(i);
if (rule->isStyleRule())
cssRules->pushObject(buildObjectForRule(ownerDocument, static_cast<CSSStyleRule*>(rule)));
}
}
result->setArray("cssRules", cssRules.release());
return result.release();
}
PassRefPtr<InspectorObject> InspectorDOMAgent::buildObjectForRule(Document* ownerDocument, CSSStyleRule* rule)
{
CSSStyleSheet* parentStyleSheet = rule->parentStyleSheet();
RefPtr<InspectorObject> result = InspectorObject::create();
result->setString("selectorText", rule->selectorText());
result->setString("cssText", rule->cssText());
result->setNumber("sourceLine", rule->sourceLine());
result->setString("documentURL", documentURLString(ownerDocument));
if (parentStyleSheet) {
RefPtr<InspectorObject> parentStyleSheetValue = InspectorObject::create();
parentStyleSheetValue->setString("href", parentStyleSheet->href());
parentStyleSheetValue->setNumber("id", cssStore()->bindStyleSheet(parentStyleSheet));
result->setObject("parentStyleSheet", parentStyleSheetValue.release());
}
bool isUserAgent = parentStyleSheet && !parentStyleSheet->ownerNode() && parentStyleSheet->href().isEmpty();
bool isUser = parentStyleSheet && parentStyleSheet->ownerNode() && parentStyleSheet->ownerNode()->nodeName() == "#document";
result->setBoolean("isUserAgent", isUserAgent);
result->setBoolean("isUser", isUser);
result->setBoolean("isViaInspector", rule->parentStyleSheet() == cssStore()->inspectorStyleSheet(ownerDocument, false));
// Bind editable scripts only.
bool bind = !isUserAgent && !isUser;
result->setObject("style", buildObjectForStyle(rule->style(), bind));
if (bind)
result->setNumber("id", cssStore()->bindRule(rule));
return result.release();
}
Vector<String> InspectorDOMAgent::longhandProperties(CSSStyleDeclaration* style, const String& shorthandProperty)
{
Vector<String> properties;
HashSet<String> foundProperties;
for (unsigned i = 0; i < style->length(); ++i) {
String individualProperty = style->item(i);
if (foundProperties.contains(individualProperty) || style->getPropertyShorthand(individualProperty) != shorthandProperty)
continue;
foundProperties.add(individualProperty);
properties.append(individualProperty);
}
return properties;
}
String InspectorDOMAgent::shorthandValue(CSSStyleDeclaration* style, const String& shorthandProperty)
{
String value = style->getPropertyValue(shorthandProperty);
if (value.isEmpty()) {
// Some shorthands (like border) return a null value, so compute a shorthand value.
// FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15823 is fixed.
for (unsigned i = 0; i < style->length(); ++i) {
String individualProperty = style->item(i);
if (style->getPropertyShorthand(individualProperty) != shorthandProperty)
continue;
if (style->isPropertyImplicit(individualProperty))
continue;
String individualValue = style->getPropertyValue(individualProperty);
if (individualValue == "initial")
continue;
if (value.length())
value.append(" ");
value.append(individualValue);
}
}
return value;
}
String InspectorDOMAgent::shorthandPriority(CSSStyleDeclaration* style, const String& shorthandProperty)
{
String priority = style->getPropertyPriority(shorthandProperty);
if (priority.isEmpty()) {
for (unsigned i = 0; i < style->length(); ++i) {
String individualProperty = style->item(i);
if (style->getPropertyShorthand(individualProperty) != shorthandProperty)
continue;
priority = style->getPropertyPriority(individualProperty);
break;
}
}
return priority;
}
bool InspectorDOMAgent::ruleAffectsNode(CSSStyleRule* rule, Node* node)
{
if (!node)
return false;
ExceptionCode ec = 0;
RefPtr<NodeList> nodes = node->ownerDocument()->querySelectorAll(rule->selectorText(), ec);
if (ec)
return false;
for (unsigned i = 0; i < nodes->length(); ++i) {
if (nodes->item(i) == node)
return true;
}
return false;
}
Node* InspectorDOMAgent::nodeForPath(const String& path)
{
// The path is of form "1,HTML,2,BODY,1,DIV"
Node* node = mainFrameDocument();
if (!node)
return 0;
Vector<String> pathTokens;
path.split(",", false, pathTokens);
if (!pathTokens.size())
return 0;
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;
}
PassRefPtr<InspectorArray> InspectorDOMAgent::toArray(const Vector<String>& data)
{
RefPtr<InspectorArray> result = InspectorArray::create();
for (unsigned i = 0; i < data.size(); ++i)
result->pushString(data[i]);
return result.release();
}
CSSStyleSheet* InspectorDOMAgent::getParentStyleSheet(CSSStyleDeclaration* style)
{
CSSStyleSheet* parentStyleSheet = style->parentRule() ? style->parentRule()->parentStyleSheet() : 0;
if (!parentStyleSheet) {
StyleBase* parent = style->parent();
if (parent && parent->isCSSStyleSheet()) {
parentStyleSheet = static_cast<CSSStyleSheet*>(parent);
if (!parentStyleSheet->length())
return 0;
}
}
return parentStyleSheet;
}
void InspectorDOMAgent::onMatchJobsTimer(Timer<InspectorDOMAgent>*)
{
if (!m_pendingMatchJobs.size()) {
searchCanceled();
return;
}
ListHashSet<Node*> resultCollector;
MatchJob* job = m_pendingMatchJobs.takeFirst();
job->match(resultCollector);
delete job;
reportNodesAsSearchResults(resultCollector);
m_matchJobsTimer.startOneShot(0.025);
}
void InspectorDOMAgent::reportNodesAsSearchResults(ListHashSet<Node*>& resultCollector)
{
RefPtr<InspectorArray> nodeIds = InspectorArray::create();
for (ListHashSet<Node*>::iterator it = resultCollector.begin(); it != resultCollector.end(); ++it) {
if (m_searchResults.contains(*it))
continue;
m_searchResults.add(*it);
nodeIds->pushNumber(static_cast<long long>(pushNodePathToFrontend(*it)));
}
m_frontend->addNodesToSearchResult(nodeIds.release());
}
void InspectorDOMAgent::copyNode(long nodeId)
{
Node* node = nodeForId(nodeId);
if (!node)
return;
String markup = createMarkup(node);
Pasteboard::generalPasteboard()->writePlainText(markup);
}
void InspectorDOMAgent::pushNodeByPathToFrontend(const String& path, long* nodeId)
{
if (Node* node = nodeForPath(path))
*nodeId = pushNodePathToFrontend(node);
}
} // namespace WebCore
#endif // ENABLE(INSPECTOR)