| /* |
| * Copyright (C) 2009, 2015-2016 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 "CryptoDigest.h" |
| #include "DOMEditor.h" |
| #include "DOMPatchSupport.h" |
| #include "DOMWindow.h" |
| #include "Document.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 "HTMLScriptElement.h" |
| #include "HTMLStyleElement.h" |
| #include "HTMLTemplateElement.h" |
| #include "HitTestResult.h" |
| #include "InspectorClient.h" |
| #include "InspectorController.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 "ShadowRoot.h" |
| #include "StyleProperties.h" |
| #include "StyleResolver.h" |
| #include "StyleSheetList.h" |
| #include "Text.h" |
| #include "TextNodeTraversal.h" |
| #include "XPathResult.h" |
| #include "htmlediting.h" |
| #include "markup.h" |
| #include <inspector/IdentifiersFactory.h> |
| #include <inspector/InjectedScript.h> |
| #include <inspector/InjectedScriptManager.h> |
| #include <runtime/JSCInlines.h> |
| #include <wtf/text/Base64.h> |
| #include <wtf/text/CString.h> |
| #include <wtf/text/WTFString.h> |
| |
| 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<int>(a * 255)); |
| } |
| |
| static Color parseConfigColor(const String& fieldName, const InspectorObject* configObject) |
| { |
| RefPtr<InspectorObject> 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<RefPtr<Element>> 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<Element*> elements; |
| for (auto& element : m_elements) |
| elements.append(element.get()); |
| m_domAgent->styleAttributeInvalidated(elements); |
| |
| m_elements.clear(); |
| } |
| |
| String InspectorDOMAgent::toErrorString(ExceptionCode ec) |
| { |
| if (ec) { |
| ExceptionCodeDescription description(ec); |
| return description.name; |
| } |
| return emptyString(); |
| } |
| |
| String InspectorDOMAgent::toErrorString(Exception&& exception) |
| { |
| return ExceptionCodeDescription { exception.code() }.name; |
| } |
| |
| InspectorDOMAgent::InspectorDOMAgent(WebAgentContext& context, InspectorPageAgent* pageAgent, InspectorOverlay* overlay) |
| : InspectorAgentBase(ASCIILiteral("DOM"), context) |
| , m_injectedScriptManager(context.injectedScriptManager) |
| , m_frontendDispatcher(std::make_unique<Inspector::DOMFrontendDispatcher>(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<InspectorHistory>(); |
| m_domEditor = std::make_unique<DOMEditor>(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(); |
| m_mousedOverNode = nullptr; |
| |
| ErrorString unused; |
| setSearchingForNode(unused, false, nullptr); |
| hideHighlight(unused); |
| |
| m_instrumentingAgents.setInspectorDOMAgent(nullptr); |
| m_documentRequested = false; |
| reset(); |
| } |
| |
| Vector<Document*> InspectorDOMAgent::documents() |
| { |
| Vector<Document*> 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* document) |
| { |
| if (document == m_document.get()) |
| return; |
| |
| reset(); |
| |
| m_document = document; |
| |
| if (!m_documentRequested) |
| return; |
| |
| // Immediately communicate null document or document that has finished loading. |
| if (!document || !document->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<const HTMLFrameOwnerElement*>(node); |
| Document* contentDocument = frameOwner->contentDocument(); |
| if (m_domListener) |
| m_domListener->didRemoveDocument(contentDocument); |
| if (contentDocument) |
| unbind(contentDocument, nodesMap); |
| } |
| |
| if (is<Element>(*node)) { |
| Element& element = downcast<Element>(*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<Document>(*node)) { |
| errorString = ASCIILiteral("Document is not available"); |
| return nullptr; |
| } |
| return downcast<Document>(node); |
| } |
| |
| Element* InspectorDOMAgent::assertElement(ErrorString& errorString, int nodeId) |
| { |
| Node* node = assertNode(errorString, nodeId); |
| if (!node) |
| return nullptr; |
| if (!is<Element>(*node)) { |
| errorString = ASCIILiteral("Node is not an Element"); |
| return nullptr; |
| } |
| return downcast<Element>(node); |
| } |
| |
| Node* InspectorDOMAgent::assertEditableNode(ErrorString& errorString, int nodeId) |
| { |
| Node* node = assertNode(errorString, nodeId); |
| if (!node) |
| return nullptr; |
| if (node->isInUserAgentShadowTree()) { |
| errorString = ASCIILiteral("Cannot edit nodes in user agent 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->isInUserAgentShadowTree()) { |
| errorString = ASCIILiteral("Cannot edit elements in user agent shadow trees"); |
| return nullptr; |
| } |
| if (element->isPseudoElement()) { |
| errorString = ASCIILiteral("Cannot edit pseudo elements"); |
| return nullptr; |
| } |
| return element; |
| } |
| |
| void InspectorDOMAgent::getDocument(ErrorString& errorString, RefPtr<Inspector::Protocol::DOM::Node>& root) |
| { |
| m_documentRequested = true; |
| |
| if (!m_document) { |
| errorString = ASCIILiteral("Document is not available"); |
| return; |
| } |
| |
| // Reset backend state. |
| RefPtr<Document> 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; |
| } |
| |
| auto children = buildArrayForContainerChildren(node, depth, nodeMap); |
| m_frontendDispatcher->setChildNodes(nodeId, WTFMove(children)); |
| } |
| |
| 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<int, Node*>::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<ContainerNode>(*node)) { |
| assertElement(errorString, nodeId); |
| return; |
| } |
| |
| auto queryResult = downcast<ContainerNode>(*node).querySelector(selectors); |
| if (queryResult.hasException()) { |
| errorString = ASCIILiteral("DOM Error while querying"); |
| return; |
| } |
| |
| if (auto* element = queryResult.releaseReturnValue()) |
| *elementId = pushNodePathToFrontend(element); |
| } |
| |
| void InspectorDOMAgent::querySelectorAll(ErrorString& errorString, int nodeId, const String& selectors, RefPtr<Inspector::Protocol::Array<int>>& result) |
| { |
| Node* node = assertNode(errorString, nodeId); |
| if (!node) |
| return; |
| if (!is<ContainerNode>(*node)) { |
| assertElement(errorString, nodeId); |
| return; |
| } |
| |
| auto queryResult = downcast<ContainerNode>(*node).querySelectorAll(selectors); |
| if (queryResult.hasException()) { |
| errorString = ASCIILiteral("DOM Error while querying"); |
| return; |
| } |
| |
| auto nodes = queryResult.releaseReturnValue(); |
| result = Inspector::Protocol::Array<int>::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<Node*> 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<NodeToIdMap>(); |
| danglingMap = newMap.get(); |
| m_danglingNodeToIdMaps.append(newMap.release()); |
| auto children = Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>::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(const Node* node) |
| { |
| return m_documentNodeToIdMap.get(const_cast<Node*>(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; |
| |
| auto parsedElement = createHTMLElement(element->document(), spanTag); |
| auto result = parsedElement.get().setInnerHTML("<span " + text + "></span>"); |
| if (result.hasException()) { |
| errorString = toErrorString(result.releaseException()); |
| return; |
| } |
| |
| Node* child = parsedElement->firstChild(); |
| if (!child) { |
| errorString = ASCIILiteral("Could not parse value as attributes"); |
| return; |
| } |
| |
| Element* childElement = downcast<Element>(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<Node> oldNode = nodeForId(nodeId); |
| if (!is<Element>(oldNode.get())) |
| return; |
| |
| auto createElementResult = oldNode->document().createElementForBindings(tagName); |
| if (createElementResult.hasException()) |
| return; |
| auto newElement = createElementResult.releaseReturnValue(); |
| |
| // Copy over the original node's attributes. |
| newElement->cloneAttributesFromElement(downcast<Element>(*oldNode)); |
| |
| // Copy over the original node's children. |
| RefPtr<Node> child; |
| while ((child = oldNode->firstChild())) { |
| if (!m_domEditor->insertBefore(newElement, *child, 0, errorString)) |
| return; |
| } |
| |
| // Replace the old node with the new node |
| RefPtr<ContainerNode> parent = oldNode->parentNode(); |
| if (!m_domEditor->insertBefore(*parent, newElement.copyRef(), oldNode->nextSibling(), errorString)) |
| return; |
| if (!m_domEditor->removeChild(*parent, *oldNode, errorString)) |
| return; |
| |
| *newId = pushNodePathToFrontend(newElement.ptr()); |
| 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<Text>(node), value, errorString); |
| } |
| |
| void InspectorDOMAgent::getEventListenersForNode(ErrorString& errorString, int nodeId, const String* objectGroup, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::DOM::EventListener>>& listenersArray) |
| { |
| listenersArray = Inspector::Protocol::Array<Inspector::Protocol::DOM::EventListener>::create(); |
| Node* node = assertNode(errorString, nodeId); |
| if (!node) |
| return; |
| Vector<EventListenerInfo> 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<EventListenerInfo>& eventInformation, bool includeAncestors) |
| { |
| // The Node's Ancestors including self. |
| Vector<Node*> 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()) { |
| auto& listeners = ancestor->eventListeners(type); |
| EventListenerVector filteredListeners; |
| filteredListeners.reserveInitialCapacity(listeners.size()); |
| for (auto& listener : listeners) { |
| if (listener->callback().type() == EventListener::JSEventListenerType) |
| filteredListeners.uncheckedAppend(listener); |
| } |
| if (!filteredListeners.isEmpty()) |
| eventInformation.append(EventListenerInfo(ancestor, type, WTFMove(filteredListeners))); |
| } |
| } |
| } |
| |
| void InspectorDOMAgent::getAccessibilityPropertiesForNode(ErrorString& errorString, int nodeId, RefPtr<Inspector::Protocol::DOM::AccessibilityProperties>& 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 { |
| // 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<RefPtr<Node>>()).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<Inspector::Protocol::Array<int>>& 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<int>::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> 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> 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(nodeAsScriptValue(*scriptState, node.get())); |
| } |
| |
| void InspectorDOMAgent::mouseDidMoveOverElement(const HitTestResult& result, unsigned) |
| { |
| m_mousedOverNode = result.innerNode(); |
| |
| if (!m_searchingForNode) |
| return; |
| |
| highlightMousedOverNode(); |
| } |
| |
| void InspectorDOMAgent::highlightMousedOverNode() |
| { |
| Node* node = m_mousedOverNode.get(); |
| 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; |
| highlightMousedOverNode(); |
| } else |
| hideHighlight(errorString); |
| |
| m_overlay->didSetSearchingForNode(m_searchingForNode); |
| |
| if (InspectorClient* client = m_pageAgent->page().inspectorController().inspectorClient()) |
| client->elementSelectionChanged(m_searchingForNode); |
| } |
| |
| std::unique_ptr<HighlightConfig> 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<HighlightConfig>(); |
| 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<FloatQuad>(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<FloatQuad>(); |
| if (!parseQuad(quadArray, quad.get())) { |
| errorString = ASCIILiteral("Invalid Quad format"); |
| return; |
| } |
| innerHighlightQuad(WTFMove(quad), color, outlineColor, usePageCoordinates); |
| } |
| |
| void InspectorDOMAgent::innerHighlightQuad(std::unique_ptr<FloatQuad> quad, const InspectorObject* color, const InspectorObject* outlineColor, const bool* usePageCoordinates) |
| { |
| auto highlightConfig = std::make_unique<HighlightConfig>(); |
| 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> 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; |
| } |
| |
| auto queryResult = document->querySelectorAll(selectorString); |
| // FIXME: <https://webkit.org/b/146161> Web Inspector: DOM.highlightSelector should work for "a:visited" |
| if (queryResult.hasException()) { |
| errorString = ASCIILiteral("DOM Error while querying"); |
| return; |
| } |
| |
| auto highlightConfig = highlightConfigFromInspectorObject(errorString, &highlightInspectorObject); |
| if (!highlightConfig) |
| return; |
| |
| m_overlay->highlightNodeList(queryResult.releaseReturnValue(), *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> 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>(); |
| 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 = toErrorString(ec); |
| } |
| |
| void InspectorDOMAgent::redo(ErrorString& errorString) |
| { |
| ExceptionCode ec = 0; |
| m_history->redo(ec); |
| errorString = 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<Inspector::Protocol::Runtime::RemoteObject>& result) |
| { |
| String objectGroupName = objectGroup ? *objectGroup : emptyString(); |
| Node* node = nodeForId(nodeId); |
| if (!node) { |
| errorString = ASCIILiteral("No node with given id found"); |
| return; |
| } |
| RefPtr<Inspector::Protocol::Runtime::RemoteObject> 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<Inspector::Protocol::Array<String>>& 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 emptyString(); |
| return document->url().string(); |
| } |
| |
| static String documentBaseURLString(Document* document) |
| { |
| return document->completeURL(emptyString()).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; |
| } |
| } |
| |
| static Inspector::Protocol::DOM::ShadowRootType shadowRootType(ShadowRootMode mode) |
| { |
| switch (mode) { |
| case ShadowRootMode::UserAgent: |
| return Inspector::Protocol::DOM::ShadowRootType::UserAgent; |
| case ShadowRootMode::Closed: |
| return Inspector::Protocol::DOM::ShadowRootType::Closed; |
| case ShadowRootMode::Open: |
| return Inspector::Protocol::DOM::ShadowRootType::Open; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return Inspector::Protocol::DOM::ShadowRootType::UserAgent; |
| } |
| |
| static Inspector::Protocol::DOM::CustomElementState customElementState(const Element& element) |
| { |
| if (element.isDefinedCustomElement()) |
| return Inspector::Protocol::DOM::CustomElementState::Custom; |
| if (element.isFailedCustomElement()) |
| return Inspector::Protocol::DOM::CustomElementState::Failed; |
| if (element.isUndefinedCustomElement() || element.isCustomElementUpgradeCandidate()) |
| return Inspector::Protocol::DOM::CustomElementState::Waiting; |
| return Inspector::Protocol::DOM::CustomElementState::Builtin; |
| } |
| |
| static String computeContentSecurityPolicySHA256Hash(const Element& element) |
| { |
| // FIXME: Compute the digest with respect to the raw bytes received from the page. |
| // See <https://bugs.webkit.org/show_bug.cgi?id=155184>. |
| TextEncoding documentEncoding = element.document().textEncoding(); |
| const TextEncoding& encodingToUse = documentEncoding.isValid() ? documentEncoding : UTF8Encoding(); |
| CString content = encodingToUse.encode(TextNodeTraversal::contentsAsString(element), EntitiesForUnencodables); |
| auto cryptoDigest = CryptoDigest::create(CryptoDigest::Algorithm::SHA_256); |
| cryptoDigest->addBytes(content.data(), content.length()); |
| Vector<uint8_t> digest = cryptoDigest->computeHash(); |
| return makeString("sha256-", base64Encode(digest.data(), digest.size())); |
| } |
| |
| Ref<Inspector::Protocol::DOM::Node> 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<int>(node->nodeType())) |
| .setNodeName(nodeName) |
| .setLocalName(localName) |
| .setNodeValue(nodeValue) |
| .release(); |
| |
| if (node->isContainerNode()) { |
| int nodeCount = innerChildNodeCount(node); |
| value->setChildNodeCount(nodeCount); |
| Ref<Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>> children = buildArrayForContainerChildren(node, depth, nodesMap); |
| if (children->length() > 0) |
| value->setChildren(WTFMove(children)); |
| } |
| |
| if (is<Element>(*node)) { |
| Element& element = downcast<Element>(*node); |
| value->setAttributes(buildArrayForElementAttributes(&element)); |
| if (is<HTMLFrameOwnerElement>(element)) { |
| HTMLFrameOwnerElement& frameOwner = downcast<HTMLFrameOwnerElement>(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<Inspector::Protocol::DOM::Node>::create(); |
| shadowRoots->addItem(buildObjectForNode(root, 0, nodesMap)); |
| value->setShadowRoots(WTFMove(shadowRoots)); |
| } |
| |
| if (is<HTMLTemplateElement>(element)) |
| value->setTemplateContent(buildObjectForNode(&downcast<HTMLTemplateElement>(element).content(), 0, nodesMap)); |
| |
| if (is<HTMLStyleElement>(element) || (is<HTMLScriptElement>(element) && !element.hasAttributeWithoutSynchronization(HTMLNames::srcAttr))) |
| value->setContentSecurityPolicyHash(computeContentSecurityPolicySHA256Hash(element)); |
| |
| auto state = customElementState(element); |
| if (state != Inspector::Protocol::DOM::CustomElementState::Builtin) |
| value->setCustomElementState(state); |
| |
| 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<Document>(*node)) { |
| Document& document = downcast<Document>(*node); |
| value->setFrameId(m_pageAgent->frameId(document.frame())); |
| value->setDocumentURL(documentURLString(&document)); |
| value->setBaseURL(documentBaseURLString(&document)); |
| value->setXmlVersion(document.xmlVersion()); |
| } else if (is<DocumentType>(*node)) { |
| DocumentType& docType = downcast<DocumentType>(*node); |
| value->setPublicId(docType.publicId()); |
| value->setSystemId(docType.systemId()); |
| } else if (is<Attr>(*node)) { |
| Attr& attribute = downcast<Attr>(*node); |
| value->setName(attribute.name()); |
| value->setValue(attribute.value()); |
| } else if (is<ShadowRoot>(*node)) { |
| ShadowRoot& shadowRoot = downcast<ShadowRoot>(*node); |
| value->setShadowRootType(shadowRootType(shadowRoot.mode())); |
| } |
| |
| // 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<Inspector::Protocol::Array<String>> InspectorDOMAgent::buildArrayForElementAttributes(Element* element) |
| { |
| auto attributesValue = Inspector::Protocol::Array<String>::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<Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>> InspectorDOMAgent::buildArrayForContainerChildren(Node* container, int depth, NodeToIdMap* nodesMap) |
| { |
| auto children = Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>::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<Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>> 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<Inspector::Protocol::DOM::Node>::create(); |
| if (beforeElement) |
| pseudoElements->addItem(buildObjectForNode(beforeElement, 0, nodesMap)); |
| if (afterElement) |
| pseudoElements->addItem(buildObjectForNode(afterElement, 0, nodesMap)); |
| return WTFMove(pseudoElements); |
| } |
| |
| Ref<Inspector::Protocol::DOM::EventListener> InspectorDOMAgent::buildObjectForEventListener(const RegisteredEventListener& registeredEventListener, const AtomicString& eventType, Node* node, const String* objectGroupId) |
| { |
| Ref<EventListener> eventListener = registeredEventListener.callback(); |
| |
| JSC::ExecState* state = nullptr; |
| JSC::JSObject* handler = nullptr; |
| String body; |
| int lineNumber = 0; |
| int columnNumber = 0; |
| String scriptID; |
| String sourceName; |
| if (auto scriptListener = JSEventListener::cast(eventListener.ptr())) { |
| 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 = jsDynamicDowncast<JSC::JSFunction*>(handler)) { |
| if (!function->isHostOrBuiltinFunction()) { |
| if (auto executable = function->jsExecutable()) { |
| lineNumber = executable->firstLine() - 1; |
| columnNumber = executable->startColumn() - 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(handler, *objectGroupId)); |
| } |
| if (!scriptID.isNull()) { |
| auto location = Inspector::Protocol::Debugger::Location::create() |
| .setScriptId(scriptID) |
| .setLineNumber(lineNumber) |
| .release(); |
| location->setColumnNumber(columnNumber); |
| value->setLocation(WTFMove(location)); |
| if (!sourceName.isEmpty()) |
| value->setSourceName(sourceName); |
| } |
| return value; |
| } |
| |
| void InspectorDOMAgent::processAccessibilityChildren(RefPtr<AccessibilityObject>&& axObject, RefPtr<Inspector::Protocol::Array<int>>&& childNodeIds) |
| { |
| const auto& children = axObject->children(); |
| if (!children.size()) |
| return; |
| |
| if (!childNodeIds) |
| childNodeIds = Inspector::Protocol::Array<int>::create(); |
| |
| for (const auto& childObject : children) { |
| if (Node* childNode = childObject->node()) |
| childNodeIds->addItem(pushNodePathToFrontend(childNode)); |
| else |
| processAccessibilityChildren(childObject.copyRef(), childNodeIds.copyRef()); |
| } |
| } |
| |
| RefPtr<Inspector::Protocol::DOM::AccessibilityProperties> 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<Inspector::Protocol::Array<int>> childNodeIds; |
| RefPtr<Inspector::Protocol::Array<int>> controlledNodeIds; |
| auto currentState = Inspector::Protocol::DOM::AccessibilityProperties::Current::False; |
| bool exists = false; |
| bool expanded = false; |
| bool disabled = false; |
| RefPtr<Inspector::Protocol::Array<int>> 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<Inspector::Protocol::Array<String>> liveRegionRelevant; |
| auto liveRegionStatus = Inspector::Protocol::DOM::AccessibilityProperties::LiveRegionStatus::Off; |
| Node* mouseEventNode = nullptr; |
| RefPtr<Inspector::Protocol::Array<int>> ownedNodeIds; |
| Node* parentNode = nullptr; |
| bool pressed = false; |
| bool readonly = false; |
| bool required = false; |
| String role; |
| bool selected = false; |
| RefPtr<Inspector::Protocol::Array<int>> selectedChildNodeIds; |
| bool supportsChecked = false; |
| bool supportsExpanded = false; |
| bool supportsLiveRegion = false; |
| bool supportsPressed = false; |
| bool supportsRequired = false; |
| bool supportsFocused = false; |
| int headingLevel = 0; |
| unsigned hierarchicalLevel = 0; |
| unsigned level = 0; |
| |
| 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)); |
| |
| Vector<Element*> controlledElements; |
| axObject->elementsFromAttribute(controlledElements, aria_controlsAttr); |
| if (controlledElements.size()) { |
| controlledNodeIds = Inspector::Protocol::Array<int>::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(); |
| |
| Vector<Element*> flowedElements; |
| axObject->elementsFromAttribute(flowedElements, aria_flowtoAttr); |
| if (flowedElements.size()) { |
| flowedNodeIds = Inspector::Protocol::Array<int>::create(); |
| for (Element* flowedElement : flowedElements) |
| flowedNodeIds->addItem(pushNodePathToFrontend(flowedElement)); |
| } |
| |
| if (is<Element>(*node)) { |
| supportsFocused = axObject->canSetFocusAttribute(); |
| 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::InspectorHelpers::getEnumConstantValue(Inspector::Protocol::DOM::LiveRegionRelevant::Additions); |
| String ariaRelevantRemovals = Inspector::Protocol::InspectorHelpers::getEnumConstantValue(Inspector::Protocol::DOM::LiveRegionRelevant::Removals); |
| String ariaRelevantText = Inspector::Protocol::InspectorHelpers::getEnumConstantValue(Inspector::Protocol::DOM::LiveRegionRelevant::Text); |
| liveRegionRelevant = Inspector::Protocol::Array<String>::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<AccessibilityNodeObject>(*axObject)) |
| mouseEventNode = downcast<AccessibilityNodeObject>(*axObject).mouseButtonListener(MouseButtonListenerResultFilter::IncludeBodyElement); |
| |
| if (axObject->supportsARIAOwns()) { |
| Vector<Element*> ownedElements; |
| axObject->elementsFromAttribute(ownedElements, aria_ownsAttr); |
| if (ownedElements.size()) { |
| ownedNodeIds = Inspector::Protocol::Array<int>::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<int>::create(); |
| for (auto& selectedChildObject : selectedChildren) { |
| if (Node* selectedChildNode = selectedChildObject->node()) |
| selectedChildNodeIds->addItem(pushNodePathToFrontend(selectedChildNode)); |
| } |
| } |
| |
| headingLevel = axObject->headingLevel(); |
| hierarchicalLevel = axObject->hierarchicalLevel(); |
| |
| level = hierarchicalLevel ? hierarchicalLevel : headingLevel; |
| } |
| } |
| |
| Ref<Inspector::Protocol::DOM::AccessibilityProperties> 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); |
| |
| // H1 -- H6 always have a headingLevel property that can be complimented by a hierarchicalLevel |
| // property when aria-level is set on the element, in which case we want to remain calling |
| // this value the "Heading Level" in the inspector. |
| // Also, we do not want it to say Hierarchy Level: 0 |
| if (headingLevel) |
| value->setHeadingLevel(level); |
| else if (level) |
| value->setHierarchyLevel(level); |
| } |
| |
| 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<Document>(*node)) |
| return downcast<Document>(*node).ownerElement(); |
| if (is<ShadowRoot>(*node)) |
| return downcast<ShadowRoot>(*node).host(); |
| 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) |
| { |
| RefPtr<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.get())); |
| m_frontendDispatcher->childNodeRemoved(parentId, frameOwnerId); |
| unbind(frameOwner.get(), &m_documentNodeToIdMap); |
| |
| Ref<Inspector::Protocol::DOM::Node> value = buildObjectForNode(frameOwner.get(), 0, &m_documentNodeToIdMap); |
| Node* previousSibling = innerPreviousSibling(frameOwner.get()); |
| 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<Inspector::Protocol::DOM::Node> 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<Element*>& elements) |
| { |
| auto nodeIds = Inspector::Protocol::Array<int>::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<RevalidateStyleAttributeTask>(this); |
| m_revalidateStyleAttrTask->scheduleFor(downcast<Element>(&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::didChangeCustomElementState(Element& element) |
| { |
| int elementId = m_documentNodeToIdMap.get(&element); |
| if (!elementId) |
| return; |
| |
| m_frontendDispatcher->customElementStateChanged(elementId, customElementState(element)); |
| } |
| |
| void InspectorDOMAgent::frameDocumentUpdated(Frame& frame) |
| { |
| Document* document = frame.document(); |
| if (!document) |
| return; |
| |
| if (!frame.isMainFrame()) |
| 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<String> pathTokens; |
| path.split(',', false, pathTokens); |
| if (!pathTokens.size()) |
| return 0; |
| for (size_t i = 0; i < pathTokens.size() - 1; i += 2) { |
| bool success = true; |
| unsigned childNumber = pathTokens[i].toUInt(&success); |
| if (!success) |
| return 0; |
| if (childNumber >= innerChildNodeCount(node)) |
| return 0; |
| |
| Node* child = innerFirstChild(node); |
| String childName = pathTokens[i + 1]; |
| for (size_t j = 0; child && j < childNumber; ++j) |
| child = innerNextSibling(child); |
| |
| if (!child || child->nodeName() != childName) |
| return 0; |
| node = child; |
| } |
| return node; |
| } |
| |
| Node* InspectorDOMAgent::nodeForObjectId(const String& objectId) |
| { |
| InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(objectId); |
| if (injectedScript.hasNoValue()) |
| return nullptr; |
| |
| return scriptValueAsNode(injectedScript.findObjectById(objectId)); |
| } |
| |
| 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) |
| { |
| auto iterator = m_backendIdToNode.find(backendNodeId); |
| if (iterator == m_backendIdToNode.end()) { |
| errorString = ASCIILiteral("No node with given backend id found"); |
| return; |
| } |
| |
| Node* node = iterator->value.first; |
| String nodeGroup = iterator->value.second; |
| |
| *nodeId = pushNodePathToFrontend(node); |
| |
| if (nodeGroup.isEmpty()) { |
| m_backendIdToNode.remove(iterator); |
| // FIXME: We really do the following only when nodeGroup is the empty string? Seems wrong. |
| ASSERT(m_nodeGroupToBackendIdMap.contains(nodeGroup)); |
| m_nodeGroupToBackendIdMap.find(nodeGroup)->value.remove(node); |
| } |
| } |
| |
| RefPtr<Inspector::Protocol::Runtime::RemoteObject> InspectorDOMAgent::resolveNode(Node* node, const String& objectGroup) |
| { |
| auto* frame = node->document().frame(); |
| if (!frame) |
| return nullptr; |
| |
| auto& state = *mainWorldExecState(frame); |
| auto injectedScript = m_injectedScriptManager.injectedScriptFor(&state); |
| if (injectedScript.hasNoValue()) |
| return nullptr; |
| |
| return injectedScript.wrapObject(nodeAsScriptValue(state, node), objectGroup); |
| } |
| |
| Node* InspectorDOMAgent::scriptValueAsNode(JSC::JSValue value) |
| { |
| if (!value) |
| return nullptr; |
| return JSNode::toWrapped(value); |
| } |
| |
| JSC::JSValue InspectorDOMAgent::nodeAsScriptValue(JSC::ExecState& state, Node* node) |
| { |
| JSC::JSLockHolder lock(&state); |
| return toJS(&state, deprecatedGlobalObjectForPrototype(&state), BindingSecurity::checkSecurityForNode(state, node)); |
| } |
| |
| } // namespace WebCore |