| /* |
| * 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 Computer, Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "core/inspector/InspectorDOMAgent.h" |
| |
| #include "bindings/core/v8/BindingSecurity.h" |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "bindings/core/v8/V8Node.h" |
| #include "core/InputTypeNames.h" |
| #include "core/dom/Attr.h" |
| #include "core/dom/CharacterData.h" |
| #include "core/dom/ContainerNode.h" |
| #include "core/dom/DOMException.h" |
| #include "core/dom/DOMNodeIds.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/DocumentFragment.h" |
| #include "core/dom/DocumentType.h" |
| #include "core/dom/Element.h" |
| #include "core/dom/Node.h" |
| #include "core/dom/PseudoElement.h" |
| #include "core/dom/StaticNodeList.h" |
| #include "core/dom/Text.h" |
| #include "core/dom/shadow/ElementShadow.h" |
| #include "core/dom/shadow/InsertionPoint.h" |
| #include "core/dom/shadow/ShadowRoot.h" |
| #include "core/editing/serializers/Serialization.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/html/HTMLFrameOwnerElement.h" |
| #include "core/html/HTMLInputElement.h" |
| #include "core/html/HTMLLinkElement.h" |
| #include "core/html/HTMLSlotElement.h" |
| #include "core/html/HTMLTemplateElement.h" |
| #include "core/html/imports/HTMLImportChild.h" |
| #include "core/html/imports/HTMLImportLoader.h" |
| #include "core/inspector/DOMEditor.h" |
| #include "core/inspector/DOMPatchSupport.h" |
| #include "core/inspector/IdentifiersFactory.h" |
| #include "core/inspector/InspectedFrames.h" |
| #include "core/inspector/InspectorHighlight.h" |
| #include "core/inspector/InspectorHistory.h" |
| #include "core/inspector/V8InspectorString.h" |
| #include "core/layout/HitTestResult.h" |
| #include "core/layout/LayoutInline.h" |
| #include "core/layout/api/LayoutViewItem.h" |
| #include "core/layout/line/InlineTextBox.h" |
| #include "core/loader/DocumentLoader.h" |
| #include "core/page/FrameTree.h" |
| #include "core/page/Page.h" |
| #include "core/xml/DocumentXPathEvaluator.h" |
| #include "core/xml/XPathResult.h" |
| #include "platform/PlatformGestureEvent.h" |
| #include "platform/PlatformMouseEvent.h" |
| #include "platform/PlatformTouchEvent.h" |
| #include "wtf/ListHashSet.h" |
| #include "wtf/PtrUtil.h" |
| #include "wtf/text/CString.h" |
| #include "wtf/text/WTFString.h" |
| #include <memory> |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| |
| namespace DOMAgentState { |
| static const char domAgentEnabled[] = "domAgentEnabled"; |
| }; |
| |
| namespace { |
| |
| const size_t maxTextSize = 10000; |
| const UChar ellipsisUChar[] = {0x2026, 0}; |
| |
| Color parseColor(protocol::DOM::RGBA* rgba) { |
| if (!rgba) |
| return Color::transparent; |
| |
| int r = rgba->getR(); |
| int g = rgba->getG(); |
| int b = rgba->getB(); |
| if (!rgba->hasA()) |
| return Color(r, g, b); |
| |
| double a = rgba->getA(1); |
| // 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)); |
| } |
| |
| bool parseQuad(std::unique_ptr<protocol::Array<double>> quadArray, |
| FloatQuad* quad) { |
| const size_t coordinatesInQuad = 8; |
| if (!quadArray || quadArray->length() != coordinatesInQuad) |
| return false; |
| quad->setP1(FloatPoint(quadArray->get(0), quadArray->get(1))); |
| quad->setP2(FloatPoint(quadArray->get(2), quadArray->get(3))); |
| quad->setP3(FloatPoint(quadArray->get(4), quadArray->get(5))); |
| quad->setP4(FloatPoint(quadArray->get(6), quadArray->get(7))); |
| return true; |
| } |
| |
| v8::Local<v8::Value> nodeV8Value(v8::Local<v8::Context> context, Node* node) { |
| v8::Isolate* isolate = context->GetIsolate(); |
| if (!node || |
| !BindingSecurity::shouldAllowAccessTo( |
| currentDOMWindow(isolate), node, |
| BindingSecurity::ErrorReportOption::DoNotReport)) |
| return v8::Null(isolate); |
| return toV8(node, context->Global(), isolate); |
| } |
| |
| } // namespace |
| |
| class InspectorRevalidateDOMTask final |
| : public GarbageCollectedFinalized<InspectorRevalidateDOMTask> { |
| public: |
| explicit InspectorRevalidateDOMTask(InspectorDOMAgent*); |
| void scheduleStyleAttrRevalidationFor(Element*); |
| void reset() { m_timer.stop(); } |
| void onTimer(TimerBase*); |
| DECLARE_TRACE(); |
| |
| private: |
| Member<InspectorDOMAgent> m_domAgent; |
| Timer<InspectorRevalidateDOMTask> m_timer; |
| HeapHashSet<Member<Element>> m_styleAttrInvalidatedElements; |
| }; |
| |
| InspectorRevalidateDOMTask::InspectorRevalidateDOMTask( |
| InspectorDOMAgent* domAgent) |
| : m_domAgent(domAgent), |
| m_timer(this, &InspectorRevalidateDOMTask::onTimer) {} |
| |
| void InspectorRevalidateDOMTask::scheduleStyleAttrRevalidationFor( |
| Element* element) { |
| m_styleAttrInvalidatedElements.add(element); |
| if (!m_timer.isActive()) |
| m_timer.startOneShot(0, BLINK_FROM_HERE); |
| } |
| |
| void InspectorRevalidateDOMTask::onTimer(TimerBase*) { |
| // The timer is stopped on m_domAgent destruction, so this method will never |
| // be called after m_domAgent has been destroyed. |
| HeapVector<Member<Element>> elements; |
| for (auto& attribute : m_styleAttrInvalidatedElements) |
| elements.append(attribute.get()); |
| m_domAgent->styleAttributeInvalidated(elements); |
| m_styleAttrInvalidatedElements.clear(); |
| } |
| |
| DEFINE_TRACE(InspectorRevalidateDOMTask) { |
| visitor->trace(m_domAgent); |
| visitor->trace(m_styleAttrInvalidatedElements); |
| } |
| |
| String InspectorDOMAgent::toErrorString(ExceptionState& exceptionState) { |
| if (exceptionState.hadException()) |
| return DOMException::getErrorName(exceptionState.code()) + " " + |
| exceptionState.message(); |
| return ""; |
| } |
| |
| bool InspectorDOMAgent::getPseudoElementType(PseudoId pseudoId, |
| protocol::DOM::PseudoType* type) { |
| switch (pseudoId) { |
| case PseudoIdFirstLine: |
| *type = protocol::DOM::PseudoTypeEnum::FirstLine; |
| return true; |
| case PseudoIdFirstLetter: |
| *type = protocol::DOM::PseudoTypeEnum::FirstLetter; |
| return true; |
| case PseudoIdBefore: |
| *type = protocol::DOM::PseudoTypeEnum::Before; |
| return true; |
| case PseudoIdAfter: |
| *type = protocol::DOM::PseudoTypeEnum::After; |
| return true; |
| case PseudoIdBackdrop: |
| *type = protocol::DOM::PseudoTypeEnum::Backdrop; |
| return true; |
| case PseudoIdSelection: |
| *type = protocol::DOM::PseudoTypeEnum::Selection; |
| return true; |
| case PseudoIdFirstLineInherited: |
| *type = protocol::DOM::PseudoTypeEnum::FirstLineInherited; |
| return true; |
| case PseudoIdScrollbar: |
| *type = protocol::DOM::PseudoTypeEnum::Scrollbar; |
| return true; |
| case PseudoIdScrollbarThumb: |
| *type = protocol::DOM::PseudoTypeEnum::ScrollbarThumb; |
| return true; |
| case PseudoIdScrollbarButton: |
| *type = protocol::DOM::PseudoTypeEnum::ScrollbarButton; |
| return true; |
| case PseudoIdScrollbarTrack: |
| *type = protocol::DOM::PseudoTypeEnum::ScrollbarTrack; |
| return true; |
| case PseudoIdScrollbarTrackPiece: |
| *type = protocol::DOM::PseudoTypeEnum::ScrollbarTrackPiece; |
| return true; |
| case PseudoIdScrollbarCorner: |
| *type = protocol::DOM::PseudoTypeEnum::ScrollbarCorner; |
| return true; |
| case PseudoIdResizer: |
| *type = protocol::DOM::PseudoTypeEnum::Resizer; |
| return true; |
| case PseudoIdInputListButton: |
| *type = protocol::DOM::PseudoTypeEnum::InputListButton; |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| InspectorDOMAgent::InspectorDOMAgent( |
| v8::Isolate* isolate, |
| InspectedFrames* inspectedFrames, |
| v8_inspector::V8InspectorSession* v8Session, |
| Client* client) |
| : m_isolate(isolate), |
| m_inspectedFrames(inspectedFrames), |
| m_v8Session(v8Session), |
| m_client(client), |
| m_domListener(nullptr), |
| m_documentNodeToIdMap(new NodeToIdMap()), |
| m_lastNodeId(1), |
| m_suppressAttributeModifiedEvent(false), |
| m_backendNodeIdToInspect(0) {} |
| |
| InspectorDOMAgent::~InspectorDOMAgent() {} |
| |
| void InspectorDOMAgent::restore() { |
| if (!enabled()) |
| return; |
| innerEnable(); |
| } |
| |
| HeapVector<Member<Document>> InspectorDOMAgent::documents() { |
| HeapVector<Member<Document>> result; |
| if (m_document) { |
| for (LocalFrame* frame : *m_inspectedFrames) { |
| if (Document* document = frame->document()) |
| result.append(document); |
| } |
| } |
| return result; |
| } |
| |
| void InspectorDOMAgent::setDOMListener(DOMListener* listener) { |
| m_domListener = listener; |
| } |
| |
| void InspectorDOMAgent::setDocument(Document* doc) { |
| if (doc == m_document.get()) |
| return; |
| |
| discardFrontendBindings(); |
| m_document = doc; |
| |
| if (!enabled()) |
| return; |
| |
| // Immediately communicate 0 document or document that has finished loading. |
| if (!doc || !doc->parsing()) |
| frontend()->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); |
| m_idToNodesMap.remove(id); |
| |
| if (node->isFrameOwnerElement()) { |
| Document* contentDocument = |
| toHTMLFrameOwnerElement(node)->contentDocument(); |
| if (m_domListener) |
| m_domListener->didRemoveDocument(contentDocument); |
| if (contentDocument) |
| unbind(contentDocument, nodesMap); |
| } |
| |
| for (ShadowRoot* root = node->youngestShadowRoot(); root; |
| root = root->olderShadowRoot()) |
| unbind(root, nodesMap); |
| |
| if (node->isElementNode()) { |
| Element* element = toElement(node); |
| if (element->pseudoElement(PseudoIdBefore)) |
| unbind(element->pseudoElement(PseudoIdBefore), nodesMap); |
| if (element->pseudoElement(PseudoIdAfter)) |
| unbind(element->pseudoElement(PseudoIdAfter), nodesMap); |
| |
| if (isHTMLLinkElement(*element)) { |
| HTMLLinkElement& linkElement = toHTMLLinkElement(*element); |
| if (linkElement.isImport() && linkElement.import()) |
| unbind(linkElement.import(), 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); |
| } |
| } |
| if (nodesMap == m_documentNodeToIdMap.get()) |
| m_cachedChildCount.remove(id); |
| } |
| |
| Node* InspectorDOMAgent::assertNode(ErrorString* errorString, int nodeId) { |
| Node* node = nodeForId(nodeId); |
| if (!node) { |
| *errorString = "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 (!(node->isDocumentNode())) { |
| *errorString = "Document is not available"; |
| return nullptr; |
| } |
| return toDocument(node); |
| } |
| |
| Element* InspectorDOMAgent::assertElement(ErrorString* errorString, |
| int nodeId) { |
| Node* node = assertNode(errorString, nodeId); |
| if (!node) |
| return nullptr; |
| |
| if (!node->isElementNode()) { |
| *errorString = "Node is not an Element"; |
| return nullptr; |
| } |
| return toElement(node); |
| } |
| |
| // static |
| ShadowRoot* InspectorDOMAgent::userAgentShadowRoot(Node* node) { |
| if (!node || !node->isInShadowTree()) |
| return nullptr; |
| |
| Node* candidate = node; |
| while (candidate && !candidate->isShadowRoot()) |
| candidate = candidate->parentOrShadowHostNode(); |
| ASSERT(candidate); |
| ShadowRoot* shadowRoot = toShadowRoot(candidate); |
| |
| return shadowRoot->type() == ShadowRootType::UserAgent ? shadowRoot : nullptr; |
| } |
| |
| Node* InspectorDOMAgent::assertEditableNode(ErrorString* errorString, |
| int nodeId) { |
| Node* node = assertNode(errorString, nodeId); |
| if (!node) |
| return nullptr; |
| |
| if (node->isInShadowTree()) { |
| if (node->isShadowRoot()) { |
| *errorString = "Cannot edit shadow roots"; |
| return nullptr; |
| } |
| if (userAgentShadowRoot(node)) { |
| *errorString = "Cannot edit nodes from user-agent shadow trees"; |
| return nullptr; |
| } |
| } |
| |
| if (node->isPseudoElement()) { |
| *errorString = "Cannot edit pseudo elements"; |
| return nullptr; |
| } |
| |
| return node; |
| } |
| |
| Node* InspectorDOMAgent::assertEditableChildNode(ErrorString* errorString, |
| Element* parentElement, |
| int nodeId) { |
| Node* node = assertEditableNode(errorString, nodeId); |
| if (!node) |
| return nullptr; |
| if (node->parentNode() != parentElement) { |
| *errorString = "Anchor node must be child of the target element"; |
| return nullptr; |
| } |
| return node; |
| } |
| |
| Element* InspectorDOMAgent::assertEditableElement(ErrorString* errorString, |
| int nodeId) { |
| Element* element = assertElement(errorString, nodeId); |
| if (!element) |
| return nullptr; |
| |
| if (element->isInShadowTree() && userAgentShadowRoot(element)) { |
| *errorString = "Cannot edit elements from user-agent shadow trees"; |
| return nullptr; |
| } |
| |
| if (element->isPseudoElement()) { |
| *errorString = "Cannot edit pseudo elements"; |
| return nullptr; |
| } |
| |
| return element; |
| } |
| |
| void InspectorDOMAgent::innerEnable() { |
| m_state->setBoolean(DOMAgentState::domAgentEnabled, true); |
| m_history = new InspectorHistory(); |
| m_domEditor = new DOMEditor(m_history.get()); |
| m_document = m_inspectedFrames->root()->document(); |
| m_instrumentingAgents->addInspectorDOMAgent(this); |
| if (m_backendNodeIdToInspect) |
| frontend()->inspectNodeRequested(m_backendNodeIdToInspect); |
| m_backendNodeIdToInspect = 0; |
| } |
| |
| void InspectorDOMAgent::enable(ErrorString*) { |
| if (enabled()) |
| return; |
| innerEnable(); |
| } |
| |
| bool InspectorDOMAgent::enabled() const { |
| return m_state->booleanProperty(DOMAgentState::domAgentEnabled, false); |
| } |
| |
| void InspectorDOMAgent::disable(ErrorString* errorString) { |
| if (!enabled()) { |
| if (errorString) |
| *errorString = "DOM agent hasn't been enabled"; |
| return; |
| } |
| m_state->setBoolean(DOMAgentState::domAgentEnabled, false); |
| setSearchingForNode(errorString, NotSearching, |
| Maybe<protocol::DOM::HighlightConfig>()); |
| m_instrumentingAgents->removeInspectorDOMAgent(this); |
| m_history.clear(); |
| m_domEditor.clear(); |
| setDocument(nullptr); |
| } |
| |
| void InspectorDOMAgent::getDocument( |
| ErrorString* errorString, |
| const Maybe<int>& depth, |
| const Maybe<bool>& traverseFrames, |
| std::unique_ptr<protocol::DOM::Node>* root) { |
| // Backward compatibility. Mark agent as enabled when it requests document. |
| if (!enabled()) |
| innerEnable(); |
| |
| if (!m_document) { |
| *errorString = "Document is not available"; |
| return; |
| } |
| |
| discardFrontendBindings(); |
| |
| int sanitizedDepth = depth.fromMaybe(2); |
| if (sanitizedDepth == -1) |
| sanitizedDepth = INT_MAX; |
| |
| *root = buildObjectForNode(m_document.get(), sanitizedDepth, |
| traverseFrames.fromMaybe(false), |
| m_documentNodeToIdMap.get()); |
| } |
| |
| void InspectorDOMAgent::pushChildNodesToFrontend(int nodeId, |
| int depth, |
| bool traverseFrames) { |
| Node* node = nodeForId(nodeId); |
| if (!node || (!node->isElementNode() && !node->isDocumentNode() && |
| !node->isDocumentFragment())) |
| 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, traverseFrames); |
| } |
| |
| return; |
| } |
| |
| std::unique_ptr<protocol::Array<protocol::DOM::Node>> children = |
| buildArrayForContainerChildren(node, depth, traverseFrames, nodeMap); |
| frontend()->setChildNodes(nodeId, std::move(children)); |
| } |
| |
| void InspectorDOMAgent::discardFrontendBindings() { |
| if (m_history) |
| m_history->reset(); |
| m_searchResults.clear(); |
| m_documentNodeToIdMap->clear(); |
| m_idToNode.clear(); |
| m_idToNodesMap.clear(); |
| releaseDanglingNodes(); |
| m_childrenRequested.clear(); |
| m_cachedChildCount.clear(); |
| if (m_revalidateTask) |
| m_revalidateTask->reset(); |
| } |
| |
| Node* InspectorDOMAgent::nodeForId(int id) { |
| if (!id) |
| return nullptr; |
| |
| HeapHashMap<int, Member<Node>>::iterator it = m_idToNode.find(id); |
| if (it != m_idToNode.end()) |
| return it->value; |
| return nullptr; |
| } |
| |
| void InspectorDOMAgent::collectClassNamesFromSubtree( |
| ErrorString* errorString, |
| int nodeId, |
| std::unique_ptr<protocol::Array<String>>* classNames) { |
| HashSet<String> uniqueNames; |
| *classNames = protocol::Array<String>::create(); |
| Node* parentNode = nodeForId(nodeId); |
| if (!parentNode || |
| (!parentNode->isElementNode() && !parentNode->isDocumentNode() && |
| !parentNode->isDocumentFragment())) { |
| *errorString = "No suitable node with given id found"; |
| return; |
| } |
| |
| for (Node* node = parentNode; node; |
| node = FlatTreeTraversal::next(*node, parentNode)) { |
| if (node->isElementNode()) { |
| const Element& element = toElement(*node); |
| if (!element.hasClass()) |
| continue; |
| const SpaceSplitString& classNameList = element.classNames(); |
| for (unsigned i = 0; i < classNameList.size(); ++i) |
| uniqueNames.add(classNameList[i]); |
| } |
| } |
| for (const String& className : uniqueNames) |
| (*classNames)->addItem(className); |
| } |
| |
| void InspectorDOMAgent::requestChildNodes( |
| ErrorString* errorString, |
| int nodeId, |
| const Maybe<int>& depth, |
| const Maybe<bool>& maybeTaverseFrames) { |
| int sanitizedDepth = depth.fromMaybe(1); |
| if (sanitizedDepth == 0 || sanitizedDepth < -1) { |
| *errorString = |
| "Please provide a positive integer as a depth or -1 for entire subtree"; |
| return; |
| } |
| if (sanitizedDepth == -1) |
| sanitizedDepth = INT_MAX; |
| |
| pushChildNodesToFrontend(nodeId, sanitizedDepth, |
| maybeTaverseFrames.fromMaybe(false)); |
| } |
| |
| void InspectorDOMAgent::querySelector(ErrorString* errorString, |
| int nodeId, |
| const String& selectors, |
| int* elementId) { |
| *elementId = 0; |
| Node* node = assertNode(errorString, nodeId); |
| if (!node || !node->isContainerNode()) |
| return; |
| |
| TrackExceptionState exceptionState; |
| Element* element = toContainerNode(node)->querySelector( |
| AtomicString(selectors), exceptionState); |
| if (exceptionState.hadException()) { |
| *errorString = "DOM Error while querying"; |
| return; |
| } |
| |
| if (element) |
| *elementId = pushNodePathToFrontend(element); |
| } |
| |
| void InspectorDOMAgent::querySelectorAll( |
| ErrorString* errorString, |
| int nodeId, |
| const String& selectors, |
| std::unique_ptr<protocol::Array<int>>* result) { |
| Node* node = assertNode(errorString, nodeId); |
| if (!node || !node->isContainerNode()) |
| return; |
| |
| TrackExceptionState exceptionState; |
| StaticElementList* elements = toContainerNode(node)->querySelectorAll( |
| AtomicString(selectors), exceptionState); |
| if (exceptionState.hadException()) { |
| *errorString = "DOM Error while querying"; |
| return; |
| } |
| |
| *result = protocol::Array<int>::create(); |
| |
| for (unsigned i = 0; i < elements->length(); ++i) |
| (*result)->addItem(pushNodePathToFrontend(elements->item(i))); |
| } |
| |
| int InspectorDOMAgent::pushNodePathToFrontend(Node* nodeToPush, |
| NodeToIdMap* nodeMap) { |
| ASSERT(nodeToPush); // Invalid input |
| // InspectorDOMAgent might have been resetted already. See crbug.com/450491 |
| if (!m_document) |
| return 0; |
| if (!m_documentNodeToIdMap->contains(m_document)) |
| return 0; |
| |
| // Return id in case the node is known. |
| int result = nodeMap->get(nodeToPush); |
| if (result) |
| return result; |
| |
| Node* node = nodeToPush; |
| HeapVector<Member<Node>> path; |
| |
| while (true) { |
| Node* parent = innerParentNode(node); |
| if (!parent) |
| return 0; |
| path.append(parent); |
| if (nodeMap->get(parent)) |
| break; |
| node = parent; |
| } |
| |
| for (int i = path.size() - 1; i >= 0; --i) { |
| int nodeId = nodeMap->get(path.at(i).get()); |
| ASSERT(nodeId); |
| pushChildNodesToFrontend(nodeId); |
| } |
| return nodeMap->get(nodeToPush); |
| } |
| |
| int InspectorDOMAgent::pushNodePathToFrontend(Node* nodeToPush) { |
| if (!m_document) |
| return 0; |
| |
| int nodeId = pushNodePathToFrontend(nodeToPush, m_documentNodeToIdMap.get()); |
| if (nodeId) |
| return nodeId; |
| |
| Node* node = nodeToPush; |
| while (Node* parent = innerParentNode(node)) |
| node = parent; |
| |
| // Node being pushed is detached -> push subtree root. |
| NodeToIdMap* newMap = new NodeToIdMap; |
| NodeToIdMap* danglingMap = newMap; |
| m_danglingNodeToIdMaps.append(newMap); |
| std::unique_ptr<protocol::Array<protocol::DOM::Node>> children = |
| protocol::Array<protocol::DOM::Node>::create(); |
| children->addItem(buildObjectForNode(node, 0, false, danglingMap)); |
| frontend()->setChildNodes(0, std::move(children)); |
| |
| return pushNodePathToFrontend(nodeToPush, danglingMap); |
| } |
| |
| int InspectorDOMAgent::boundNodeId(Node* node) { |
| return m_documentNodeToIdMap->get(node); |
| } |
| |
| 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 Maybe<String>& name) { |
| Element* element = assertEditableElement(errorString, elementId); |
| if (!element) |
| return; |
| |
| String markup = "<span " + text + "></span>"; |
| DocumentFragment* fragment = element->document().createDocumentFragment(); |
| |
| bool shouldIgnoreCase = |
| element->document().isHTMLDocument() && element->isHTMLElement(); |
| // Not all elements can represent the context (i.e. IFRAME), hence using |
| // document.body. |
| if (shouldIgnoreCase && element->document().body()) |
| fragment->parseHTML(markup, element->document().body(), |
| AllowScriptingContent); |
| else |
| fragment->parseXML(markup, 0, AllowScriptingContent); |
| |
| Element* parsedElement = |
| fragment->firstChild() && fragment->firstChild()->isElementNode() |
| ? toElement(fragment->firstChild()) |
| : nullptr; |
| if (!parsedElement) { |
| *errorString = "Could not parse value as attributes"; |
| return; |
| } |
| |
| String caseAdjustedName = |
| shouldIgnoreCase ? name.fromMaybe("").lower() : name.fromMaybe(""); |
| |
| AttributeCollection attributes = parsedElement->attributes(); |
| if (attributes.isEmpty() && name.isJust()) { |
| m_domEditor->removeAttribute(element, caseAdjustedName, errorString); |
| return; |
| } |
| |
| bool foundOriginalAttribute = false; |
| for (auto& attribute : attributes) { |
| // Add attribute pair |
| String attributeName = attribute.name().toString(); |
| if (shouldIgnoreCase) |
| attributeName = attributeName.lower(); |
| foundOriginalAttribute |= |
| name.isJust() && attributeName == caseAdjustedName; |
| if (!m_domEditor->setAttribute(element, attributeName, attribute.value(), |
| errorString)) |
| return; |
| } |
| |
| if (!foundOriginalAttribute && name.isJust() && |
| !name.fromJust().stripWhiteSpace().isEmpty()) |
| m_domEditor->removeAttribute(element, caseAdjustedName, 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 = "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; |
| |
| Node* oldNode = nodeForId(nodeId); |
| if (!oldNode || !oldNode->isElementNode()) |
| return; |
| |
| TrackExceptionState exceptionState; |
| Element* newElem = |
| oldNode->document().createElement(AtomicString(tagName), exceptionState); |
| if (exceptionState.hadException()) |
| return; |
| |
| // Copy over the original node's attributes. |
| newElem->cloneAttributesFromElement(*toElement(oldNode)); |
| |
| // Copy over the original node's children. |
| for (Node* child = oldNode->firstChild(); child; |
| child = oldNode->firstChild()) { |
| if (!m_domEditor->insertBefore(newElem, child, 0, errorString)) |
| return; |
| } |
| |
| // Replace the old node with the new node |
| ContainerNode* parent = oldNode->parentNode(); |
| if (!m_domEditor->insertBefore(parent, newElem, oldNode->nextSibling(), |
| errorString)) |
| return; |
| if (!m_domEditor->removeChild(parent, oldNode, errorString)) |
| return; |
| |
| *newId = pushNodePathToFrontend(newElem); |
| 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) { |
| ASSERT(m_document); |
| DOMPatchSupport domPatchSupport(m_domEditor.get(), *m_document.get()); |
| domPatchSupport.patchDocument(outerHTML); |
| return; |
| } |
| |
| Node* node = assertEditableNode(errorString, nodeId); |
| if (!node) |
| return; |
| |
| Document* document = |
| node->isDocumentNode() ? toDocument(node) : node->ownerDocument(); |
| if (!document || |
| (!document->isHTMLDocument() && !document->isXMLDocument())) { |
| *errorString = "Not an HTML/XML document"; |
| return; |
| } |
| |
| Node* newNode = nullptr; |
| 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->getNodeType() != Node::kTextNode) { |
| *errorString = "Can only set value of text nodes"; |
| return; |
| } |
| |
| m_domEditor->replaceWholeText(toText(node), value, errorString); |
| } |
| |
| static Node* nextNodeWithShadowDOMInMind(const Node& current, |
| const Node* stayWithin, |
| bool includeUserAgentShadowDOM) { |
| // At first traverse the subtree. |
| if (current.isElementNode()) { |
| const Element& element = toElement(current); |
| ElementShadow* elementShadow = element.shadow(); |
| if (elementShadow) { |
| ShadowRoot& shadowRoot = elementShadow->youngestShadowRoot(); |
| if (shadowRoot.type() != ShadowRootType::UserAgent || |
| includeUserAgentShadowDOM) |
| return &shadowRoot; |
| } |
| } |
| if (current.hasChildren()) |
| return current.firstChild(); |
| |
| // Then traverse siblings of the node itself and its ancestors. |
| const Node* node = ¤t; |
| do { |
| if (node == stayWithin) |
| return nullptr; |
| if (node->isShadowRoot()) { |
| const ShadowRoot* shadowRoot = toShadowRoot(node); |
| if (shadowRoot->olderShadowRoot()) |
| return shadowRoot->olderShadowRoot(); |
| Element& host = shadowRoot->host(); |
| if (host.hasChildren()) |
| return host.firstChild(); |
| } |
| if (node->nextSibling()) |
| return node->nextSibling(); |
| node = |
| node->isShadowRoot() ? &toShadowRoot(node)->host() : node->parentNode(); |
| } while (node); |
| |
| return nullptr; |
| } |
| |
| void InspectorDOMAgent::performSearch( |
| ErrorString*, |
| const String& whitespaceTrimmedQuery, |
| const Maybe<bool>& optionalIncludeUserAgentShadowDOM, |
| String* searchId, |
| int* resultCount) { |
| // FIXME: Few things are missing here: |
| // 1) Search works with node granularity - number of matches within node is |
| // not calculated. |
| // 2) There is no need to push all search results to the front-end at a time, |
| // pushing next / previous result is sufficient. |
| |
| bool includeUserAgentShadowDOM = |
| optionalIncludeUserAgentShadowDOM.fromMaybe(false); |
| |
| unsigned queryLength = whitespaceTrimmedQuery.length(); |
| bool startTagFound = !whitespaceTrimmedQuery.find('<'); |
| bool endTagFound = whitespaceTrimmedQuery.reverseFind('>') + 1 == queryLength; |
| bool startQuoteFound = !whitespaceTrimmedQuery.find('"'); |
| bool endQuoteFound = |
| whitespaceTrimmedQuery.reverseFind('"') + 1 == queryLength; |
| bool exactAttributeMatch = startQuoteFound && endQuoteFound; |
| |
| String tagNameQuery = whitespaceTrimmedQuery; |
| String attributeQuery = whitespaceTrimmedQuery; |
| if (startTagFound) |
| tagNameQuery = tagNameQuery.right(tagNameQuery.length() - 1); |
| if (endTagFound) |
| tagNameQuery = tagNameQuery.left(tagNameQuery.length() - 1); |
| if (startQuoteFound) |
| attributeQuery = attributeQuery.right(attributeQuery.length() - 1); |
| if (endQuoteFound) |
| attributeQuery = attributeQuery.left(attributeQuery.length() - 1); |
| |
| HeapVector<Member<Document>> docs = documents(); |
| HeapListHashSet<Member<Node>> resultCollector; |
| |
| for (Document* document : docs) { |
| Node* documentElement = document->documentElement(); |
| Node* node = documentElement; |
| if (!node) |
| continue; |
| |
| // Manual plain text search. |
| for (; node; node = nextNodeWithShadowDOMInMind( |
| *node, documentElement, includeUserAgentShadowDOM)) { |
| switch (node->getNodeType()) { |
| case Node::kTextNode: |
| case Node::kCommentNode: |
| case Node::kCdataSectionNode: { |
| String text = node->nodeValue(); |
| if (text.findIgnoringCase(whitespaceTrimmedQuery) != kNotFound) |
| resultCollector.add(node); |
| break; |
| } |
| case Node::kElementNode: { |
| if ((!startTagFound && !endTagFound && |
| (node->nodeName().findIgnoringCase(tagNameQuery) != |
| kNotFound)) || |
| (startTagFound && endTagFound && |
| equalIgnoringCase(node->nodeName(), tagNameQuery)) || |
| (startTagFound && !endTagFound && |
| node->nodeName().startsWith(tagNameQuery, |
| TextCaseInsensitive)) || |
| (!startTagFound && endTagFound && |
| node->nodeName().endsWith(tagNameQuery, TextCaseInsensitive))) { |
| resultCollector.add(node); |
| break; |
| } |
| // Go through all attributes and serialize them. |
| const Element* element = toElement(node); |
| AttributeCollection attributes = element->attributes(); |
| for (auto& attribute : attributes) { |
| // Add attribute pair |
| if (attribute.localName().find(whitespaceTrimmedQuery, 0, |
| TextCaseInsensitive) != kNotFound) { |
| resultCollector.add(node); |
| break; |
| } |
| size_t foundPosition = |
| attribute.value().find(attributeQuery, 0, TextCaseInsensitive); |
| if (foundPosition != kNotFound) { |
| if (!exactAttributeMatch || |
| (!foundPosition && |
| attribute.value().length() == attributeQuery.length())) { |
| resultCollector.add(node); |
| break; |
| } |
| } |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| // XPath evaluation |
| for (Document* document : docs) { |
| ASSERT(document); |
| TrackExceptionState exceptionState; |
| XPathResult* result = DocumentXPathEvaluator::evaluate( |
| *document, whitespaceTrimmedQuery, document, nullptr, |
| XPathResult::kOrderedNodeSnapshotType, ScriptValue(), exceptionState); |
| if (exceptionState.hadException() || !result) |
| continue; |
| |
| unsigned long size = result->snapshotLength(exceptionState); |
| for (unsigned long i = 0; !exceptionState.hadException() && i < size; |
| ++i) { |
| Node* node = result->snapshotItem(i, exceptionState); |
| if (exceptionState.hadException()) |
| break; |
| |
| if (node->getNodeType() == Node::kAttributeNode) |
| node = toAttr(node)->ownerElement(); |
| resultCollector.add(node); |
| } |
| } |
| |
| // Selector evaluation |
| for (Document* document : docs) { |
| TrackExceptionState exceptionState; |
| StaticElementList* elementList = document->querySelectorAll( |
| AtomicString(whitespaceTrimmedQuery), exceptionState); |
| if (exceptionState.hadException() || !elementList) |
| continue; |
| |
| unsigned size = elementList->length(); |
| for (unsigned i = 0; i < size; ++i) |
| resultCollector.add(elementList->item(i)); |
| } |
| } |
| |
| *searchId = IdentifiersFactory::createIdentifier(); |
| HeapVector<Member<Node>>* resultsIt = |
| &m_searchResults.add(*searchId, HeapVector<Member<Node>>()) |
| .storedValue->value; |
| |
| for (auto& result : resultCollector) |
| resultsIt->append(result); |
| |
| *resultCount = resultsIt->size(); |
| } |
| |
| void InspectorDOMAgent::getSearchResults( |
| ErrorString* errorString, |
| const String& searchId, |
| int fromIndex, |
| int toIndex, |
| std::unique_ptr<protocol::Array<int>>* nodeIds) { |
| SearchResults::iterator it = m_searchResults.find(searchId); |
| if (it == m_searchResults.end()) { |
| *errorString = "No search session with given id found"; |
| return; |
| } |
| |
| int size = it->value.size(); |
| if (fromIndex < 0 || toIndex > size || fromIndex >= toIndex) { |
| *errorString = "Invalid search result range"; |
| return; |
| } |
| |
| *nodeIds = 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); |
| } |
| |
| void InspectorDOMAgent::inspect(Node* inspectedNode) { |
| if (!inspectedNode) |
| return; |
| |
| Node* node = inspectedNode; |
| while (node && !node->isElementNode() && !node->isDocumentNode() && |
| !node->isDocumentFragment()) |
| node = node->parentOrShadowHostNode(); |
| if (!node) |
| return; |
| |
| int backendNodeId = DOMNodeIds::idForNode(node); |
| if (!frontend() || !enabled()) { |
| m_backendNodeIdToInspect = backendNodeId; |
| return; |
| } |
| |
| frontend()->inspectNodeRequested(backendNodeId); |
| } |
| |
| void InspectorDOMAgent::nodeHighlightedInOverlay(Node* node) { |
| if (!frontend() || !enabled()) |
| return; |
| |
| while (node && !node->isElementNode() && !node->isDocumentNode() && |
| !node->isDocumentFragment()) |
| node = node->parentOrShadowHostNode(); |
| |
| if (!node) |
| return; |
| |
| int nodeId = pushNodePathToFrontend(node); |
| frontend()->nodeHighlightRequested(nodeId); |
| } |
| |
| void InspectorDOMAgent::setSearchingForNode( |
| ErrorString* errorString, |
| SearchMode searchMode, |
| const Maybe<protocol::DOM::HighlightConfig>& highlightInspectorObject) { |
| if (m_client) |
| m_client->setInspectMode(searchMode, |
| searchMode != NotSearching |
| ? highlightConfigFromInspectorObject( |
| errorString, highlightInspectorObject) |
| : nullptr); |
| } |
| |
| std::unique_ptr<InspectorHighlightConfig> |
| InspectorDOMAgent::highlightConfigFromInspectorObject( |
| ErrorString* errorString, |
| const Maybe<protocol::DOM::HighlightConfig>& highlightInspectorObject) { |
| if (!highlightInspectorObject.isJust()) { |
| *errorString = |
| "Internal error: highlight configuration parameter is missing"; |
| return nullptr; |
| } |
| |
| protocol::DOM::HighlightConfig* config = highlightInspectorObject.fromJust(); |
| std::unique_ptr<InspectorHighlightConfig> highlightConfig = |
| wrapUnique(new InspectorHighlightConfig()); |
| highlightConfig->showInfo = config->getShowInfo(false); |
| highlightConfig->showRulers = config->getShowRulers(false); |
| highlightConfig->showExtensionLines = config->getShowExtensionLines(false); |
| highlightConfig->displayAsMaterial = config->getDisplayAsMaterial(false); |
| highlightConfig->content = parseColor(config->getContentColor(nullptr)); |
| highlightConfig->padding = parseColor(config->getPaddingColor(nullptr)); |
| highlightConfig->border = parseColor(config->getBorderColor(nullptr)); |
| highlightConfig->margin = parseColor(config->getMarginColor(nullptr)); |
| highlightConfig->eventTarget = |
| parseColor(config->getEventTargetColor(nullptr)); |
| highlightConfig->shape = parseColor(config->getShapeColor(nullptr)); |
| highlightConfig->shapeMargin = |
| parseColor(config->getShapeMarginColor(nullptr)); |
| highlightConfig->selectorList = config->getSelectorList(""); |
| |
| return highlightConfig; |
| } |
| |
| void InspectorDOMAgent::setInspectMode( |
| ErrorString* errorString, |
| const String& mode, |
| const Maybe<protocol::DOM::HighlightConfig>& highlightConfig) { |
| SearchMode searchMode; |
| if (mode == protocol::DOM::InspectModeEnum::SearchForNode) { |
| searchMode = SearchingForNormal; |
| } else if (mode == protocol::DOM::InspectModeEnum::SearchForUAShadowDOM) { |
| searchMode = SearchingForUAShadow; |
| } else if (mode == protocol::DOM::InspectModeEnum::None) { |
| searchMode = NotSearching; |
| } else if (mode == protocol::DOM::InspectModeEnum::ShowLayoutEditor) { |
| searchMode = ShowLayoutEditor; |
| } else { |
| *errorString = String("Unknown mode \"" + mode + "\" was provided."); |
| return; |
| } |
| |
| if (searchMode != NotSearching && |
| !pushDocumentUponHandlelessOperation(errorString)) |
| return; |
| |
| setSearchingForNode(errorString, searchMode, highlightConfig); |
| } |
| |
| void InspectorDOMAgent::highlightRect( |
| ErrorString*, |
| int x, |
| int y, |
| int width, |
| int height, |
| const Maybe<protocol::DOM::RGBA>& color, |
| const Maybe<protocol::DOM::RGBA>& outlineColor) { |
| std::unique_ptr<FloatQuad> quad = |
| wrapUnique(new FloatQuad(FloatRect(x, y, width, height))); |
| innerHighlightQuad(std::move(quad), color, outlineColor); |
| } |
| |
| void InspectorDOMAgent::highlightQuad( |
| ErrorString* errorString, |
| std::unique_ptr<protocol::Array<double>> quadArray, |
| const Maybe<protocol::DOM::RGBA>& color, |
| const Maybe<protocol::DOM::RGBA>& outlineColor) { |
| std::unique_ptr<FloatQuad> quad = wrapUnique(new FloatQuad()); |
| if (!parseQuad(std::move(quadArray), quad.get())) { |
| *errorString = "Invalid Quad format"; |
| return; |
| } |
| innerHighlightQuad(std::move(quad), color, outlineColor); |
| } |
| |
| void InspectorDOMAgent::innerHighlightQuad( |
| std::unique_ptr<FloatQuad> quad, |
| const Maybe<protocol::DOM::RGBA>& color, |
| const Maybe<protocol::DOM::RGBA>& outlineColor) { |
| std::unique_ptr<InspectorHighlightConfig> highlightConfig = |
| wrapUnique(new InspectorHighlightConfig()); |
| highlightConfig->content = parseColor(color.fromMaybe(nullptr)); |
| highlightConfig->contentOutline = parseColor(outlineColor.fromMaybe(nullptr)); |
| if (m_client) |
| m_client->highlightQuad(std::move(quad), *highlightConfig); |
| } |
| |
| Node* InspectorDOMAgent::nodeForRemoteId(ErrorString* errorString, |
| const String& objectId) { |
| v8::HandleScope handles(m_isolate); |
| v8::Local<v8::Value> value; |
| v8::Local<v8::Context> context; |
| std::unique_ptr<v8_inspector::StringBuffer> error; |
| if (!m_v8Session->unwrapObject(&error, toV8InspectorStringView(objectId), |
| &value, &context, nullptr)) { |
| *errorString = toCoreString(std::move(error)); |
| return nullptr; |
| } |
| if (!V8Node::hasInstance(value, m_isolate)) { |
| *errorString = "Object id doesn't reference a Node"; |
| return nullptr; |
| } |
| Node* node = V8Node::toImpl(v8::Local<v8::Object>::Cast(value)); |
| if (!node) |
| *errorString = "Couldn't convert object with given objectId to Node"; |
| return node; |
| } |
| |
| void InspectorDOMAgent::highlightNode( |
| ErrorString* errorString, |
| std::unique_ptr<protocol::DOM::HighlightConfig> highlightInspectorObject, |
| const Maybe<int>& nodeId, |
| const Maybe<int>& backendNodeId, |
| const Maybe<String>& objectId) { |
| Node* node = nullptr; |
| if (nodeId.isJust()) { |
| node = assertNode(errorString, nodeId.fromJust()); |
| } else if (backendNodeId.isJust()) { |
| node = DOMNodeIds::nodeForId(backendNodeId.fromJust()); |
| } else if (objectId.isJust()) { |
| node = nodeForRemoteId(errorString, objectId.fromJust()); |
| } else |
| *errorString = "Either nodeId or objectId must be specified"; |
| |
| if (!node) |
| return; |
| |
| std::unique_ptr<InspectorHighlightConfig> highlightConfig = |
| highlightConfigFromInspectorObject(errorString, |
| std::move(highlightInspectorObject)); |
| if (!highlightConfig) |
| return; |
| |
| if (m_client) |
| m_client->highlightNode(node, *highlightConfig, false); |
| } |
| |
| void InspectorDOMAgent::highlightFrame( |
| ErrorString*, |
| const String& frameId, |
| const Maybe<protocol::DOM::RGBA>& color, |
| const Maybe<protocol::DOM::RGBA>& outlineColor) { |
| LocalFrame* frame = IdentifiersFactory::frameById(m_inspectedFrames, frameId); |
| // FIXME: Inspector doesn't currently work cross process. |
| if (frame && frame->deprecatedLocalOwner()) { |
| std::unique_ptr<InspectorHighlightConfig> highlightConfig = |
| wrapUnique(new InspectorHighlightConfig()); |
| highlightConfig->showInfo = true; // Always show tooltips for frames. |
| highlightConfig->content = parseColor(color.fromMaybe(nullptr)); |
| highlightConfig->contentOutline = |
| parseColor(outlineColor.fromMaybe(nullptr)); |
| if (m_client) |
| m_client->highlightNode(frame->deprecatedLocalOwner(), *highlightConfig, |
| false); |
| } |
| } |
| |
| void InspectorDOMAgent::hideHighlight(ErrorString*) { |
| if (m_client) |
| m_client->hideHighlight(); |
| } |
| |
| void InspectorDOMAgent::copyTo(ErrorString* errorString, |
| int nodeId, |
| int targetElementId, |
| const Maybe<int>& anchorNodeId, |
| int* newNodeId) { |
| Node* node = assertEditableNode(errorString, nodeId); |
| if (!node) |
| return; |
| |
| Element* targetElement = assertEditableElement(errorString, targetElementId); |
| if (!targetElement) |
| return; |
| |
| Node* anchorNode = nullptr; |
| if (anchorNodeId.isJust() && anchorNodeId.fromJust()) { |
| anchorNode = assertEditableChildNode(errorString, targetElement, |
| anchorNodeId.fromJust()); |
| if (!anchorNode) |
| return; |
| } |
| |
| // The clone is deep by default. |
| Node* clonedNode = node->cloneNode(true); |
| if (!clonedNode) { |
| *errorString = "Failed to clone node"; |
| return; |
| } |
| if (!m_domEditor->insertBefore(targetElement, clonedNode, anchorNode, |
| errorString)) |
| return; |
| |
| *newNodeId = pushNodePathToFrontend(clonedNode); |
| } |
| |
| void InspectorDOMAgent::moveTo(ErrorString* errorString, |
| int nodeId, |
| int targetElementId, |
| const Maybe<int>& anchorNodeId, |
| int* newNodeId) { |
| Node* node = assertEditableNode(errorString, nodeId); |
| if (!node) |
| return; |
| |
| Element* targetElement = assertEditableElement(errorString, targetElementId); |
| if (!targetElement) |
| return; |
| |
| Node* current = targetElement; |
| while (current) { |
| if (current == node) { |
| *errorString = "Unable to move node into self or descendant"; |
| return; |
| } |
| current = current->parentNode(); |
| } |
| |
| Node* anchorNode = nullptr; |
| if (anchorNodeId.isJust() && anchorNodeId.fromJust()) { |
| anchorNode = assertEditableChildNode(errorString, targetElement, |
| anchorNodeId.fromJust()); |
| if (!anchorNode) |
| return; |
| } |
| |
| if (!m_domEditor->insertBefore(targetElement, node, anchorNode, errorString)) |
| return; |
| |
| *newNodeId = pushNodePathToFrontend(node); |
| } |
| |
| void InspectorDOMAgent::undo(ErrorString* errorString) { |
| TrackExceptionState exceptionState; |
| m_history->undo(exceptionState); |
| *errorString = InspectorDOMAgent::toErrorString(exceptionState); |
| } |
| |
| void InspectorDOMAgent::redo(ErrorString* errorString) { |
| TrackExceptionState exceptionState; |
| m_history->redo(exceptionState); |
| *errorString = InspectorDOMAgent::toErrorString(exceptionState); |
| } |
| |
| void InspectorDOMAgent::markUndoableState(ErrorString*) { |
| m_history->markUndoableState(); |
| } |
| |
| void InspectorDOMAgent::focus(ErrorString* errorString, int nodeId) { |
| Element* element = assertElement(errorString, nodeId); |
| if (!element) |
| return; |
| |
| element->document().updateStyleAndLayoutIgnorePendingStylesheets(); |
| if (!element->isFocusable()) { |
| *errorString = "Element is not focusable"; |
| return; |
| } |
| element->focus(); |
| } |
| |
| void InspectorDOMAgent::setFileInputFiles( |
| ErrorString* errorString, |
| int nodeId, |
| std::unique_ptr<protocol::Array<String>> files) { |
| Node* node = assertNode(errorString, nodeId); |
| if (!node) |
| return; |
| if (!isHTMLInputElement(*node) || |
| toHTMLInputElement(*node).type() != InputTypeNames::file) { |
| *errorString = "Node is not a file input element"; |
| return; |
| } |
| |
| Vector<String> paths; |
| for (size_t index = 0; index < files->length(); ++index) |
| paths.append(files->get(index)); |
| toHTMLInputElement(node)->setFilesFromPaths(paths); |
| } |
| |
| void InspectorDOMAgent::getBoxModel( |
| ErrorString* errorString, |
| int nodeId, |
| std::unique_ptr<protocol::DOM::BoxModel>* model) { |
| Node* node = assertNode(errorString, nodeId); |
| if (!node) |
| return; |
| |
| bool result = InspectorHighlight::getBoxModel(node, model); |
| if (!result) |
| *errorString = "Could not compute box model."; |
| } |
| |
| void InspectorDOMAgent::getNodeForLocation(ErrorString* errorString, |
| int x, |
| int y, |
| int* nodeId) { |
| if (!pushDocumentUponHandlelessOperation(errorString)) |
| return; |
| HitTestRequest request(HitTestRequest::Move | HitTestRequest::ReadOnly | |
| HitTestRequest::AllowChildFrameContent); |
| HitTestResult result(request, IntPoint(x, y)); |
| m_document->frame()->contentLayoutItem().hitTest(result); |
| Node* node = result.innerPossiblyPseudoNode(); |
| while (node && node->getNodeType() == Node::kTextNode) |
| node = node->parentNode(); |
| if (!node) { |
| *errorString = "No node found at given location"; |
| return; |
| } |
| *nodeId = pushNodePathToFrontend(node); |
| } |
| |
| void InspectorDOMAgent::resolveNode( |
| ErrorString* errorString, |
| int nodeId, |
| const Maybe<String>& objectGroup, |
| std::unique_ptr<v8_inspector::protocol::Runtime::API::RemoteObject>* |
| result) { |
| String objectGroupName = objectGroup.fromMaybe(""); |
| Node* node = nodeForId(nodeId); |
| if (!node) { |
| *errorString = "No node with given id found"; |
| return; |
| } |
| *result = resolveNode(node, objectGroupName); |
| if (!*result) |
| *errorString = "Node with given id does not belong to the document"; |
| } |
| |
| void InspectorDOMAgent::getAttributes( |
| ErrorString* errorString, |
| int nodeId, |
| std::unique_ptr<protocol::Array<String>>* result) { |
| Element* element = assertElement(errorString, nodeId); |
| if (!element) |
| return; |
| |
| *result = buildArrayForElementAttributes(element); |
| } |
| |
| void InspectorDOMAgent::requestNode(ErrorString* errorString, |
| const String& objectId, |
| int* nodeId) { |
| Node* node = nodeForRemoteId(errorString, objectId); |
| if (node) |
| *nodeId = pushNodePathToFrontend(node); |
| else |
| *nodeId = 0; |
| } |
| |
| // static |
| String InspectorDOMAgent::documentURLString(Document* document) { |
| if (!document || document->url().isNull()) |
| return ""; |
| return document->url().getString(); |
| } |
| |
| static String documentBaseURLString(Document* document) { |
| return document->baseURLForOverride(document->baseURL()).getString(); |
| } |
| |
| static protocol::DOM::ShadowRootType shadowRootType(ShadowRoot* shadowRoot) { |
| switch (shadowRoot->type()) { |
| case ShadowRootType::UserAgent: |
| return protocol::DOM::ShadowRootTypeEnum::UserAgent; |
| case ShadowRootType::V0: |
| case ShadowRootType::Open: |
| return protocol::DOM::ShadowRootTypeEnum::Open; |
| case ShadowRootType::Closed: |
| return protocol::DOM::ShadowRootTypeEnum::Closed; |
| } |
| ASSERT_NOT_REACHED(); |
| return protocol::DOM::ShadowRootTypeEnum::UserAgent; |
| } |
| |
| std::unique_ptr<protocol::DOM::Node> InspectorDOMAgent::buildObjectForNode( |
| Node* node, |
| int depth, |
| bool traverseFrames, |
| NodeToIdMap* nodesMap) { |
| int id = bind(node, nodesMap); |
| String localName; |
| String nodeValue; |
| |
| switch (node->getNodeType()) { |
| case Node::kTextNode: |
| case Node::kCommentNode: |
| case Node::kCdataSectionNode: |
| nodeValue = node->nodeValue(); |
| if (nodeValue.length() > maxTextSize) |
| nodeValue = nodeValue.left(maxTextSize) + ellipsisUChar; |
| break; |
| case Node::kAttributeNode: |
| localName = toAttr(node)->localName(); |
| break; |
| case Node::kElementNode: |
| localName = toElement(node)->localName(); |
| break; |
| default: |
| break; |
| } |
| |
| std::unique_ptr<protocol::DOM::Node> value = |
| protocol::DOM::Node::create() |
| .setNodeId(id) |
| .setNodeType(static_cast<int>(node->getNodeType())) |
| .setNodeName(node->nodeName()) |
| .setLocalName(localName) |
| .setNodeValue(nodeValue) |
| .build(); |
| |
| bool forcePushChildren = false; |
| if (node->isElementNode()) { |
| Element* element = toElement(node); |
| value->setAttributes(buildArrayForElementAttributes(element)); |
| |
| if (node->isFrameOwnerElement()) { |
| HTMLFrameOwnerElement* frameOwner = toHTMLFrameOwnerElement(node); |
| if (LocalFrame* frame = frameOwner->contentFrame() && |
| frameOwner->contentFrame()->isLocalFrame() |
| ? toLocalFrame(frameOwner->contentFrame()) |
| : nullptr) |
| value->setFrameId(IdentifiersFactory::frameId(frame)); |
| if (Document* doc = frameOwner->contentDocument()) { |
| value->setContentDocument(buildObjectForNode( |
| doc, traverseFrames ? depth : 0, traverseFrames, nodesMap)); |
| } |
| } |
| |
| if (node->parentNode() && node->parentNode()->isDocumentNode()) { |
| LocalFrame* frame = node->document().frame(); |
| if (frame) |
| value->setFrameId(IdentifiersFactory::frameId(frame)); |
| } |
| |
| ElementShadow* shadow = element->shadow(); |
| if (shadow) { |
| std::unique_ptr<protocol::Array<protocol::DOM::Node>> shadowRoots = |
| protocol::Array<protocol::DOM::Node>::create(); |
| for (ShadowRoot* root = &shadow->youngestShadowRoot(); root; |
| root = root->olderShadowRoot()) { |
| shadowRoots->addItem( |
| buildObjectForNode(root, 0, traverseFrames, nodesMap)); |
| } |
| value->setShadowRoots(std::move(shadowRoots)); |
| forcePushChildren = true; |
| } |
| |
| if (isHTMLLinkElement(*element)) { |
| HTMLLinkElement& linkElement = toHTMLLinkElement(*element); |
| if (linkElement.isImport() && linkElement.import() && |
| innerParentNode(linkElement.import()) == linkElement) { |
| value->setImportedDocument(buildObjectForNode( |
| linkElement.import(), 0, traverseFrames, nodesMap)); |
| } |
| forcePushChildren = true; |
| } |
| |
| if (isHTMLTemplateElement(*element)) { |
| value->setTemplateContent( |
| buildObjectForNode(toHTMLTemplateElement(*element).content(), 0, |
| traverseFrames, nodesMap)); |
| forcePushChildren = true; |
| } |
| |
| if (element->getPseudoId()) { |
| protocol::DOM::PseudoType pseudoType; |
| if (InspectorDOMAgent::getPseudoElementType(element->getPseudoId(), |
| &pseudoType)) |
| value->setPseudoType(pseudoType); |
| } else { |
| std::unique_ptr<protocol::Array<protocol::DOM::Node>> pseudoElements = |
| buildArrayForPseudoElements(element, nodesMap); |
| if (pseudoElements) { |
| value->setPseudoElements(std::move(pseudoElements)); |
| forcePushChildren = true; |
| } |
| if (!element->ownerDocument()->xmlVersion().isEmpty()) |
| value->setXmlVersion(element->ownerDocument()->xmlVersion()); |
| } |
| |
| if (element->isInsertionPoint()) { |
| value->setDistributedNodes( |
| buildArrayForDistributedNodes(toInsertionPoint(element))); |
| forcePushChildren = true; |
| } |
| if (isHTMLSlotElement(*element)) { |
| value->setDistributedNodes( |
| buildDistributedNodesForSlot(toHTMLSlotElement(element))); |
| forcePushChildren = true; |
| } |
| } else if (node->isDocumentNode()) { |
| Document* document = toDocument(node); |
| value->setDocumentURL(documentURLString(document)); |
| value->setBaseURL(documentBaseURLString(document)); |
| value->setXmlVersion(document->xmlVersion()); |
| } else if (node->isDocumentTypeNode()) { |
| DocumentType* docType = toDocumentType(node); |
| value->setPublicId(docType->publicId()); |
| value->setSystemId(docType->systemId()); |
| } else if (node->isAttributeNode()) { |
| Attr* attribute = toAttr(node); |
| value->setName(attribute->name()); |
| value->setValue(attribute->value()); |
| } else if (node->isShadowRoot()) { |
| value->setShadowRootType(shadowRootType(toShadowRoot(node))); |
| } |
| |
| if (node->isContainerNode()) { |
| int nodeCount = innerChildNodeCount(node); |
| value->setChildNodeCount(nodeCount); |
| if (nodesMap == m_documentNodeToIdMap) |
| m_cachedChildCount.set(id, nodeCount); |
| if (forcePushChildren && !depth) |
| depth = 1; |
| std::unique_ptr<protocol::Array<protocol::DOM::Node>> children = |
| buildArrayForContainerChildren(node, depth, traverseFrames, nodesMap); |
| if (children->length() > 0 || |
| depth) // Push children along with shadow in any case. |
| value->setChildren(std::move(children)); |
| } |
| |
| return value; |
| } |
| |
| std::unique_ptr<protocol::Array<String>> |
| InspectorDOMAgent::buildArrayForElementAttributes(Element* element) { |
| std::unique_ptr<protocol::Array<String>> attributesValue = |
| protocol::Array<String>::create(); |
| // Go through all attributes and serialize them. |
| AttributeCollection attributes = element->attributes(); |
| for (auto& attribute : attributes) { |
| // Add attribute pair |
| attributesValue->addItem(attribute.name().toString()); |
| attributesValue->addItem(attribute.value()); |
| } |
| return attributesValue; |
| } |
| |
| std::unique_ptr<protocol::Array<protocol::DOM::Node>> |
| InspectorDOMAgent::buildArrayForContainerChildren(Node* container, |
| int depth, |
| bool traverseFrames, |
| NodeToIdMap* nodesMap) { |
| std::unique_ptr<protocol::Array<protocol::DOM::Node>> children = |
| protocol::Array<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->getNodeType() == Node::kTextNode && |
| !firstChild->nextSibling()) { |
| children->addItem( |
| buildObjectForNode(firstChild, 0, traverseFrames, 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, traverseFrames, nodesMap)); |
| child = innerNextSibling(child); |
| } |
| return children; |
| } |
| |
| std::unique_ptr<protocol::Array<protocol::DOM::Node>> |
| InspectorDOMAgent::buildArrayForPseudoElements(Element* element, |
| NodeToIdMap* nodesMap) { |
| if (!element->pseudoElement(PseudoIdBefore) && |
| !element->pseudoElement(PseudoIdAfter)) |
| return nullptr; |
| |
| std::unique_ptr<protocol::Array<protocol::DOM::Node>> pseudoElements = |
| protocol::Array<protocol::DOM::Node>::create(); |
| if (element->pseudoElement(PseudoIdBefore)) { |
| pseudoElements->addItem(buildObjectForNode( |
| element->pseudoElement(PseudoIdBefore), 0, false, nodesMap)); |
| } |
| if (element->pseudoElement(PseudoIdAfter)) { |
| pseudoElements->addItem(buildObjectForNode( |
| element->pseudoElement(PseudoIdAfter), 0, false, nodesMap)); |
| } |
| return pseudoElements; |
| } |
| |
| std::unique_ptr<protocol::Array<protocol::DOM::BackendNode>> |
| InspectorDOMAgent::buildArrayForDistributedNodes( |
| InsertionPoint* insertionPoint) { |
| std::unique_ptr<protocol::Array<protocol::DOM::BackendNode>> |
| distributedNodes = protocol::Array<protocol::DOM::BackendNode>::create(); |
| for (size_t i = 0; i < insertionPoint->distributedNodesSize(); ++i) { |
| Node* distributedNode = insertionPoint->distributedNodeAt(i); |
| if (isWhitespace(distributedNode)) |
| continue; |
| |
| std::unique_ptr<protocol::DOM::BackendNode> backendNode = |
| protocol::DOM::BackendNode::create() |
| .setNodeType(distributedNode->getNodeType()) |
| .setNodeName(distributedNode->nodeName()) |
| .setBackendNodeId(DOMNodeIds::idForNode(distributedNode)) |
| .build(); |
| distributedNodes->addItem(std::move(backendNode)); |
| } |
| return distributedNodes; |
| } |
| |
| std::unique_ptr<protocol::Array<protocol::DOM::BackendNode>> |
| InspectorDOMAgent::buildDistributedNodesForSlot(HTMLSlotElement* slotElement) { |
| std::unique_ptr<protocol::Array<protocol::DOM::BackendNode>> |
| distributedNodes = protocol::Array<protocol::DOM::BackendNode>::create(); |
| for (Node* node = slotElement->firstDistributedNode(); node; |
| node = slotElement->distributedNodeNextTo(*node)) { |
| if (isWhitespace(node)) |
| continue; |
| |
| std::unique_ptr<protocol::DOM::BackendNode> backendNode = |
| protocol::DOM::BackendNode::create() |
| .setNodeType(node->getNodeType()) |
| .setNodeName(node->nodeName()) |
| .setBackendNodeId(DOMNodeIds::idForNode(node)) |
| .build(); |
| distributedNodes->addItem(std::move(backendNode)); |
| } |
| return distributedNodes; |
| } |
| |
| 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) { |
| if (node->isDocumentNode()) { |
| Document* document = toDocument(node); |
| if (HTMLImportLoader* loader = document->importLoader()) |
| return loader->firstImport()->link(); |
| return document->localOwner(); |
| } |
| return node->parentOrShadowHostNode(); |
| } |
| |
| bool InspectorDOMAgent::isWhitespace(Node* node) { |
| // TODO: pull ignoreWhitespace setting from the frontend and use here. |
| return node && node->getNodeType() == Node::kTextNode && |
| node->nodeValue().stripWhiteSpace().length() == 0; |
| } |
| |
| void InspectorDOMAgent::domContentLoadedEventFired(LocalFrame* frame) { |
| if (frame != m_inspectedFrames->root()) |
| return; |
| |
| // Re-push document once it is loaded. |
| discardFrontendBindings(); |
| if (enabled()) |
| frontend()->documentUpdated(); |
| } |
| |
| void InspectorDOMAgent::invalidateFrameOwnerElement(LocalFrame* frame) { |
| HTMLFrameOwnerElement* frameOwner = frame->document()->localOwner(); |
| 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)); |
| frontend()->childNodeRemoved(parentId, frameOwnerId); |
| unbind(frameOwner, m_documentNodeToIdMap.get()); |
| |
| std::unique_ptr<protocol::DOM::Node> value = |
| buildObjectForNode(frameOwner, 0, false, m_documentNodeToIdMap.get()); |
| Node* previousSibling = innerPreviousSibling(frameOwner); |
| int prevId = |
| previousSibling ? m_documentNodeToIdMap->get(previousSibling) : 0; |
| frontend()->childNodeInserted(parentId, prevId, std::move(value)); |
| } |
| |
| void InspectorDOMAgent::didCommitLoad(LocalFrame*, DocumentLoader* loader) { |
| LocalFrame* inspectedFrame = m_inspectedFrames->root(); |
| if (loader->frame() != inspectedFrame) { |
| invalidateFrameOwnerElement(loader->frame()); |
| return; |
| } |
| |
| setDocument(inspectedFrame->document()); |
| } |
| |
| void InspectorDOMAgent::didInsertDOMNode(Node* node) { |
| if (isWhitespace(node)) |
| return; |
| |
| // We could be attaching existing subtree. Forget the bindings. |
| unbind(node, m_documentNodeToIdMap.get()); |
| |
| 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 child count. |
| int count = m_cachedChildCount.get(parentId) + 1; |
| m_cachedChildCount.set(parentId, count); |
| frontend()->childNodeCountUpdated(parentId, count); |
| } else { |
| // Children have been requested -> return value of a new child. |
| Node* prevSibling = innerPreviousSibling(node); |
| int prevId = prevSibling ? m_documentNodeToIdMap->get(prevSibling) : 0; |
| std::unique_ptr<protocol::DOM::Node> value = |
| buildObjectForNode(node, 0, false, m_documentNodeToIdMap.get()); |
| frontend()->childNodeInserted(parentId, prevId, std::move(value)); |
| } |
| } |
| |
| void InspectorDOMAgent::willRemoveDOMNode(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 child count. |
| int count = m_cachedChildCount.get(parentId) - 1; |
| m_cachedChildCount.set(parentId, count); |
| frontend()->childNodeCountUpdated(parentId, count); |
| } else { |
| frontend()->childNodeRemoved(parentId, m_documentNodeToIdMap->get(node)); |
| } |
| unbind(node, m_documentNodeToIdMap.get()); |
| } |
| |
| void InspectorDOMAgent::willModifyDOMAttr(Element*, |
| const AtomicString& oldValue, |
| const AtomicString& newValue) { |
| m_suppressAttributeModifiedEvent = (oldValue == newValue); |
| } |
| |
| void InspectorDOMAgent::didModifyDOMAttr(Element* element, |
| const QualifiedName& 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); |
| |
| frontend()->attributeModified(id, name.toString(), value); |
| } |
| |
| void InspectorDOMAgent::didRemoveDOMAttr(Element* element, |
| const QualifiedName& name) { |
| int id = boundNodeId(element); |
| // If node is not mapped yet -> ignore the event. |
| if (!id) |
| return; |
| |
| if (m_domListener) |
| m_domListener->didModifyDOMAttr(element); |
| |
| frontend()->attributeRemoved(id, name.toString()); |
| } |
| |
| void InspectorDOMAgent::styleAttributeInvalidated( |
| const HeapVector<Member<Element>>& elements) { |
| std::unique_ptr<protocol::Array<int>> nodeIds = |
| 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); |
| } |
| frontend()->inlineStyleInvalidated(std::move(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; |
| } |
| frontend()->characterDataModified(id, characterData->data()); |
| } |
| |
| Member<InspectorRevalidateDOMTask> InspectorDOMAgent::revalidateTask() { |
| if (!m_revalidateTask) |
| m_revalidateTask = new InspectorRevalidateDOMTask(this); |
| return m_revalidateTask.get(); |
| } |
| |
| void InspectorDOMAgent::didInvalidateStyleAttr(Node* node) { |
| int id = m_documentNodeToIdMap->get(node); |
| // If node is not mapped yet -> ignore the event. |
| if (!id) |
| return; |
| |
| revalidateTask()->scheduleStyleAttrRevalidationFor(toElement(node)); |
| } |
| |
| void InspectorDOMAgent::didPushShadowRoot(Element* host, ShadowRoot* root) { |
| if (!host->ownerDocument()) |
| return; |
| |
| int hostId = m_documentNodeToIdMap->get(host); |
| if (!hostId) |
| return; |
| |
| pushChildNodesToFrontend(hostId, 1); |
| frontend()->shadowRootPushed( |
| hostId, buildObjectForNode(root, 0, false, m_documentNodeToIdMap.get())); |
| } |
| |
| void InspectorDOMAgent::willPopShadowRoot(Element* host, ShadowRoot* root) { |
| if (!host->ownerDocument()) |
| return; |
| |
| int hostId = m_documentNodeToIdMap->get(host); |
| int rootId = m_documentNodeToIdMap->get(root); |
| if (hostId && rootId) |
| frontend()->shadowRootPopped(hostId, rootId); |
| } |
| |
| void InspectorDOMAgent::didPerformElementShadowDistribution( |
| Element* shadowHost) { |
| int shadowHostId = m_documentNodeToIdMap->get(shadowHost); |
| if (!shadowHostId) |
| return; |
| |
| for (ShadowRoot* root = shadowHost->youngestShadowRoot(); root; |
| root = root->olderShadowRoot()) { |
| const HeapVector<Member<InsertionPoint>>& insertionPoints = |
| root->descendantInsertionPoints(); |
| for (const auto& it : insertionPoints) { |
| InsertionPoint* insertionPoint = it.get(); |
| int insertionPointId = m_documentNodeToIdMap->get(insertionPoint); |
| if (insertionPointId) |
| frontend()->distributedNodesUpdated( |
| insertionPointId, buildArrayForDistributedNodes(insertionPoint)); |
| } |
| } |
| } |
| |
| void InspectorDOMAgent::didPerformSlotDistribution( |
| HTMLSlotElement* slotElement) { |
| int insertionPointId = m_documentNodeToIdMap->get(slotElement); |
| if (insertionPointId) |
| frontend()->distributedNodesUpdated( |
| insertionPointId, buildDistributedNodesForSlot(slotElement)); |
| } |
| |
| void InspectorDOMAgent::frameDocumentUpdated(LocalFrame* frame) { |
| Document* document = frame->document(); |
| if (!document) |
| return; |
| |
| if (frame != m_inspectedFrames->root()) |
| return; |
| |
| // Only update the main frame document, nested frame document updates are not |
| // required (will be handled by invalidateFrameOwnerElement()). |
| setDocument(document); |
| } |
| |
| void InspectorDOMAgent::pseudoElementCreated(PseudoElement* pseudoElement) { |
| Element* parent = pseudoElement->parentOrShadowHostElement(); |
| if (!parent) |
| return; |
| int parentId = m_documentNodeToIdMap->get(parent); |
| if (!parentId) |
| return; |
| |
| pushChildNodesToFrontend(parentId, 1); |
| frontend()->pseudoElementAdded( |
| parentId, |
| buildObjectForNode(pseudoElement, 0, false, m_documentNodeToIdMap.get())); |
| } |
| |
| void InspectorDOMAgent::pseudoElementDestroyed(PseudoElement* pseudoElement) { |
| int pseudoElementId = m_documentNodeToIdMap->get(pseudoElement); |
| if (!pseudoElementId) |
| return; |
| |
| // If a PseudoElement is bound, its parent element must be bound, too. |
| Element* parent = pseudoElement->parentOrShadowHostElement(); |
| ASSERT(parent); |
| int parentId = m_documentNodeToIdMap->get(parent); |
| ASSERT(parentId); |
| |
| unbind(pseudoElement, m_documentNodeToIdMap.get()); |
| frontend()->pseudoElementRemoved(parentId, pseudoElementId); |
| } |
| |
| static ShadowRoot* shadowRootForNode(Node* node, const String& type) { |
| if (!node->isElementNode()) |
| return nullptr; |
| if (type == "a") |
| return toElement(node)->authorShadowRoot(); |
| if (type == "u") |
| return toElement(node)->userAgentShadowRoot(); |
| return nullptr; |
| } |
| |
| Node* InspectorDOMAgent::nodeForPath(const String& path) { |
| // The path is of form "1,HTML,2,BODY,1,DIV" (<index> and <nodeName> |
| // interleaved). <index> may also be "a" (author shadow root) or "u" |
| // (user-agent shadow root), in which case <nodeName> MUST be |
| // "#document-fragment". |
| if (!m_document) |
| return nullptr; |
| |
| Node* node = m_document.get(); |
| Vector<String> pathTokens; |
| path.split(',', pathTokens); |
| if (!pathTokens.size()) |
| return nullptr; |
| |
| for (size_t i = 0; i < pathTokens.size() - 1; i += 2) { |
| bool success = true; |
| String& indexValue = pathTokens[i]; |
| unsigned childNumber = indexValue.toUInt(&success); |
| Node* child; |
| if (!success) { |
| child = shadowRootForNode(node, indexValue); |
| } else { |
| if (childNumber >= innerChildNodeCount(node)) |
| return nullptr; |
| |
| 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 nullptr; |
| node = child; |
| } |
| return node; |
| } |
| |
| void InspectorDOMAgent::pushNodeByPathToFrontend(ErrorString* errorString, |
| const String& path, |
| int* nodeId) { |
| if (Node* node = nodeForPath(path)) |
| *nodeId = pushNodePathToFrontend(node); |
| else |
| *errorString = "No node with given path found"; |
| } |
| |
| void InspectorDOMAgent::pushNodesByBackendIdsToFrontend( |
| ErrorString* errorString, |
| std::unique_ptr<protocol::Array<int>> backendNodeIds, |
| std::unique_ptr<protocol::Array<int>>* result) { |
| *result = protocol::Array<int>::create(); |
| for (size_t index = 0; index < backendNodeIds->length(); ++index) { |
| Node* node = DOMNodeIds::nodeForId(backendNodeIds->get(index)); |
| if (node && node->document().frame() && |
| m_inspectedFrames->contains(node->document().frame())) |
| (*result)->addItem(pushNodePathToFrontend(node)); |
| else |
| (*result)->addItem(0); |
| } |
| } |
| |
| class InspectableNode final |
| : public v8_inspector::V8InspectorSession::Inspectable { |
| public: |
| explicit InspectableNode(Node* node) |
| : m_nodeId(DOMNodeIds::idForNode(node)) {} |
| |
| v8::Local<v8::Value> get(v8::Local<v8::Context> context) override { |
| return nodeV8Value(context, DOMNodeIds::nodeForId(m_nodeId)); |
| } |
| |
| private: |
| int m_nodeId; |
| }; |
| |
| void InspectorDOMAgent::setInspectedNode(ErrorString* errorString, int nodeId) { |
| Node* node = assertNode(errorString, nodeId); |
| if (!node) |
| return; |
| m_v8Session->addInspectedObject(wrapUnique(new InspectableNode(node))); |
| if (m_client) |
| m_client->setInspectedNode(node); |
| } |
| |
| void InspectorDOMAgent::getRelayoutBoundary(ErrorString* errorString, |
| int nodeId, |
| int* relayoutBoundaryNodeId) { |
| Node* node = assertNode(errorString, nodeId); |
| if (!node) |
| return; |
| LayoutObject* layoutObject = node->layoutObject(); |
| if (!layoutObject) { |
| *errorString = "No layout object for node, perhaps orphan or hidden node"; |
| return; |
| } |
| while (layoutObject && !layoutObject->isDocumentElement() && |
| !layoutObject->isRelayoutBoundaryForInspector()) |
| layoutObject = layoutObject->container(); |
| Node* resultNode = |
| layoutObject ? layoutObject->generatingNode() : node->ownerDocument(); |
| *relayoutBoundaryNodeId = pushNodePathToFrontend(resultNode); |
| } |
| |
| void InspectorDOMAgent::getHighlightObjectForTest( |
| ErrorString* errorString, |
| int nodeId, |
| std::unique_ptr<protocol::DictionaryValue>* result) { |
| Node* node = assertNode(errorString, nodeId); |
| if (!node) |
| return; |
| InspectorHighlight highlight(node, InspectorHighlight::defaultConfig(), true); |
| *result = highlight.asProtocolValue(); |
| } |
| |
| std::unique_ptr<v8_inspector::protocol::Runtime::API::RemoteObject> |
| InspectorDOMAgent::resolveNode(Node* node, const String& objectGroup) { |
| Document* document = |
| node->isDocumentNode() ? &node->document() : node->ownerDocument(); |
| LocalFrame* frame = document ? document->frame() : nullptr; |
| if (!frame) |
| return nullptr; |
| |
| ScriptState* scriptState = ScriptState::forMainWorld(frame); |
| if (!scriptState) |
| return nullptr; |
| |
| ScriptState::Scope scope(scriptState); |
| return m_v8Session->wrapObject(scriptState->context(), |
| nodeV8Value(scriptState->context(), node), |
| toV8InspectorStringView(objectGroup)); |
| } |
| |
| bool InspectorDOMAgent::pushDocumentUponHandlelessOperation( |
| ErrorString* errorString) { |
| if (!m_documentNodeToIdMap->contains(m_document)) { |
| std::unique_ptr<protocol::DOM::Node> root; |
| getDocument(errorString, Maybe<int>(), Maybe<bool>(), &root); |
| return errorString->isEmpty(); |
| } |
| return true; |
| } |
| |
| DEFINE_TRACE(InspectorDOMAgent) { |
| visitor->trace(m_domListener); |
| visitor->trace(m_inspectedFrames); |
| visitor->trace(m_documentNodeToIdMap); |
| visitor->trace(m_danglingNodeToIdMaps); |
| visitor->trace(m_idToNode); |
| visitor->trace(m_idToNodesMap); |
| visitor->trace(m_document); |
| visitor->trace(m_revalidateTask); |
| visitor->trace(m_searchResults); |
| visitor->trace(m_history); |
| visitor->trace(m_domEditor); |
| InspectorBaseAgent::trace(visitor); |
| } |
| |
| } // namespace blink |