/* * 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 "CSSPropertySourceData.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 #include #include #include #include #include namespace WebCore { class MatchJob { public: virtual void match(ListHashSet& resultCollector) = 0; virtual ~MatchJob() { } protected: MatchJob(Document* document, const String& query) : m_document(document) , m_query(query) { } void addNodesToResults(PassRefPtr nodes, ListHashSet& resultCollector) { for (unsigned i = 0; nodes && i < nodes->length(); ++i) resultCollector.add(nodes->item(i)); } RefPtr 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& 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& 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& 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& resultCollector) { if (m_query.isEmpty()) return; ExceptionCode ec = 0; RefPtr 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& resultCollector) { #if ENABLE(XPATH) if (m_query.isEmpty()) return; ExceptionCode ec = 0; RefPtr 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 > copy = m_documents; for (ListHashSet >::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 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(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 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::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 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(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(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 newElem = oldNode->document()->createElement(tagName, ec); if (ec) return; // Copy over the original node's attributes. Element* oldElem = static_cast(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(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(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(node); ExceptionCode ec = 0; text_node->replaceWholeText(value, ec); *success = !ec; } } void InspectorDOMAgent::getEventListenersForNode(long nodeId, long* outNodeId, RefPtr* 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 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 > ancestors; node->eventAncestors(ancestors); // Nodes and their Listeners for the concerned event types (order is top to bottom) Vector 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(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 resultCollector; for (Deque::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* details) { if (hasBreakpoint(parent, SubtreeModified)) { *details = descriptionForDOMEvent(parent, SubtreeModified, true); return true; } return false; } bool InspectorDOMAgent::shouldBreakOnNodeRemoval(Node* node, PassRefPtr* details) { if (hasBreakpoint(node, NodeRemoved)) { *details = descriptionForDOMEvent(node, NodeRemoved, false); return true; } if (hasBreakpoint(innerParentNode(node), SubtreeModified)) { *details = descriptionForDOMEvent(node, SubtreeModified, false); return true; } return false; } bool InspectorDOMAgent::shouldBreakOnAttributeModification(Element* element, PassRefPtr* details) { if (hasBreakpoint(element, AttributeModified)) { *details = descriptionForDOMEvent(element, AttributeModified, false); return true; } return false; } PassRefPtr InspectorDOMAgent::descriptionForDOMEvent(Node* target, long breakpointType, bool insertion) { ASSERT(hasBreakpoint(target, breakpointType)); RefPtr description = InspectorObject::create(); Node* breakpointOwner = target; if ((1 << breakpointType) & inheritableDOMBreakpointTypesMask) { // For inheritable breakpoint types, target node isn't always the same as the node that owns a breakpoint. // Target node may be unknown to frontend, so we need to push it first. long targetNodeId = pushNodePathToFrontend(target); ASSERT(targetNodeId); description->setNumber("targetNodeId", targetNodeId); // Find breakpoint owner node. if (!insertion) breakpointOwner = innerParentNode(target); ASSERT(breakpointOwner); while (!(m_breakpoints.get(breakpointOwner) & (1 << breakpointType))) { breakpointOwner = innerParentNode(breakpointOwner); ASSERT(breakpointOwner); } if (breakpointType == SubtreeModified) description->setBoolean("insertion", insertion); } long breakpointOwnerNodeId = m_documentNodeToIdMap.get(breakpointOwner); ASSERT(breakpointOwnerNodeId); RefPtr breakpoint = InspectorObject::create(); breakpoint->setNumber("nodeId", breakpointOwnerNodeId); breakpoint->setNumber("type", breakpointType); description->setObject("breakpoint", breakpoint); return description; } String InspectorDOMAgent::documentURLString(Document* document) const { if (!document || document->url().isNull()) return ""; return document->url().string(); } PassRefPtr InspectorDOMAgent::buildObjectForNode(Node* node, int depth, NodeToIdMap* nodesMap) { RefPtr 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 children = buildArrayForContainerChildren(node, depth, nodesMap); if (children->length() > 0) value->setArray("children", children.release()); if (node->nodeType() == Node::ELEMENT_NODE) { Element* element = static_cast(node); value->setArray("attributes", buildArrayForElementAttributes(element)); if (node->isFrameOwnerElement()) { HTMLFrameOwnerElement* frameOwner = static_cast(node); value->setString("documentURL", documentURLString(frameOwner->contentDocument())); } } else if (node->nodeType() == Node::DOCUMENT_NODE) { Document* document = static_cast(node); value->setString("documentURL", documentURLString(document)); } } else if (node->nodeType() == Node::DOCUMENT_TYPE_NODE) { DocumentType* docType = static_cast(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(node); value->setString("name", attribute->name()); value->setString("value", attribute->value()); } return value.release(); } PassRefPtr InspectorDOMAgent::buildArrayForElementAttributes(Element* element) { RefPtr 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 InspectorDOMAgent::buildArrayForContainerChildren(Node* container, int depth, NodeToIdMap* nodesMap) { RefPtr 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 InspectorDOMAgent::buildObjectForEventListener(const RegisteredEventListener& registeredEventListener, const AtomicString& eventType, Node* node) { RefPtr eventListener = registeredEventListener.listener; RefPtr 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(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(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 >::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 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 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)); } 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* 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(node); RefPtr computedStyleInfo = computedStyle(node, true); // Support the viewing of :visited information in computed style. RefPtr 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 matchedRules = selector->styleRulesForElement(element, authorOnly, true); result->setArray("matchedCSSRules", buildArrayForCSSRules(node->ownerDocument(), matchedRules.get())); result->setObject("styleAttributes", buildObjectForAttributeStyles(element)); result->setArray("pseudoElements", buildArrayForPseudoElements(element, authorOnly)); RefPtr currentStyle = result; Element* parentElement = element->parentElement(); while (parentElement) { RefPtr 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 parentMatchedRules = parentSelector->styleRulesForElement(parentElement, authorOnly, true); parentStyle->setArray("matchedCSSRules", buildArrayForCSSRules(parentElement->ownerDocument(), parentMatchedRules.get())); parentElement = parentElement->parentElement(); currentStyle = parentStyle; } *styles = result.release(); } void InspectorDOMAgent::getAllStyles(RefPtr* styles) { for (ListHashSet >::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(styleSheet))); } } } void InspectorDOMAgent::getStyleSheet(long styleSheetId, RefPtr* styleSheetObject) { CSSStyleSheet* styleSheet = cssStore()->styleSheetForId(styleSheetId); if (styleSheet && styleSheet->document()) *styleSheetObject = buildObjectForStyleSheet(styleSheet->document(), styleSheet); } void InspectorDOMAgent::getStyleSourceData(long styleId, RefPtr* dataObject) { CSSStyleDeclaration* style = cssStore()->styleForId(styleId); if (!style) return; RefPtr sourceData = CSSStyleSourceData::create(); bool success = cssStore()->getStyleSourceData(style, &sourceData); if (!success) return; RefPtr result = InspectorObject::create(); RefPtr bodyRange = InspectorObject::create(); result->setObject("bodyRange", bodyRange); bodyRange->setNumber("start", sourceData->styleBodyRange.start); bodyRange->setNumber("end", sourceData->styleBodyRange.end); RefPtr propertyRanges = InspectorArray::create(); result->setArray("propertyData", propertyRanges); Vector& propertyData = sourceData->propertyData; for (Vector::iterator it = propertyData.begin(); it != propertyData.end(); ++it) { RefPtr propertyRange = InspectorObject::create(); propertyRange->setString("name", it->name); propertyRange->setString("value", it->value); propertyRange->setBoolean("important", it->important); propertyRange->setBoolean("parsed", it->parsedOk); propertyRange->setNumber("start", it->range.start); propertyRange->setNumber("end", it->range.end); propertyRanges->pushObject(propertyRange); } *dataObject = result.release(); } void InspectorDOMAgent::getInlineStyle(long nodeId, RefPtr* style) { Node* node = nodeForId(nodeId); if (!node || node->nodeType() != Node::ELEMENT_NODE) return; *style = buildObjectForStyle(static_cast(node)->style(), true); } void InspectorDOMAgent::getComputedStyle(long nodeId, RefPtr* 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(node); *style = buildObjectForStyle(defaultView->getComputedStyle(element, "").get(), false); } PassRefPtr InspectorDOMAgent::buildObjectForAttributeStyles(Element* element) { RefPtr 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 InspectorDOMAgent::buildArrayForCSSRules(Document* ownerDocument, CSSRuleList* matchedRules) { RefPtr 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(rule))); } return matchedCSSRules.release(); } PassRefPtr InspectorDOMAgent::buildArrayForPseudoElements(Element* element, bool authorOnly) { RefPtr result = InspectorArray::create(); CSSStyleSelector* selector = element->ownerDocument()->styleSelector(); RefPtr renderStyle = element->styleForRenderer(); for (PseudoId pseudoId = FIRST_PUBLIC_PSEUDOID; pseudoId < AFTER_LAST_INTERNAL_PSEUDOID; pseudoId = static_cast(pseudoId + 1)) { RefPtr matchedRules = selector->pseudoStyleRulesForElement(element, pseudoId, authorOnly, true); if (matchedRules && matchedRules->length()) { RefPtr pseudoStyles = InspectorObject::create(); pseudoStyles->setNumber("pseudoId", static_cast(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* styleObject, RefPtr* 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 tempMutableStyle = CSSMutableStyleDeclaration::create(); tempMutableStyle->parseDeclaration(styleText); CSSStyleDeclaration* tempStyle = static_cast(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 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 foundShorthands; Vector 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* 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* 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(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* 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(styleSheet->item(styleSheet->length() - 1)); *selectorAffectsNode = ruleAffectsNode(newRule, node); *ruleObject = buildObjectForRule(node->ownerDocument(), newRule); } void InspectorDOMAgent::getSupportedCSSProperties(RefPtr* cssProperties) { RefPtr properties = InspectorArray::create(); for (int i = 0; i < numCSSProperties; ++i) properties->pushString(propertyNameStrings[i]); *cssProperties = properties.release(); } PassRefPtr InspectorDOMAgent::buildObjectForStyle(CSSStyleDeclaration* style, bool bind) { RefPtr result = InspectorObject::create(); if (bind) { long styleId = cssStore()->bindStyle(style); result->setNumber("id", styleId); CSSStyleSheet* parentStyleSheet = InspectorCSSStore::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 properties = InspectorArray::create(); RefPtr shorthandValues = InspectorObject::create(); HashSet foundShorthands; for (unsigned i = 0; i < style->length(); ++i) { RefPtr 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 InspectorDOMAgent::buildArrayForDisabledStyleProperties(DisabledStyleDeclaration* declaration) { RefPtr properties = InspectorArray::create(); for (DisabledStyleDeclaration::iterator it = declaration->begin(); it != declaration->end(); ++it) { RefPtr 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 InspectorDOMAgent::buildObjectForStyleSheet(Document* ownerDocument, CSSStyleSheet* styleSheet) { RefPtr 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 cssRules = InspectorArray::create(); PassRefPtr 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(rule))); } } result->setArray("cssRules", cssRules.release()); return result.release(); } PassRefPtr InspectorDOMAgent::buildObjectForRule(Document* ownerDocument, CSSStyleRule* rule) { CSSStyleSheet* parentStyleSheet = rule->parentStyleSheet(); RefPtr 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 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 InspectorDOMAgent::longhandProperties(CSSStyleDeclaration* style, const String& shorthandProperty) { Vector properties; HashSet 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 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 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 InspectorDOMAgent::toArray(const Vector& data) { RefPtr result = InspectorArray::create(); for (unsigned i = 0; i < data.size(); ++i) result->pushString(data[i]); return result.release(); } void InspectorDOMAgent::onMatchJobsTimer(Timer*) { if (!m_pendingMatchJobs.size()) { searchCanceled(); return; } ListHashSet resultCollector; MatchJob* job = m_pendingMatchJobs.takeFirst(); job->match(resultCollector); delete job; reportNodesAsSearchResults(resultCollector); m_matchJobsTimer.startOneShot(0.025); } void InspectorDOMAgent::reportNodesAsSearchResults(ListHashSet& resultCollector) { RefPtr nodeIds = InspectorArray::create(); for (ListHashSet::iterator it = resultCollector.begin(); it != resultCollector.end(); ++it) { if (m_searchResults.contains(*it)) continue; m_searchResults.add(*it); nodeIds->pushNumber(static_cast(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)