| /* |
| * Copyright (C) 2009 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" |
| |
| #if ENABLE(INSPECTOR) |
| |
| #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 "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 "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 <inspector/IdentifiersFactory.h> |
| #include <inspector/InjectedScript.h> |
| #include <inspector/InjectedScriptManager.h> |
| #include <runtime/JSCInlines.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 PassRefPtr<InspectorObject> colorObject) |
| { |
| if (!colorObject) |
| return Color::transparent; |
| |
| int r; |
| int g; |
| int b; |
| if (!colorObject->getInteger("r", r) || !colorObject->getInteger("g", g) || !colorObject->getInteger("b", b)) |
| return Color::transparent; |
| |
| double a; |
| 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, InspectorObject* configObject) |
| { |
| const RefPtr<InspectorObject> colorObject = configObject->getObject(fieldName); |
| return parseColor(colorObject); |
| } |
| |
| static bool parseQuad(const RefPtr<InspectorArray>& quadArray, FloatQuad* quad) |
| { |
| if (!quadArray) |
| return false; |
| 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(Timer<RevalidateStyleAttributeTask>&); |
| |
| private: |
| InspectorDOMAgent* m_domAgent; |
| Timer<RevalidateStyleAttributeTask> 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(Timer<RevalidateStyleAttributeTask>&) |
| { |
| // 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 (HashSet<RefPtr<Element>>::iterator it = m_elements.begin(), end = m_elements.end(); it != end; ++it) |
| elements.append(it->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(InstrumentingAgents* instrumentingAgents, InspectorPageAgent* pageAgent, InjectedScriptManager* injectedScriptManager, InspectorOverlay* overlay) |
| : InspectorAgentBase(ASCIILiteral("DOM"), instrumentingAgents) |
| , m_pageAgent(pageAgent) |
| , m_injectedScriptManager(injectedScriptManager) |
| , m_overlay(overlay) |
| , m_domListener(0) |
| , m_lastNodeId(1) |
| , m_lastBackendNodeId(-1) |
| , m_searchingForNode(false) |
| , m_suppressAttributeModifiedEvent(false) |
| , m_documentRequested(false) |
| { |
| } |
| |
| InspectorDOMAgent::~InspectorDOMAgent() |
| { |
| reset(); |
| ASSERT(!m_searchingForNode); |
| } |
| |
| void InspectorDOMAgent::didCreateFrontendAndBackend(InspectorFrontendChannel* frontendChannel, InspectorBackendDispatcher* backendDispatcher) |
| { |
| m_frontendDispatcher = std::make_unique<InspectorDOMFrontendDispatcher>(frontendChannel); |
| m_backendDispatcher = InspectorDOMBackendDispatcher::create(backendDispatcher, this); |
| |
| 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(InspectorDisconnectReason) |
| { |
| m_frontendDispatcher = nullptr; |
| m_backendDispatcher.clear(); |
| |
| m_history.reset(); |
| m_domEditor.reset(); |
| |
| ErrorString unused; |
| setSearchingForNode(unused, false, 0); |
| hideHighlight(unused); |
| |
| m_instrumentingAgents->setInspectorDOMAgent(0); |
| 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 = 0; |
| } |
| |
| 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<const HTMLFrameOwnerElement*>(node); |
| Document* contentDocument = frameOwner->contentDocument(); |
| if (m_domListener) |
| m_domListener->didRemoveDocument(contentDocument); |
| if (contentDocument) |
| unbind(contentDocument, nodesMap); |
| } |
| |
| if (is<Element>(*node)) { |
| if (ShadowRoot* root = downcast<Element>(*node).shadowRoot()) |
| unbind(root, 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->isInShadowTree()) { |
| errorString = ASCIILiteral("Can not edit nodes from shadow trees"); |
| 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("Can not edit elements from shadow trees"); |
| 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; |
| } |
| |
| RefPtr<Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>> 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<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; |
| } |
| |
| ExceptionCode ec = 0; |
| RefPtr<Element> element = downcast<ContainerNode>(*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<Inspector::Protocol::Array<int>>& result) |
| { |
| Node* node = assertNode(errorString, nodeId); |
| if (!node) |
| return; |
| if (!is<ContainerNode>(*node)) { |
| assertElement(errorString, nodeId); |
| return; |
| } |
| |
| ExceptionCode ec = 0; |
| RefPtr<NodeList> nodes = downcast<ContainerNode>(*node).querySelectorAll(selectors, ec); |
| if (ec) { |
| errorString = ASCIILiteral("DOM Error while querying"); |
| return; |
| } |
| |
| 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()); |
| RefPtr<Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>> children = Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>::create(); |
| children->addItem(buildObjectForNode(node, 0, danglingMap)); |
| m_frontendDispatcher->setChildNodes(0, 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 (NodeToBackendIdMap::iterator it = map.begin(); it != map.end(); ++it) |
| m_backendIdToNode.remove(it->value); |
| 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<HTMLElement> parsedElement = createHTMLElement(element->document(), spanTag); |
| ExceptionCode ec = 0; |
| parsedElement.get()->setInnerHTML("<span " + text + "></span>", 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<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("Can not remove detached node"); |
| return; |
| } |
| |
| m_domEditor->removeChild(parentNode, node, errorString); |
| } |
| |
| void InspectorDOMAgent::setNodeName(ErrorString& errorString, int nodeId, const String& tagName, int* newId) |
| { |
| *newId = 0; |
| |
| Node* oldNode = nodeForId(nodeId); |
| if (!is<Element>(oldNode)) |
| return; |
| |
| ExceptionCode ec = 0; |
| RefPtr<Element> newElem = oldNode->document().createElement(tagName, ec); |
| if (ec) |
| return; |
| |
| // Copy over the original node's attributes. |
| newElem->cloneAttributesFromElement(*downcast<Element>(oldNode)); |
| |
| // Copy over the original node's children. |
| Node* child; |
| while ((child = oldNode->firstChild())) { |
| if (!m_domEditor->insertBefore(newElem.get(), child, 0, errorString)) |
| return; |
| } |
| |
| // Replace the old node with the new node |
| ContainerNode* parent = oldNode->parentNode(); |
| if (!m_domEditor->insertBefore(parent, newElem.get(), oldNode->nextSibling(), errorString)) |
| return; |
| if (!m_domEditor->removeChild(parent, oldNode, errorString)) |
| return; |
| |
| *newId = pushNodePathToFrontend(newElem.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.isXHTMLDocument() && !document.isSVGDocument()) { |
| 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 (size_t i = 0; i < eventInformationLength; ++i) { |
| const EventListenerInfo& info = eventInformation[i]; |
| const EventListenerVector& vector = info.eventListenerVector; |
| for (size_t j = 0; j < vector.size(); ++j) { |
| const RegisteredEventListener& listener = vector[j]; |
| if (listener.useCapture) |
| listenersArray->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]; |
| const EventListenerVector& vector = info.eventListenerVector; |
| for (size_t j = 0; j < vector.size(); ++j) { |
| const RegisteredEventListener& listener = vector[j]; |
| 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 |
| Vector<AtomicString> eventTypes = d->eventListenerMap.eventTypes(); |
| for (size_t j = 0; j < eventTypes.size(); ++j) { |
| AtomicString& type = eventTypes[j]; |
| const EventListenerVector& listeners = ancestor->getEventListeners(type); |
| EventListenerVector filteredListeners; |
| filteredListeners.reserveCapacity(listeners.size()); |
| for (size_t k = 0; k < listeners.size(); ++k) { |
| if (listeners[k].listener->type() == EventListener::JSEventListenerType) |
| filteredListeners.append(listeners[k]); |
| } |
| if (!filteredListeners.isEmpty()) |
| eventInformation.append(EventListenerInfo(ancestor, type, 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 RefPtr<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) { |
| const RefPtr<InspectorArray>& nodeIdsRef = *nodeIds; |
| for (unsigned i = 0; i < nodeIdsRef->length(); ++i) { |
| RefPtr<InspectorValue> nodeValue = nodeIdsRef->get(i); |
| 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<RefPtr<Node>>()).iterator->value; |
| for (auto iterator = finder.results().begin(); iterator != finder.results().end(); ++iterator) |
| resultsVector.append(*iterator); |
| |
| *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 (node && 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, 0); |
| |
| if (node->nodeType() != Node::ELEMENT_NODE && node->nodeType() != Node::DOCUMENT_NODE) |
| node = node->parentNode(); |
| m_nodeToFocus = node; |
| |
| focusNode(); |
| } |
| |
| void InspectorDOMAgent::focusNode() |
| { |
| if (!m_frontendDispatcher) |
| return; |
| |
| ASSERT(m_nodeToFocus); |
| |
| RefPtr<Node> node = m_nodeToFocus.get(); |
| m_nodeToFocus = 0; |
| |
| 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, 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<HighlightConfig> InspectorDOMAgent::highlightConfigFromInspectorObject(ErrorString& errorString, 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 RefPtr<InspectorObject>* highlightConfig) |
| { |
| setSearchingForNode(errorString, enabled, highlightConfig ? highlightConfig->get() : 0); |
| } |
| |
| void InspectorDOMAgent::highlightRect(ErrorString&, int x, int y, int width, int height, const RefPtr<InspectorObject>* color, const RefPtr<InspectorObject>* outlineColor, const bool* usePageCoordinates) |
| { |
| auto quad = std::make_unique<FloatQuad>(FloatRect(x, y, width, height)); |
| innerHighlightQuad(WTF::move(quad), color, outlineColor, usePageCoordinates); |
| } |
| |
| void InspectorDOMAgent::highlightQuad(ErrorString& errorString, const RefPtr<InspectorArray>& quadArray, const RefPtr<InspectorObject>* color, const RefPtr<InspectorObject>* outlineColor, const bool* usePageCoordinates) |
| { |
| auto quad = std::make_unique<FloatQuad>(); |
| if (!parseQuad(quadArray, quad.get())) { |
| errorString = ASCIILiteral("Invalid Quad format"); |
| return; |
| } |
| innerHighlightQuad(WTF::move(quad), color, outlineColor, usePageCoordinates); |
| } |
| |
| void InspectorDOMAgent::innerHighlightQuad(std::unique_ptr<FloatQuad> quad, const RefPtr<InspectorObject>* color, const RefPtr<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(WTF::move(quad), *highlightConfig); |
| } |
| |
| void InspectorDOMAgent::highlightNode(ErrorString& errorString, const RefPtr<InspectorObject>& highlightInspectorObject, const int* nodeId, const String* objectId) |
| { |
| Node* node = 0; |
| 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.get()); |
| if (!highlightConfig) |
| return; |
| |
| m_overlay->highlightNode(node, *highlightConfig); |
| } |
| |
| void InspectorDOMAgent::highlightFrame(ErrorString&, const String& frameId, const RefPtr<InspectorObject>* color, const RefPtr<InspectorObject>* outlineColor) |
| { |
| Frame* frame = m_pageAgent->frameForId(frameId); |
| if (frame && 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 = 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<Inspector::Protocol::Runtime::RemoteObject>& result) |
| { |
| String objectGroupName = objectGroup ? *objectGroup : ""; |
| 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 ""; |
| return document->url().string(); |
| } |
| |
| static String documentBaseURLString(Document* document) |
| { |
| return document->completeURL("").string(); |
| } |
| |
| PassRefPtr<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; |
| } |
| |
| RefPtr<Inspector::Protocol::DOM::Node> value = Inspector::Protocol::DOM::Node::create() |
| .setNodeId(id) |
| .setNodeType(static_cast<int>(node->nodeType())) |
| .setNodeName(nodeName) |
| .setLocalName(localName) |
| .setNodeValue(nodeValue); |
| |
| if (node->isContainerNode()) { |
| int nodeCount = innerChildNodeCount(node); |
| value->setChildNodeCount(nodeCount); |
| RefPtr<Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>> children = buildArrayForContainerChildren(node, depth, nodesMap); |
| if (children->length() > 0) |
| value->setChildren(children.release()); |
| } |
| |
| 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()) { |
| RefPtr<Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>> shadowRoots = Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>::create(); |
| shadowRoots->addItem(buildObjectForNode(root, 0, nodesMap)); |
| value->setShadowRoots(shadowRoots); |
| } |
| |
| #if ENABLE(TEMPLATE_ELEMENT) |
| if (is<HTMLTemplateElement>(element)) |
| value->setTemplateContent(buildObjectForNode(downcast<HTMLTemplateElement>(element).content(), 0, nodesMap)); |
| #endif |
| |
| } else if (is<Document>(*node)) { |
| Document& document = downcast<Document>(*node); |
| 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()); |
| value->setInternalSubset(docType.internalSubset()); |
| } else if (is<Attr>(*node)) { |
| Attr& attribute = downcast<Attr>(*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.release(); |
| } |
| |
| PassRefPtr<Inspector::Protocol::Array<String>> InspectorDOMAgent::buildArrayForElementAttributes(Element* element) |
| { |
| RefPtr<Inspector::Protocol::Array<String>> attributesValue = Inspector::Protocol::Array<String>::create(); |
| // Go through all attributes and serialize them. |
| if (!element->hasAttributes()) |
| return attributesValue.release(); |
| for (const Attribute& attribute : element->attributesIterator()) { |
| // Add attribute pair |
| attributesValue->addItem(attribute.name().toString()); |
| attributesValue->addItem(attribute.value()); |
| } |
| return attributesValue.release(); |
| } |
| |
| PassRefPtr<Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>> InspectorDOMAgent::buildArrayForContainerChildren(Node* container, int depth, NodeToIdMap* nodesMap) |
| { |
| RefPtr<Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>> 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.release(); |
| } |
| |
| Node* child = innerFirstChild(container); |
| depth--; |
| m_childrenRequested.add(bind(container, nodesMap)); |
| |
| while (child) { |
| children->addItem(buildObjectForNode(child, depth, nodesMap)); |
| child = innerNextSibling(child); |
| } |
| return children.release(); |
| } |
| |
| PassRefPtr<Inspector::Protocol::DOM::EventListener> InspectorDOMAgent::buildObjectForEventListener(const RegisteredEventListener& registeredEventListener, const AtomicString& eventType, Node* node, const String* objectGroupId) |
| { |
| RefPtr<EventListener> 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) { |
| body = handler->toString(state)->value(state); |
| if (auto function = JSC::jsDynamicCast<JSC::JSFunction*>(handler)) { |
| if (!function->isHostOrBuiltinFunction()) { |
| if (auto executable = function->jsExecutable()) { |
| lineNumber = executable->lineNo() - 1; |
| scriptID = executable->sourceID() == JSC::SourceProvider::nullID ? emptyString() : String::number(executable->sourceID()); |
| sourceName = executable->sourceURL(); |
| } |
| } |
| } |
| } |
| } |
| |
| RefPtr<Inspector::Protocol::DOM::EventListener> value = Inspector::Protocol::DOM::EventListener::create() |
| .setType(eventType) |
| .setUseCapture(registeredEventListener.useCapture) |
| .setIsAttribute(eventListener->isAttribute()) |
| .setNodeId(pushNodePathToFrontend(node)) |
| .setHandlerBody(body); |
| 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()) { |
| RefPtr<Inspector::Protocol::Debugger::Location> location = Inspector::Protocol::Debugger::Location::create() |
| .setScriptId(scriptID) |
| .setLineNumber(lineNumber); |
| value->setLocation(location.release()); |
| if (!sourceName.isEmpty()) |
| value->setSourceName(sourceName); |
| } |
| return value.release(); |
| } |
| |
| void InspectorDOMAgent::processAccessibilityChildren(PassRefPtr<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, childNodeIds); |
| } |
| } |
| |
| PassRefPtr<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; |
| Inspector::Protocol::DOM::AccessibilityProperties::Checked checked = Inspector::Protocol::DOM::AccessibilityProperties::Checked::False; |
| RefPtr<Inspector::Protocol::Array<int>> childNodeIds; |
| RefPtr<Inspector::Protocol::Array<int>> controlledNodeIds; |
| bool exists = false; |
| bool expanded = false; |
| bool disabled = false; |
| RefPtr<Inspector::Protocol::Array<int>> flowedNodeIds; |
| bool focused = false; |
| bool ignored = true; |
| bool ignoredByDefault = false; |
| Inspector::Protocol::DOM::AccessibilityProperties::Invalid invalid = Inspector::Protocol::DOM::AccessibilityProperties::Invalid::False; |
| bool hidden = false; |
| String label; |
| bool liveRegionAtomic = false; |
| RefPtr<Inspector::Protocol::Array<String>> liveRegionRelevant; |
| Inspector::Protocol::DOM::AccessibilityProperties::LiveRegionStatus 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; |
| |
| 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, childNodeIds); |
| |
| if (axObject->supportsARIAControls()) { |
| 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)); |
| } |
| } |
| |
| disabled = !axObject->isEnabled(); |
| exists = true; |
| |
| supportsExpanded = axObject->supportsARIAExpanded(); |
| if (supportsExpanded) |
| expanded = axObject->isExpanded(); |
| |
| if (axObject->supportsARIAFlowTo()) { |
| 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 = downcast<Element>(*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<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->isReadOnly(); |
| |
| 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)); |
| } |
| } |
| } |
| } |
| |
| RefPtr<Inspector::Protocol::DOM::AccessibilityProperties> value = Inspector::Protocol::DOM::AccessibilityProperties::create() |
| .setExists(exists) |
| .setLabel(label) |
| .setRole(role) |
| .setNodeId(pushNodePathToFrontend(node)); |
| |
| 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 (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 value.release(); |
| } |
| |
| 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(); |
| 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); |
| |
| RefPtr<Inspector::Protocol::DOM::Node> value = buildObjectForNode(frameOwner, 0, &m_documentNodeToIdMap); |
| Node* previousSibling = innerPreviousSibling(frameOwner); |
| int prevId = previousSibling ? m_documentNodeToIdMap.get(previousSibling) : 0; |
| m_frontendDispatcher->childNodeInserted(parentId, prevId, value.release()); |
| } |
| |
| 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; |
| RefPtr<Inspector::Protocol::DOM::Node> value = buildObjectForNode(node, 0, &m_documentNodeToIdMap); |
| m_frontendDispatcher->childNodeInserted(parentId, prevId, value.release()); |
| } |
| } |
| |
| 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) |
| { |
| RefPtr<Inspector::Protocol::Array<int>> nodeIds = Inspector::Protocol::Array<int>::create(); |
| for (unsigned i = 0, size = elements.size(); i < size; ++i) { |
| Element* element = elements.at(i); |
| 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(nodeIds.release()); |
| } |
| |
| 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::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); |
| } |
| |
| 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); |
| 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); |
| } |
| } |
| |
| PassRefPtr<Inspector::Protocol::Runtime::RemoteObject> 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 |
| |
| #endif // ENABLE(INSPECTOR) |