/* * Copyright (C) 2009, 2015 Apple Inc. All rights reserved. * Copyright (C) 2011 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 Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "InspectorDOMAgent.h" #include "AXObjectCache.h" #include "AccessibilityNodeObject.h" #include "Attr.h" #include "CSSComputedStyleDeclaration.h" #include "CSSPropertyNames.h" #include "CSSPropertySourceData.h" #include "CSSRule.h" #include "CSSRuleList.h" #include "CSSStyleRule.h" #include "CSSStyleSheet.h" #include "CharacterData.h" #include "ContainerNode.h" #include "Cookie.h" #include "CookieJar.h" #include "DOMEditor.h" #include "DOMPatchSupport.h" #include "DOMWindow.h" #include "Document.h" #include "DocumentFragment.h" #include "DocumentType.h" #include "Element.h" #include "Event.h" #include "EventListener.h" #include "EventNames.h" #include "ExceptionCodeDescription.h" #include "FrameTree.h" #include "HTMLElement.h" #include "HTMLFrameOwnerElement.h" #include "HTMLNames.h" #include "HTMLTemplateElement.h" #include "HitTestResult.h" #include "InspectorHistory.h" #include "InspectorNodeFinder.h" #include "InspectorOverlay.h" #include "InspectorPageAgent.h" #include "InstrumentingAgents.h" #include "IntRect.h" #include "JSEventListener.h" #include "JSNode.h" #include "MainFrame.h" #include "MutationEvent.h" #include "Node.h" #include "NodeList.h" #include "Page.h" #include "Pasteboard.h" #include "PseudoElement.h" #include "RenderStyle.h" #include "RenderStyleConstants.h" #include "ScriptState.h" #include "Settings.h" #include "ShadowRoot.h" #include "StyleProperties.h" #include "StyleResolver.h" #include "StyleSheetList.h" #include "Text.h" #include "XPathResult.h" #include "htmlediting.h" #include "markup.h" #include #include #include #include #include #include using namespace Inspector; namespace WebCore { using namespace HTMLNames; static const size_t maxTextSize = 10000; static const UChar ellipsisUChar[] = { 0x2026, 0 }; static Color parseColor(const InspectorObject* colorObject) { if (!colorObject) return Color::transparent; int r = 0; int g = 0; int b = 0; if (!colorObject->getInteger("r", r) || !colorObject->getInteger("g", g) || !colorObject->getInteger("b", b)) return Color::transparent; double a = 1.0; if (!colorObject->getDouble("a", a)) return Color(r, g, b); // Clamp alpha to the [0..1] range. if (a < 0) a = 0; else if (a > 1) a = 1; return Color(r, g, b, static_cast(a * 255)); } static Color parseConfigColor(const String& fieldName, const InspectorObject* configObject) { RefPtr colorObject; configObject->getObject(fieldName, colorObject); return parseColor(colorObject.get()); } static bool parseQuad(const InspectorArray& quadArray, FloatQuad* quad) { const size_t coordinatesInQuad = 8; double coordinates[coordinatesInQuad]; if (quadArray.length() != coordinatesInQuad) return false; for (size_t i = 0; i < coordinatesInQuad; ++i) { if (!quadArray.get(i)->asDouble(*(coordinates + i))) return false; } quad->setP1(FloatPoint(coordinates[0], coordinates[1])); quad->setP2(FloatPoint(coordinates[2], coordinates[3])); quad->setP3(FloatPoint(coordinates[4], coordinates[5])); quad->setP4(FloatPoint(coordinates[6], coordinates[7])); return true; } class RevalidateStyleAttributeTask { WTF_MAKE_FAST_ALLOCATED; public: RevalidateStyleAttributeTask(InspectorDOMAgent*); void scheduleFor(Element*); void reset() { m_timer.stop(); } void timerFired(); private: InspectorDOMAgent* m_domAgent; Timer m_timer; HashSet> m_elements; }; RevalidateStyleAttributeTask::RevalidateStyleAttributeTask(InspectorDOMAgent* domAgent) : m_domAgent(domAgent) , m_timer(*this, &RevalidateStyleAttributeTask::timerFired) { } void RevalidateStyleAttributeTask::scheduleFor(Element* element) { m_elements.add(element); if (!m_timer.isActive()) m_timer.startOneShot(0); } void RevalidateStyleAttributeTask::timerFired() { // The timer is stopped on m_domAgent destruction, so this method will never be called after m_domAgent has been destroyed. Vector elements; for (auto& element : m_elements) elements.append(element.get()); m_domAgent->styleAttributeInvalidated(elements); m_elements.clear(); } String InspectorDOMAgent::toErrorString(const ExceptionCode& ec) { if (ec) { ExceptionCodeDescription description(ec); return description.name; } return ""; } InspectorDOMAgent::InspectorDOMAgent(WebAgentContext& context, InspectorPageAgent* pageAgent, InspectorOverlay* overlay) : InspectorAgentBase(ASCIILiteral("DOM"), context) , m_injectedScriptManager(context.injectedScriptManager) , m_frontendDispatcher(std::make_unique(context.frontendRouter)) , m_backendDispatcher(Inspector::DOMBackendDispatcher::create(context.backendDispatcher, this)) , m_pageAgent(pageAgent) , m_overlay(overlay) { } InspectorDOMAgent::~InspectorDOMAgent() { reset(); ASSERT(!m_searchingForNode); } void InspectorDOMAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*) { m_history = std::make_unique(); m_domEditor = std::make_unique(m_history.get()); m_instrumentingAgents.setInspectorDOMAgent(this); m_document = m_pageAgent->mainFrame().document(); if (m_nodeToFocus) focusNode(); } void InspectorDOMAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason) { m_history.reset(); m_domEditor.reset(); ErrorString unused; setSearchingForNode(unused, false, 0); hideHighlight(unused); m_instrumentingAgents.setInspectorDOMAgent(0); m_documentRequested = false; reset(); } Vector InspectorDOMAgent::documents() { Vector result; for (Frame* frame = m_document->frame(); frame; frame = frame->tree().traverseNext()) { Document* document = frame->document(); if (!document) continue; result.append(document); } return result; } void InspectorDOMAgent::reset() { if (m_history) m_history->reset(); m_searchResults.clear(); discardBindings(); if (m_revalidateStyleAttrTask) m_revalidateStyleAttrTask->reset(); m_document = nullptr; } void InspectorDOMAgent::setDOMListener(DOMListener* listener) { m_domListener = listener; } void InspectorDOMAgent::setDocument(Document* doc) { if (doc == m_document.get()) return; reset(); m_document = doc; if (!m_documentRequested) return; // Immediately communicate 0 document or document that has finished loading. if (!doc || !doc->parsing()) m_frontendDispatcher->documentUpdated(); } void InspectorDOMAgent::releaseDanglingNodes() { m_danglingNodeToIdMaps.clear(); } int InspectorDOMAgent::bind(Node* node, NodeToIdMap* nodesMap) { int 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) { int id = nodesMap->get(node); if (!id) return; m_idToNode.remove(id); if (node->isFrameOwnerElement()) { const HTMLFrameOwnerElement* frameOwner = static_cast(node); Document* contentDocument = frameOwner->contentDocument(); if (m_domListener) m_domListener->didRemoveDocument(contentDocument); if (contentDocument) unbind(contentDocument, nodesMap); } if (is(*node)) { Element& element = downcast(*node); if (ShadowRoot* root = element.shadowRoot()) unbind(root, nodesMap); if (PseudoElement* beforeElement = element.beforePseudoElement()) unbind(beforeElement, nodesMap); if (PseudoElement* afterElement = element.afterPseudoElement()) unbind(afterElement, nodesMap); } nodesMap->remove(node); if (m_domListener) m_domListener->didRemoveDOMNode(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); } } } Node* InspectorDOMAgent::assertNode(ErrorString& errorString, int nodeId) { Node* node = nodeForId(nodeId); if (!node) { errorString = ASCIILiteral("Could not find node with given id"); return nullptr; } return node; } Document* InspectorDOMAgent::assertDocument(ErrorString& errorString, int nodeId) { Node* node = assertNode(errorString, nodeId); if (!node) return nullptr; if (!is(*node)) { errorString = ASCIILiteral("Document is not available"); return nullptr; } return downcast(node); } Element* InspectorDOMAgent::assertElement(ErrorString& errorString, int nodeId) { Node* node = assertNode(errorString, nodeId); if (!node) return nullptr; if (!is(*node)) { errorString = ASCIILiteral("Node is not an Element"); return nullptr; } return downcast(node); } Node* InspectorDOMAgent::assertEditableNode(ErrorString& errorString, int nodeId) { Node* node = assertNode(errorString, nodeId); if (!node) return nullptr; if (node->isInShadowTree()) { errorString = ASCIILiteral("Cannot edit nodes from shadow trees"); return nullptr; } if (node->isPseudoElement()) { errorString = ASCIILiteral("Cannot edit pseudo elements"); return nullptr; } return node; } Element* InspectorDOMAgent::assertEditableElement(ErrorString& errorString, int nodeId) { Element* element = assertElement(errorString, nodeId); if (!element) return nullptr; if (element->isInShadowTree()) { errorString = ASCIILiteral("Cannot edit elements from shadow trees"); return nullptr; } if (element->isPseudoElement()) { errorString = ASCIILiteral("Cannot edit pseudo elements"); return nullptr; } return element; } void InspectorDOMAgent::getDocument(ErrorString& errorString, RefPtr& root) { m_documentRequested = true; if (!m_document) { errorString = ASCIILiteral("Document is not available"); return; } // Reset backend state. RefPtr document = m_document; reset(); m_document = document; root = buildObjectForNode(m_document.get(), 2, &m_documentNodeToIdMap); } void InspectorDOMAgent::pushChildNodesToFrontend(int nodeId, int depth) { Node* node = nodeForId(nodeId); if (!node || (node->nodeType() != Node::ELEMENT_NODE && node->nodeType() != Node::DOCUMENT_NODE && node->nodeType() != Node::DOCUMENT_FRAGMENT_NODE)) return; NodeToIdMap* nodeMap = m_idToNodesMap.get(nodeId); if (m_childrenRequested.contains(nodeId)) { if (depth <= 1) return; depth--; for (node = innerFirstChild(node); node; node = innerNextSibling(node)) { int childNodeId = nodeMap->get(node); ASSERT(childNodeId); pushChildNodesToFrontend(childNodeId, depth); } return; } RefPtr> children = buildArrayForContainerChildren(node, depth, nodeMap); m_frontendDispatcher->setChildNodes(nodeId, children.release()); } void InspectorDOMAgent::discardBindings() { m_documentNodeToIdMap.clear(); m_idToNode.clear(); releaseDanglingNodes(); m_childrenRequested.clear(); m_backendIdToNode.clear(); m_nodeGroupToBackendIdMap.clear(); } int InspectorDOMAgent::pushNodeToFrontend(ErrorString& errorString, int documentNodeId, Node* nodeToPush) { Document* document = assertDocument(errorString, documentNodeId); if (!document) return 0; if (&nodeToPush->document() != document) { errorString = ASCIILiteral("Node is not part of the document with given id"); return 0; } return pushNodePathToFrontend(nodeToPush); } Node* InspectorDOMAgent::nodeForId(int id) { if (!id) return 0; HashMap::iterator it = m_idToNode.find(id); if (it != m_idToNode.end()) return it->value; return 0; } void InspectorDOMAgent::requestChildNodes(ErrorString& errorString, int nodeId, const int* depth) { int sanitizedDepth; if (!depth) sanitizedDepth = 1; else if (*depth == -1) sanitizedDepth = INT_MAX; else if (*depth > 0) sanitizedDepth = *depth; else { errorString = ASCIILiteral("Please provide a positive integer as a depth or -1 for entire subtree"); return; } pushChildNodesToFrontend(nodeId, sanitizedDepth); } void InspectorDOMAgent::querySelector(ErrorString& errorString, int nodeId, const String& selectors, int* elementId) { *elementId = 0; Node* node = assertNode(errorString, nodeId); if (!node) return; if (!is(*node)) { assertElement(errorString, nodeId); return; } ExceptionCode ec = 0; RefPtr element = downcast(*node).querySelector(selectors, ec); if (ec) { errorString = ASCIILiteral("DOM Error while querying"); return; } if (element) *elementId = pushNodePathToFrontend(element.get()); } void InspectorDOMAgent::querySelectorAll(ErrorString& errorString, int nodeId, const String& selectors, RefPtr>& result) { Node* node = assertNode(errorString, nodeId); if (!node) return; if (!is(*node)) { assertElement(errorString, nodeId); return; } ExceptionCode ec = 0; RefPtr nodes = downcast(*node).querySelectorAll(selectors, ec); if (ec) { errorString = ASCIILiteral("DOM Error while querying"); return; } result = Inspector::Protocol::Array::create(); for (unsigned i = 0; i < nodes->length(); ++i) result->addItem(pushNodePathToFrontend(nodes->item(i))); } int InspectorDOMAgent::pushNodePathToFrontend(Node* nodeToPush) { ASSERT(nodeToPush); // Invalid input if (!m_document) return 0; if (!m_documentNodeToIdMap.contains(m_document)) return 0; // Return id in case the node is known. int 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. auto newMap = std::make_unique(); danglingMap = newMap.get(); m_danglingNodeToIdMaps.append(newMap.release()); auto children = Inspector::Protocol::Array::create(); children->addItem(buildObjectForNode(node, 0, danglingMap)); m_frontendDispatcher->setChildNodes(0, WTFMove(children)); 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) { int nodeId = map->get(path.at(i)); ASSERT(nodeId); pushChildNodesToFrontend(nodeId); } return map->get(nodeToPush); } int InspectorDOMAgent::boundNodeId(Node* node) { return m_documentNodeToIdMap.get(node); } BackendNodeId InspectorDOMAgent::backendNodeIdForNode(Node* node, const String& nodeGroup) { if (!node) return 0; if (!m_nodeGroupToBackendIdMap.contains(nodeGroup)) m_nodeGroupToBackendIdMap.set(nodeGroup, NodeToBackendIdMap()); NodeToBackendIdMap& map = m_nodeGroupToBackendIdMap.find(nodeGroup)->value; BackendNodeId id = map.get(node); if (!id) { id = --m_lastBackendNodeId; map.set(node, id); m_backendIdToNode.set(id, std::make_pair(node, nodeGroup)); } return id; } void InspectorDOMAgent::releaseBackendNodeIds(ErrorString& errorString, const String& nodeGroup) { if (m_nodeGroupToBackendIdMap.contains(nodeGroup)) { NodeToBackendIdMap& map = m_nodeGroupToBackendIdMap.find(nodeGroup)->value; for (auto& backendId : map.values()) m_backendIdToNode.remove(backendId); m_nodeGroupToBackendIdMap.remove(nodeGroup); return; } errorString = ASCIILiteral("Group name not found"); } void InspectorDOMAgent::setAttributeValue(ErrorString& errorString, int elementId, const String& name, const String& value) { Element* element = assertEditableElement(errorString, elementId); if (!element) return; m_domEditor->setAttribute(element, name, value, errorString); } void InspectorDOMAgent::setAttributesAsText(ErrorString& errorString, int elementId, const String& text, const String* const name) { Element* element = assertEditableElement(errorString, elementId); if (!element) return; RefPtr parsedElement = createHTMLElement(element->document(), spanTag); ExceptionCode ec = 0; parsedElement.get()->setInnerHTML("", ec); if (ec) { errorString = InspectorDOMAgent::toErrorString(ec); return; } Node* child = parsedElement->firstChild(); if (!child) { errorString = ASCIILiteral("Could not parse value as attributes"); return; } Element* childElement = downcast(child); if (!childElement->hasAttributes() && name) { m_domEditor->removeAttribute(element, *name, errorString); return; } bool foundOriginalAttribute = false; for (const Attribute& attribute : childElement->attributesIterator()) { // Add attribute pair foundOriginalAttribute = foundOriginalAttribute || (name && attribute.name().toString() == *name); if (!m_domEditor->setAttribute(element, attribute.name().toString(), attribute.value(), errorString)) return; } if (!foundOriginalAttribute && name && !name->stripWhiteSpace().isEmpty()) m_domEditor->removeAttribute(element, *name, errorString); } void InspectorDOMAgent::removeAttribute(ErrorString& errorString, int elementId, const String& name) { Element* element = assertEditableElement(errorString, elementId); if (!element) return; m_domEditor->removeAttribute(element, name, errorString); } void InspectorDOMAgent::removeNode(ErrorString& errorString, int nodeId) { Node* node = assertEditableNode(errorString, nodeId); if (!node) return; ContainerNode* parentNode = node->parentNode(); if (!parentNode) { errorString = ASCIILiteral("Cannot remove detached node"); return; } m_domEditor->removeChild(parentNode, node, errorString); } void InspectorDOMAgent::setNodeName(ErrorString& errorString, int nodeId, const String& tagName, int* newId) { *newId = 0; RefPtr oldNode = nodeForId(nodeId); if (!is(oldNode.get())) return; ExceptionCode ec = 0; RefPtr newElement = oldNode->document().createElementForBindings(tagName, ec); if (ec) return; // Copy over the original node's attributes. newElement->cloneAttributesFromElement(downcast(*oldNode)); // Copy over the original node's children. RefPtr child; while ((child = oldNode->firstChild())) { if (!m_domEditor->insertBefore(newElement.get(), child.get(), 0, errorString)) return; } // Replace the old node with the new node RefPtr parent = oldNode->parentNode(); if (!m_domEditor->insertBefore(parent.get(), newElement.get(), oldNode->nextSibling(), errorString)) return; if (!m_domEditor->removeChild(parent.get(), oldNode.get(), errorString)) return; *newId = pushNodePathToFrontend(newElement.get()); if (m_childrenRequested.contains(nodeId)) pushChildNodesToFrontend(*newId); } void InspectorDOMAgent::getOuterHTML(ErrorString& errorString, int nodeId, WTF::String* outerHTML) { Node* node = assertNode(errorString, nodeId); if (!node) return; *outerHTML = createMarkup(*node); } void InspectorDOMAgent::setOuterHTML(ErrorString& errorString, int nodeId, const String& outerHTML) { if (!nodeId) { DOMPatchSupport domPatchSupport(m_domEditor.get(), m_document.get()); domPatchSupport.patchDocument(outerHTML); return; } Node* node = assertEditableNode(errorString, nodeId); if (!node) return; Document& document = node->document(); if (!document.isHTMLDocument() && !document.isXMLDocument()) { errorString = ASCIILiteral("Not an HTML/XML document"); return; } Node* newNode = 0; if (!m_domEditor->setOuterHTML(*node, outerHTML, &newNode, errorString)) return; if (!newNode) { // The only child node has been deleted. return; } int newId = pushNodePathToFrontend(newNode); bool childrenRequested = m_childrenRequested.contains(nodeId); if (childrenRequested) pushChildNodesToFrontend(newId); } void InspectorDOMAgent::setNodeValue(ErrorString& errorString, int nodeId, const String& value) { Node* node = assertEditableNode(errorString, nodeId); if (!node) return; if (node->nodeType() != Node::TEXT_NODE) { errorString = ASCIILiteral("Can only set value of text nodes"); return; } m_domEditor->replaceWholeText(downcast(node), value, errorString); } void InspectorDOMAgent::getEventListenersForNode(ErrorString& errorString, int nodeId, const String* objectGroup, RefPtr>& listenersArray) { listenersArray = Inspector::Protocol::Array::create(); Node* node = assertNode(errorString, nodeId); if (!node) return; Vector eventInformation; getEventListeners(node, eventInformation, true); // Get Capturing Listeners (in this order) size_t eventInformationLength = eventInformation.size(); for (auto& info : eventInformation) { for (auto& listener : info.eventListenerVector) { if (listener.useCapture) listenersArray->addItem(buildObjectForEventListener(listener, info.eventType, info.node, objectGroup)); } } // Get Bubbling Listeners (reverse order) for (size_t i = eventInformationLength; i; --i) { const EventListenerInfo& info = eventInformation[i - 1]; for (auto& listener : info.eventListenerVector) { if (!listener.useCapture) listenersArray->addItem(buildObjectForEventListener(listener, info.eventType, info.node, objectGroup)); } } } void InspectorDOMAgent::getEventListeners(Node* node, Vector& eventInformation, bool includeAncestors) { // The Node's Ancestors including self. Vector ancestors; // Push this node as the firs element. ancestors.append(node); if (includeAncestors) { for (ContainerNode* ancestor = node->parentOrShadowHostNode(); ancestor; ancestor = ancestor->parentOrShadowHostNode()) ancestors.append(ancestor); } // Nodes and their Listeners for the concerned event types (order is top to bottom) for (size_t i = ancestors.size(); i; --i) { Node* ancestor = ancestors[i - 1]; EventTargetData* d = ancestor->eventTargetData(); if (!d) continue; // Get the list of event types this Node is concerned with for (auto& type : d->eventListenerMap.eventTypes()) { const EventListenerVector& listeners = ancestor->getEventListeners(type); EventListenerVector filteredListeners; filteredListeners.reserveCapacity(listeners.size()); for (auto& listener : listeners) { if (listener.listener->type() == EventListener::JSEventListenerType) filteredListeners.append(listener); } if (!filteredListeners.isEmpty()) eventInformation.append(EventListenerInfo(ancestor, type, filteredListeners)); } } } void InspectorDOMAgent::getAccessibilityPropertiesForNode(ErrorString& errorString, int nodeId, RefPtr& axProperties) { Node* node = assertNode(errorString, nodeId); if (!node) return; axProperties = buildObjectForAccessibilityProperties(node); } void InspectorDOMAgent::performSearch(ErrorString& errorString, const String& whitespaceTrimmedQuery, const InspectorArray* nodeIds, String* searchId, int* resultCount) { // FIXME: Search works with node granularity - number of matches within node is not calculated. InspectorNodeFinder finder(whitespaceTrimmedQuery); if (nodeIds) { for (auto& nodeValue : *nodeIds) { if (!nodeValue) { errorString = ASCIILiteral("Invalid nodeIds item."); return; } int nodeId = 0; if (!nodeValue->asInteger(nodeId)) { errorString = ASCIILiteral("Invalid nodeIds item type. Expecting integer types."); return; } Node* node = assertNode(errorString, nodeId); if (!node) { // assertNode should have filled the errorString for us. ASSERT(errorString.length()); return; } finder.performSearch(node); } } else if (m_document) { // There's no need to iterate the frames tree because // the search helper will go inside the frame owner elements. finder.performSearch(m_document.get()); } *searchId = IdentifiersFactory::createIdentifier(); auto& resultsVector = m_searchResults.add(*searchId, Vector>()).iterator->value; for (auto& result : finder.results()) resultsVector.append(result); *resultCount = resultsVector.size(); } void InspectorDOMAgent::getSearchResults(ErrorString& errorString, const String& searchId, int fromIndex, int toIndex, RefPtr>& nodeIds) { SearchResults::iterator it = m_searchResults.find(searchId); if (it == m_searchResults.end()) { errorString = ASCIILiteral("No search session with given id found"); return; } int size = it->value.size(); if (fromIndex < 0 || toIndex > size || fromIndex >= toIndex) { errorString = ASCIILiteral("Invalid search result range"); return; } nodeIds = Inspector::Protocol::Array::create(); for (int i = fromIndex; i < toIndex; ++i) nodeIds->addItem(pushNodePathToFrontend((it->value)[i].get())); } void InspectorDOMAgent::discardSearchResults(ErrorString&, const String& searchId) { m_searchResults.remove(searchId); } bool InspectorDOMAgent::handleMousePress() { if (!m_searchingForNode) return false; if (Node* node = m_overlay->highlightedNode()) { inspect(node); return true; } return false; } bool InspectorDOMAgent::handleTouchEvent(Node& node) { if (!m_searchingForNode) return false; if (m_inspectModeHighlightConfig) { m_overlay->highlightNode(&node, *m_inspectModeHighlightConfig); inspect(&node); return true; } return false; } void InspectorDOMAgent::inspect(Node* inspectedNode) { ErrorString unused; RefPtr node = inspectedNode; setSearchingForNode(unused, false, nullptr); if (node->nodeType() != Node::ELEMENT_NODE && node->nodeType() != Node::DOCUMENT_NODE) node = node->parentNode(); m_nodeToFocus = node; if (!m_nodeToFocus) return; focusNode(); } void InspectorDOMAgent::focusNode() { if (!m_frontendDispatcher) return; ASSERT(m_nodeToFocus); RefPtr node = m_nodeToFocus.get(); m_nodeToFocus = nullptr; Frame* frame = node->document().frame(); if (!frame) return; JSC::ExecState* scriptState = mainWorldExecState(frame); InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(scriptState); if (injectedScript.hasNoValue()) return; injectedScript.inspectObject(InspectorDOMAgent::nodeAsScriptValue(scriptState, node.get())); } void InspectorDOMAgent::mouseDidMoveOverElement(const HitTestResult& result, unsigned) { if (!m_searchingForNode) return; Node* node = result.innerNode(); while (node && node->nodeType() == Node::TEXT_NODE) node = node->parentNode(); if (node && m_inspectModeHighlightConfig) m_overlay->highlightNode(node, *m_inspectModeHighlightConfig); } void InspectorDOMAgent::setSearchingForNode(ErrorString& errorString, bool enabled, const InspectorObject* highlightInspectorObject) { if (m_searchingForNode == enabled) return; m_searchingForNode = enabled; if (enabled) { m_inspectModeHighlightConfig = highlightConfigFromInspectorObject(errorString, highlightInspectorObject); if (!m_inspectModeHighlightConfig) return; } else hideHighlight(errorString); m_overlay->didSetSearchingForNode(m_searchingForNode); } std::unique_ptr InspectorDOMAgent::highlightConfigFromInspectorObject(ErrorString& errorString, const InspectorObject* highlightInspectorObject) { if (!highlightInspectorObject) { errorString = ASCIILiteral("Internal error: highlight configuration parameter is missing"); return nullptr; } auto highlightConfig = std::make_unique(); bool showInfo = false; // Default: false (do not show a tooltip). highlightInspectorObject->getBoolean("showInfo", showInfo); highlightConfig->showInfo = showInfo; highlightConfig->content = parseConfigColor("contentColor", highlightInspectorObject); highlightConfig->contentOutline = parseConfigColor("contentOutlineColor", highlightInspectorObject); highlightConfig->padding = parseConfigColor("paddingColor", highlightInspectorObject); highlightConfig->border = parseConfigColor("borderColor", highlightInspectorObject); highlightConfig->margin = parseConfigColor("marginColor", highlightInspectorObject); return highlightConfig; } void InspectorDOMAgent::setInspectModeEnabled(ErrorString& errorString, bool enabled, const InspectorObject* highlightConfig) { setSearchingForNode(errorString, enabled, highlightConfig ? highlightConfig : nullptr); } void InspectorDOMAgent::highlightRect(ErrorString&, int x, int y, int width, int height, const InspectorObject* color, const InspectorObject* outlineColor, const bool* usePageCoordinates) { auto quad = std::make_unique(FloatRect(x, y, width, height)); innerHighlightQuad(WTFMove(quad), color, outlineColor, usePageCoordinates); } void InspectorDOMAgent::highlightQuad(ErrorString& errorString, const InspectorArray& quadArray, const InspectorObject* color, const InspectorObject* outlineColor, const bool* usePageCoordinates) { auto quad = std::make_unique(); if (!parseQuad(quadArray, quad.get())) { errorString = ASCIILiteral("Invalid Quad format"); return; } innerHighlightQuad(WTFMove(quad), color, outlineColor, usePageCoordinates); } void InspectorDOMAgent::innerHighlightQuad(std::unique_ptr quad, const InspectorObject* color, const InspectorObject* outlineColor, const bool* usePageCoordinates) { auto highlightConfig = std::make_unique(); highlightConfig->content = parseColor(color); highlightConfig->contentOutline = parseColor(outlineColor); highlightConfig->usePageCoordinates = usePageCoordinates ? *usePageCoordinates : false; m_overlay->highlightQuad(WTFMove(quad), *highlightConfig); } void InspectorDOMAgent::highlightSelector(ErrorString& errorString, const InspectorObject& highlightInspectorObject, const String& selectorString, const String* frameId) { RefPtr document; if (frameId) { Frame* frame = m_pageAgent->frameForId(*frameId); if (!frame) { errorString = ASCIILiteral("No frame for given id found"); return; } document = frame->document(); } else document = m_document; if (!document) { errorString = ASCIILiteral("Document could not be found"); return; } ExceptionCode ec = 0; RefPtr nodes = document->querySelectorAll(selectorString, ec); // FIXME: Web Inspector: DOM.highlightSelector should work for "a:visited" if (ec) { errorString = ASCIILiteral("DOM Error while querying"); return; } std::unique_ptr highlightConfig = highlightConfigFromInspectorObject(errorString, &highlightInspectorObject); if (!highlightConfig) return; m_overlay->highlightNodeList(WTFMove(nodes), *highlightConfig); } void InspectorDOMAgent::highlightNode(ErrorString& errorString, const InspectorObject& highlightInspectorObject, const int* nodeId, const String* objectId) { Node* node = nullptr; if (nodeId) node = assertNode(errorString, *nodeId); else if (objectId) { node = nodeForObjectId(*objectId); if (!node) errorString = ASCIILiteral("Node for given objectId not found"); } else errorString = ASCIILiteral("Either nodeId or objectId must be specified"); if (!node) return; std::unique_ptr highlightConfig = highlightConfigFromInspectorObject(errorString, &highlightInspectorObject); if (!highlightConfig) return; m_overlay->highlightNode(node, *highlightConfig); } void InspectorDOMAgent::highlightFrame(ErrorString& errorString, const String& frameId, const InspectorObject* color, const InspectorObject* outlineColor) { Frame* frame = m_pageAgent->assertFrame(errorString, frameId); if (!frame) return; if (frame->ownerElement()) { auto highlightConfig = std::make_unique(); highlightConfig->showInfo = true; // Always show tooltips for frames. highlightConfig->content = parseColor(color); highlightConfig->contentOutline = parseColor(outlineColor); m_overlay->highlightNode(frame->ownerElement(), *highlightConfig); } } void InspectorDOMAgent::hideHighlight(ErrorString&) { m_overlay->hideHighlight(); } void InspectorDOMAgent::moveTo(ErrorString& errorString, int nodeId, int targetElementId, const int* const anchorNodeId, int* newNodeId) { Node* node = assertEditableNode(errorString, nodeId); if (!node) return; Element* targetElement = assertEditableElement(errorString, targetElementId); if (!targetElement) return; Node* anchorNode = 0; if (anchorNodeId && *anchorNodeId) { anchorNode = assertEditableNode(errorString, *anchorNodeId); if (!anchorNode) return; if (anchorNode->parentNode() != targetElement) { errorString = ASCIILiteral("Anchor node must be child of the target element"); return; } } if (!m_domEditor->insertBefore(targetElement, node, anchorNode, errorString)) return; *newNodeId = pushNodePathToFrontend(node); } void InspectorDOMAgent::undo(ErrorString& errorString) { ExceptionCode ec = 0; m_history->undo(ec); errorString = InspectorDOMAgent::toErrorString(ec); } void InspectorDOMAgent::redo(ErrorString& errorString) { ExceptionCode ec = 0; m_history->redo(ec); errorString = InspectorDOMAgent::toErrorString(ec); } void InspectorDOMAgent::markUndoableState(ErrorString&) { m_history->markUndoableState(); } void InspectorDOMAgent::focus(ErrorString& errorString, int nodeId) { Element* element = assertElement(errorString, nodeId); if (!element) return; if (!element->isFocusable()) { errorString = ASCIILiteral("Element is not focusable"); return; } element->focus(); } void InspectorDOMAgent::resolveNode(ErrorString& errorString, int nodeId, const String* const objectGroup, RefPtr& result) { String objectGroupName = objectGroup ? *objectGroup : ""; Node* node = nodeForId(nodeId); if (!node) { errorString = ASCIILiteral("No node with given id found"); return; } RefPtr object = resolveNode(node, objectGroupName); if (!object) { errorString = ASCIILiteral("Node with given id does not belong to the document"); return; } result = object; } void InspectorDOMAgent::getAttributes(ErrorString& errorString, int nodeId, RefPtr>& result) { Element* element = assertElement(errorString, nodeId); if (!element) return; result = buildArrayForElementAttributes(element); } void InspectorDOMAgent::requestNode(ErrorString&, const String& objectId, int* nodeId) { Node* node = nodeForObjectId(objectId); if (node) *nodeId = pushNodePathToFrontend(node); else *nodeId = 0; } // static String InspectorDOMAgent::documentURLString(Document* document) { if (!document || document->url().isNull()) return ""; return document->url().string(); } static String documentBaseURLString(Document* document) { return document->completeURL("").string(); } static bool pseudoElementType(PseudoId pseudoId, Inspector::Protocol::DOM::PseudoType* type) { switch (pseudoId) { case BEFORE: *type = Inspector::Protocol::DOM::PseudoType::Before; return true; case AFTER: *type = Inspector::Protocol::DOM::PseudoType::After; return true; default: return false; } } Ref InspectorDOMAgent::buildObjectForNode(Node* node, int depth, NodeToIdMap* nodesMap) { int id = bind(node, nodesMap); String nodeName; String localName; String nodeValue; switch (node->nodeType()) { case Node::PROCESSING_INSTRUCTION_NODE: nodeName = node->nodeName(); localName = node->localName(); FALLTHROUGH; case Node::TEXT_NODE: case Node::COMMENT_NODE: case Node::CDATA_SECTION_NODE: nodeValue = node->nodeValue(); if (nodeValue.length() > maxTextSize) { nodeValue = nodeValue.left(maxTextSize); nodeValue.append(ellipsisUChar); } break; case Node::ATTRIBUTE_NODE: localName = node->localName(); break; case Node::DOCUMENT_FRAGMENT_NODE: case Node::DOCUMENT_NODE: case Node::ELEMENT_NODE: default: nodeName = node->nodeName(); localName = node->localName(); break; } auto value = Inspector::Protocol::DOM::Node::create() .setNodeId(id) .setNodeType(static_cast(node->nodeType())) .setNodeName(nodeName) .setLocalName(localName) .setNodeValue(nodeValue) .release(); if (node->isContainerNode()) { int nodeCount = innerChildNodeCount(node); value->setChildNodeCount(nodeCount); Ref> children = buildArrayForContainerChildren(node, depth, nodesMap); if (children->length() > 0) value->setChildren(WTFMove(children)); } if (is(*node)) { Element& element = downcast(*node); value->setAttributes(buildArrayForElementAttributes(&element)); if (is(element)) { HTMLFrameOwnerElement& frameOwner = downcast(element); Frame* frame = frameOwner.contentFrame(); if (frame) value->setFrameId(m_pageAgent->frameId(frame)); Document* document = frameOwner.contentDocument(); if (document) value->setContentDocument(buildObjectForNode(document, 0, nodesMap)); } if (ShadowRoot* root = element.shadowRoot()) { auto shadowRoots = Inspector::Protocol::Array::create(); shadowRoots->addItem(buildObjectForNode(root, 0, nodesMap)); value->setShadowRoots(WTFMove(shadowRoots)); } #if ENABLE(TEMPLATE_ELEMENT) if (is(element)) value->setTemplateContent(buildObjectForNode(downcast(element).content(), 0, nodesMap)); #endif if (element.pseudoId()) { Inspector::Protocol::DOM::PseudoType pseudoType; if (pseudoElementType(element.pseudoId(), &pseudoType)) value->setPseudoType(pseudoType); } else { if (auto pseudoElements = buildArrayForPseudoElements(element, nodesMap)) value->setPseudoElements(WTFMove(pseudoElements)); } } else if (is(*node)) { Document& document = downcast(*node); value->setFrameId(m_pageAgent->frameId(document.frame())); value->setDocumentURL(documentURLString(&document)); value->setBaseURL(documentBaseURLString(&document)); value->setXmlVersion(document.xmlVersion()); } else if (is(*node)) { DocumentType& docType = downcast(*node); value->setPublicId(docType.publicId()); value->setSystemId(docType.systemId()); value->setInternalSubset(docType.internalSubset()); } else if (is(*node)) { Attr& attribute = downcast(*node); value->setName(attribute.name()); value->setValue(attribute.value()); } // Need to enable AX to get the computed role. if (!WebCore::AXObjectCache::accessibilityEnabled()) WebCore::AXObjectCache::enableAccessibility(); if (AXObjectCache* axObjectCache = node->document().axObjectCache()) { if (AccessibilityObject* axObject = axObjectCache->getOrCreate(node)) value->setRole(axObject->computedRoleString()); } return value; } Ref> InspectorDOMAgent::buildArrayForElementAttributes(Element* element) { auto attributesValue = Inspector::Protocol::Array::create(); // Go through all attributes and serialize them. if (!element->hasAttributes()) return attributesValue; for (const Attribute& attribute : element->attributesIterator()) { // Add attribute pair attributesValue->addItem(attribute.name().toString()); attributesValue->addItem(attribute.value()); } return attributesValue; } Ref> InspectorDOMAgent::buildArrayForContainerChildren(Node* container, int depth, NodeToIdMap* nodesMap) { auto children = Inspector::Protocol::Array::create(); if (depth == 0) { // Special-case the only text child - pretend that container's children have been requested. Node* firstChild = container->firstChild(); if (firstChild && firstChild->nodeType() == Node::TEXT_NODE && !firstChild->nextSibling()) { children->addItem(buildObjectForNode(firstChild, 0, nodesMap)); m_childrenRequested.add(bind(container, nodesMap)); } return children; } Node* child = innerFirstChild(container); depth--; m_childrenRequested.add(bind(container, nodesMap)); while (child) { children->addItem(buildObjectForNode(child, depth, nodesMap)); child = innerNextSibling(child); } return children; } RefPtr> InspectorDOMAgent::buildArrayForPseudoElements(const Element& element, NodeToIdMap* nodesMap) { PseudoElement* beforeElement = element.beforePseudoElement(); PseudoElement* afterElement = element.afterPseudoElement(); if (!beforeElement && !afterElement) return nullptr; auto pseudoElements = Inspector::Protocol::Array::create(); if (beforeElement) pseudoElements->addItem(buildObjectForNode(beforeElement, 0, nodesMap)); if (afterElement) pseudoElements->addItem(buildObjectForNode(afterElement, 0, nodesMap)); return WTFMove(pseudoElements); } Ref InspectorDOMAgent::buildObjectForEventListener(const RegisteredEventListener& registeredEventListener, const AtomicString& eventType, Node* node, const String* objectGroupId) { RefPtr eventListener = registeredEventListener.listener; JSC::ExecState* state = nullptr; JSC::JSObject* handler = nullptr; String body; int lineNumber = 0; String scriptID; String sourceName; if (auto scriptListener = JSEventListener::cast(eventListener.get())) { JSC::JSLockHolder lock(scriptListener->isolatedWorld().vm()); state = execStateFromNode(scriptListener->isolatedWorld(), &node->document()); handler = scriptListener->jsFunction(&node->document()); if (handler && state) { body = handler->toString(state)->value(state); if (auto function = JSC::jsDynamicCast(handler)) { if (!function->isHostOrBuiltinFunction()) { if (auto executable = function->jsExecutable()) { lineNumber = executable->firstLine() - 1; scriptID = executable->sourceID() == JSC::SourceProvider::nullID ? emptyString() : String::number(executable->sourceID()); sourceName = executable->sourceURL(); } } } } } auto value = Inspector::Protocol::DOM::EventListener::create() .setType(eventType) .setUseCapture(registeredEventListener.useCapture) .setIsAttribute(eventListener->isAttribute()) .setNodeId(pushNodePathToFrontend(node)) .setHandlerBody(body) .release(); if (objectGroupId && handler && state) { InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(state); if (!injectedScript.hasNoValue()) value->setHandler(injectedScript.wrapObject(Deprecated::ScriptValue(state->vm(), handler), *objectGroupId)); } if (!scriptID.isNull()) { auto location = Inspector::Protocol::Debugger::Location::create() .setScriptId(scriptID) .setLineNumber(lineNumber) .release(); value->setLocation(WTFMove(location)); if (!sourceName.isEmpty()) value->setSourceName(sourceName); } return value; } void InspectorDOMAgent::processAccessibilityChildren(RefPtr&& axObject, RefPtr>&& childNodeIds) { const auto& children = axObject->children(); if (!children.size()) return; if (!childNodeIds) childNodeIds = Inspector::Protocol::Array::create(); for (const auto& childObject : children) { if (Node* childNode = childObject->node()) childNodeIds->addItem(pushNodePathToFrontend(childNode)); else processAccessibilityChildren(childObject.copyRef(), childNodeIds.copyRef()); } } RefPtr InspectorDOMAgent::buildObjectForAccessibilityProperties(Node* node) { ASSERT(node); if (!node) return nullptr; if (!WebCore::AXObjectCache::accessibilityEnabled()) WebCore::AXObjectCache::enableAccessibility(); Node* activeDescendantNode = nullptr; bool busy = false; auto checked = Inspector::Protocol::DOM::AccessibilityProperties::Checked::False; RefPtr> childNodeIds; RefPtr> controlledNodeIds; auto currentState = Inspector::Protocol::DOM::AccessibilityProperties::Current::False; bool exists = false; bool expanded = false; bool disabled = false; RefPtr> flowedNodeIds; bool focused = false; bool ignored = true; bool ignoredByDefault = false; auto invalid = Inspector::Protocol::DOM::AccessibilityProperties::Invalid::False; bool hidden = false; String label; bool liveRegionAtomic = false; RefPtr> liveRegionRelevant; auto liveRegionStatus = Inspector::Protocol::DOM::AccessibilityProperties::LiveRegionStatus::Off; Node* mouseEventNode = nullptr; RefPtr> ownedNodeIds; Node* parentNode = nullptr; bool pressed = false; bool readonly = false; bool required = false; String role; bool selected = false; RefPtr> selectedChildNodeIds; bool supportsChecked = false; bool supportsExpanded = false; bool supportsLiveRegion = false; bool supportsPressed = false; bool supportsRequired = false; bool supportsFocused = false; if (AXObjectCache* axObjectCache = node->document().axObjectCache()) { if (AccessibilityObject* axObject = axObjectCache->getOrCreate(node)) { if (AccessibilityObject* activeDescendant = axObject->activeDescendant()) activeDescendantNode = activeDescendant->node(); // An AX object is "busy" if it or any ancestor has aria-busy="true" set. AccessibilityObject* current = axObject; while (!busy && current) { busy = current->ariaLiveRegionBusy(); current = current->parentObject(); } supportsChecked = axObject->supportsChecked(); if (supportsChecked) { int checkValue = axObject->checkboxOrRadioValue(); // Element using aria-checked. if (checkValue == 1) checked = Inspector::Protocol::DOM::AccessibilityProperties::Checked::True; else if (checkValue == 2) checked = Inspector::Protocol::DOM::AccessibilityProperties::Checked::Mixed; else if (axObject->isChecked()) // Native checkbox. checked = Inspector::Protocol::DOM::AccessibilityProperties::Checked::True; } processAccessibilityChildren(axObject, WTFMove(childNodeIds)); if (axObject->supportsARIAControls()) { Vector controlledElements; axObject->elementsFromAttribute(controlledElements, aria_controlsAttr); if (controlledElements.size()) { controlledNodeIds = Inspector::Protocol::Array::create(); for (Element* controlledElement : controlledElements) controlledNodeIds->addItem(pushNodePathToFrontend(controlledElement)); } } switch (axObject->ariaCurrentState()) { case ARIACurrentFalse: currentState = Inspector::Protocol::DOM::AccessibilityProperties::Current::False; break; case ARIACurrentPage: currentState = Inspector::Protocol::DOM::AccessibilityProperties::Current::Page; break; case ARIACurrentStep: currentState = Inspector::Protocol::DOM::AccessibilityProperties::Current::Step; break; case ARIACurrentLocation: currentState = Inspector::Protocol::DOM::AccessibilityProperties::Current::Location; break; case ARIACurrentDate: currentState = Inspector::Protocol::DOM::AccessibilityProperties::Current::Date; break; case ARIACurrentTime: currentState = Inspector::Protocol::DOM::AccessibilityProperties::Current::Time; break; default: case ARIACurrentTrue: currentState = Inspector::Protocol::DOM::AccessibilityProperties::Current::True; break; } disabled = !axObject->isEnabled(); exists = true; supportsExpanded = axObject->supportsExpanded(); if (supportsExpanded) expanded = axObject->isExpanded(); if (axObject->supportsARIAFlowTo()) { Vector flowedElements; axObject->elementsFromAttribute(flowedElements, aria_flowtoAttr); if (flowedElements.size()) { flowedNodeIds = Inspector::Protocol::Array::create(); for (Element* flowedElement : flowedElements) flowedNodeIds->addItem(pushNodePathToFrontend(flowedElement)); } } if (is(*node)) { supportsFocused = downcast(*node).isFocusable(); if (supportsFocused) focused = axObject->isFocused(); } ignored = axObject->accessibilityIsIgnored(); ignoredByDefault = axObject->accessibilityIsIgnoredByDefault(); String invalidValue = axObject->invalidStatus(); if (invalidValue == "false") invalid = Inspector::Protocol::DOM::AccessibilityProperties::Invalid::False; else if (invalidValue == "grammar") invalid = Inspector::Protocol::DOM::AccessibilityProperties::Invalid::Grammar; else if (invalidValue == "spelling") invalid = Inspector::Protocol::DOM::AccessibilityProperties::Invalid::Spelling; else // Future versions of ARIA may allow additional truthy values. Ex. format, order, or size. invalid = Inspector::Protocol::DOM::AccessibilityProperties::Invalid::True; if (axObject->isARIAHidden() || axObject->isDOMHidden()) hidden = true; label = axObject->computedLabel(); if (axObject->supportsARIALiveRegion()) { supportsLiveRegion = true; liveRegionAtomic = axObject->ariaLiveRegionAtomic(); String ariaRelevantAttrValue = axObject->ariaLiveRegionRelevant(); if (!ariaRelevantAttrValue.isEmpty()) { // FIXME: Pass enum values rather than strings once unblocked. http://webkit.org/b/133711 String ariaRelevantAdditions = Inspector::Protocol::getEnumConstantValue(Inspector::Protocol::DOM::LiveRegionRelevant::Additions); String ariaRelevantRemovals = Inspector::Protocol::getEnumConstantValue(Inspector::Protocol::DOM::LiveRegionRelevant::Removals); String ariaRelevantText = Inspector::Protocol::getEnumConstantValue(Inspector::Protocol::DOM::LiveRegionRelevant::Text); liveRegionRelevant = Inspector::Protocol::Array::create(); const SpaceSplitString& values = SpaceSplitString(ariaRelevantAttrValue, true); // @aria-relevant="all" is exposed as ["additions","removals","text"], in order. // This order is controlled in WebCore and expected in WebInspectorUI. if (values.contains("all")) { liveRegionRelevant->addItem(ariaRelevantAdditions); liveRegionRelevant->addItem(ariaRelevantRemovals); liveRegionRelevant->addItem(ariaRelevantText); } else { if (values.contains(ariaRelevantAdditions)) liveRegionRelevant->addItem(ariaRelevantAdditions); if (values.contains(ariaRelevantRemovals)) liveRegionRelevant->addItem(ariaRelevantRemovals); if (values.contains(ariaRelevantText)) liveRegionRelevant->addItem(ariaRelevantText); } } String ariaLive = axObject->ariaLiveRegionStatus(); if (ariaLive == "assertive") liveRegionStatus = Inspector::Protocol::DOM::AccessibilityProperties::LiveRegionStatus::Assertive; else if (ariaLive == "polite") liveRegionStatus = Inspector::Protocol::DOM::AccessibilityProperties::LiveRegionStatus::Polite; } if (is(*axObject)) mouseEventNode = downcast(*axObject).mouseButtonListener(MouseButtonListenerResultFilter::IncludeBodyElement); if (axObject->supportsARIAOwns()) { Vector ownedElements; axObject->elementsFromAttribute(ownedElements, aria_ownsAttr); if (ownedElements.size()) { ownedNodeIds = Inspector::Protocol::Array::create(); for (Element* ownedElement : ownedElements) ownedNodeIds->addItem(pushNodePathToFrontend(ownedElement)); } } if (AccessibilityObject* parentObject = axObject->parentObjectUnignored()) parentNode = parentObject->node(); supportsPressed = axObject->ariaPressedIsPresent(); if (supportsPressed) pressed = axObject->isPressed(); if (axObject->isTextControl()) readonly = !axObject->canSetValueAttribute(); supportsRequired = axObject->supportsRequiredAttribute(); if (supportsRequired) required = axObject->isRequired(); role = axObject->computedRoleString(); selected = axObject->isSelected(); AccessibilityObject::AccessibilityChildrenVector selectedChildren; axObject->selectedChildren(selectedChildren); if (selectedChildren.size()) { selectedChildNodeIds = Inspector::Protocol::Array::create(); for (auto& selectedChildObject : selectedChildren) { if (Node* selectedChildNode = selectedChildObject->node()) selectedChildNodeIds->addItem(pushNodePathToFrontend(selectedChildNode)); } } } } Ref value = Inspector::Protocol::DOM::AccessibilityProperties::create() .setExists(exists) .setLabel(label) .setRole(role) .setNodeId(pushNodePathToFrontend(node)) .release(); if (exists) { if (activeDescendantNode) value->setActiveDescendantNodeId(pushNodePathToFrontend(activeDescendantNode)); if (busy) value->setBusy(busy); if (supportsChecked) value->setChecked(checked); if (childNodeIds) value->setChildNodeIds(childNodeIds); if (controlledNodeIds) value->setControlledNodeIds(controlledNodeIds); if (currentState != Inspector::Protocol::DOM::AccessibilityProperties::Current::False) value->setCurrent(currentState); if (disabled) value->setDisabled(disabled); if (supportsExpanded) value->setExpanded(expanded); if (flowedNodeIds) value->setFlowedNodeIds(flowedNodeIds); if (supportsFocused) value->setFocused(focused); if (ignored) value->setIgnored(ignored); if (ignoredByDefault) value->setIgnoredByDefault(ignoredByDefault); if (invalid != Inspector::Protocol::DOM::AccessibilityProperties::Invalid::False) value->setInvalid(invalid); if (hidden) value->setHidden(hidden); if (supportsLiveRegion) { value->setLiveRegionAtomic(liveRegionAtomic); if (liveRegionRelevant->length()) value->setLiveRegionRelevant(liveRegionRelevant); value->setLiveRegionStatus(liveRegionStatus); } if (mouseEventNode) value->setMouseEventNodeId(pushNodePathToFrontend(mouseEventNode)); if (ownedNodeIds) value->setOwnedNodeIds(ownedNodeIds); if (parentNode) value->setParentNodeId(pushNodePathToFrontend(parentNode)); if (supportsPressed) value->setPressed(pressed); if (readonly) value->setReadonly(readonly); if (supportsRequired) value->setRequired(required); if (selected) value->setSelected(selected); if (selectedChildNodeIds) value->setSelectedChildNodeIds(selectedChildNodeIds); } return WTFMove(value); } Node* InspectorDOMAgent::innerFirstChild(Node* node) { 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) { ASSERT(node); if (is(*node)) return downcast(*node).ownerElement(); return node->parentNode(); } 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; } void InspectorDOMAgent::mainFrameDOMContentLoaded() { // Re-push document once it is loaded. discardBindings(); if (m_documentRequested) m_frontendDispatcher->documentUpdated(); } void InspectorDOMAgent::didCommitLoad(Document* document) { Element* frameOwner = document->ownerElement(); if (!frameOwner) return; int frameOwnerId = m_documentNodeToIdMap.get(frameOwner); if (!frameOwnerId) return; // Re-add frame owner element together with its new children. int parentId = m_documentNodeToIdMap.get(innerParentNode(frameOwner)); m_frontendDispatcher->childNodeRemoved(parentId, frameOwnerId); unbind(frameOwner, &m_documentNodeToIdMap); Ref value = buildObjectForNode(frameOwner, 0, &m_documentNodeToIdMap); Node* previousSibling = innerPreviousSibling(frameOwner); int prevId = previousSibling ? m_documentNodeToIdMap.get(previousSibling) : 0; m_frontendDispatcher->childNodeInserted(parentId, prevId, WTFMove(value)); } void InspectorDOMAgent::didInsertDOMNode(Node& node) { if (isWhitespace(&node)) return; // We could be attaching existing subtree. Forget the bindings. unbind(&node, &m_documentNodeToIdMap); ContainerNode* parent = node.parentNode(); if (!parent) return; int 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_frontendDispatcher->childNodeCountUpdated(parentId, innerChildNodeCount(parent)); } else { // Children have been requested -> return value of a new child. Node* prevSibling = innerPreviousSibling(&node); int prevId = prevSibling ? m_documentNodeToIdMap.get(prevSibling) : 0; Ref value = buildObjectForNode(&node, 0, &m_documentNodeToIdMap); m_frontendDispatcher->childNodeInserted(parentId, prevId, WTFMove(value)); } } void InspectorDOMAgent::didRemoveDOMNode(Node& node) { if (isWhitespace(&node)) return; ContainerNode* parent = node.parentNode(); // If parent is not mapped yet -> ignore the event. if (!m_documentNodeToIdMap.contains(parent)) return; int parentId = m_documentNodeToIdMap.get(parent); if (!m_childrenRequested.contains(parentId)) { // No children are mapped yet -> only notify on changes of hasChildren. if (innerChildNodeCount(parent) == 1) m_frontendDispatcher->childNodeCountUpdated(parentId, 0); } else m_frontendDispatcher->childNodeRemoved(parentId, m_documentNodeToIdMap.get(&node)); unbind(&node, &m_documentNodeToIdMap); } void InspectorDOMAgent::willModifyDOMAttr(Element&, const AtomicString& oldValue, const AtomicString& newValue) { m_suppressAttributeModifiedEvent = (oldValue == newValue); } void InspectorDOMAgent::didModifyDOMAttr(Element& element, const AtomicString& name, const AtomicString& value) { bool shouldSuppressEvent = m_suppressAttributeModifiedEvent; m_suppressAttributeModifiedEvent = false; if (shouldSuppressEvent) return; int id = boundNodeId(&element); // If node is not mapped yet -> ignore the event. if (!id) return; if (m_domListener) m_domListener->didModifyDOMAttr(&element); m_frontendDispatcher->attributeModified(id, name, value); } void InspectorDOMAgent::didRemoveDOMAttr(Element& element, const AtomicString& name) { int id = boundNodeId(&element); // If node is not mapped yet -> ignore the event. if (!id) return; if (m_domListener) m_domListener->didModifyDOMAttr(&element); m_frontendDispatcher->attributeRemoved(id, name); } void InspectorDOMAgent::styleAttributeInvalidated(const Vector& elements) { auto nodeIds = Inspector::Protocol::Array::create(); for (auto& element : elements) { int id = boundNodeId(element); // If node is not mapped yet -> ignore the event. if (!id) continue; if (m_domListener) m_domListener->didModifyDOMAttr(element); nodeIds->addItem(id); } m_frontendDispatcher->inlineStyleInvalidated(WTFMove(nodeIds)); } void InspectorDOMAgent::characterDataModified(CharacterData& characterData) { int id = m_documentNodeToIdMap.get(&characterData); if (!id) { // Push text node if it is being created. didInsertDOMNode(characterData); return; } m_frontendDispatcher->characterDataModified(id, characterData.data()); } void InspectorDOMAgent::didInvalidateStyleAttr(Node& node) { int id = m_documentNodeToIdMap.get(&node); // If node is not mapped yet -> ignore the event. if (!id) return; if (!m_revalidateStyleAttrTask) m_revalidateStyleAttrTask = std::make_unique(this); m_revalidateStyleAttrTask->scheduleFor(downcast(&node)); } void InspectorDOMAgent::didPushShadowRoot(Element& host, ShadowRoot& root) { int hostId = m_documentNodeToIdMap.get(&host); if (hostId) m_frontendDispatcher->shadowRootPushed(hostId, buildObjectForNode(&root, 0, &m_documentNodeToIdMap)); } void InspectorDOMAgent::willPopShadowRoot(Element& host, ShadowRoot& root) { int hostId = m_documentNodeToIdMap.get(&host); int rootId = m_documentNodeToIdMap.get(&root); if (hostId && rootId) m_frontendDispatcher->shadowRootPopped(hostId, rootId); } void InspectorDOMAgent::frameDocumentUpdated(Frame* frame) { Document* document = frame->document(); if (!document) return; Page* page = frame->page(); ASSERT(page); if (frame != &page->mainFrame()) return; // Only update the main frame document, nested frame document updates are not required // (will be handled by didCommitLoad()). setDocument(document); } void InspectorDOMAgent::pseudoElementCreated(PseudoElement& pseudoElement) { Element* parent = pseudoElement.hostElement(); if (!parent) return; int parentId = m_documentNodeToIdMap.get(parent); if (!parentId) return; pushChildNodesToFrontend(parentId, 1); m_frontendDispatcher->pseudoElementAdded(parentId, buildObjectForNode(&pseudoElement, 0, &m_documentNodeToIdMap)); } void InspectorDOMAgent::pseudoElementDestroyed(PseudoElement& pseudoElement) { int pseudoElementId = m_documentNodeToIdMap.get(&pseudoElement); if (!pseudoElementId) return; // If a PseudoElement is bound, its parent element must have been bound. Element* parent = pseudoElement.hostElement(); ASSERT(parent); int parentId = m_documentNodeToIdMap.get(parent); ASSERT(parentId); unbind(&pseudoElement, &m_documentNodeToIdMap); m_frontendDispatcher->pseudoElementRemoved(parentId, pseudoElementId); } Node* InspectorDOMAgent::nodeForPath(const String& path) { // The path is of form "1,HTML,2,BODY,1,DIV" if (!m_document) return 0; Node* node = m_document.get(); 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; } Node* InspectorDOMAgent::nodeForObjectId(const String& objectId) { InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(objectId); if (injectedScript.hasNoValue()) return nullptr; Deprecated::ScriptValue value = injectedScript.findObjectById(objectId); return InspectorDOMAgent::scriptValueAsNode(value); } void InspectorDOMAgent::pushNodeByPathToFrontend(ErrorString& errorString, const String& path, int* nodeId) { if (Node* node = nodeForPath(path)) *nodeId = pushNodePathToFrontend(node); else errorString = ASCIILiteral("No node with given path found"); } void InspectorDOMAgent::pushNodeByBackendIdToFrontend(ErrorString& errorString, BackendNodeId backendNodeId, int* nodeId) { if (!m_backendIdToNode.contains(backendNodeId)) { errorString = ASCIILiteral("No node with given backend id found"); return; } Node* node = m_backendIdToNode.get(backendNodeId).first; String nodeGroup = m_backendIdToNode.get(backendNodeId).second; *nodeId = pushNodePathToFrontend(node); if (nodeGroup == "") { m_backendIdToNode.remove(backendNodeId); m_nodeGroupToBackendIdMap.find(nodeGroup)->value.remove(node); } } RefPtr InspectorDOMAgent::resolveNode(Node* node, const String& objectGroup) { Frame* frame = node->document().frame(); if (!frame) return 0; JSC::ExecState* scriptState = mainWorldExecState(frame); InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(scriptState); if (injectedScript.hasNoValue()) return 0; return injectedScript.wrapObject(InspectorDOMAgent::nodeAsScriptValue(scriptState, node), objectGroup); } Node* InspectorDOMAgent::scriptValueAsNode(Deprecated::ScriptValue value) { if (!value.isObject() || value.isNull()) return nullptr; return JSNode::toWrapped(value.jsValue()); } Deprecated::ScriptValue InspectorDOMAgent::nodeAsScriptValue(JSC::ExecState* state, Node* node) { if (!shouldAllowAccessToNode(state, node)) return Deprecated::ScriptValue(state->vm(), JSC::jsNull()); JSC::JSLockHolder lock(state); return Deprecated::ScriptValue(state->vm(), toJS(state, deprecatedGlobalObjectForPrototype(state), node)); } } // namespace WebCore