/*
 * Copyright (C) 2026 Apple Inc. All rights reserved.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "FrameDOMAgent.h"

#include "Attr.h"
#include "CharacterData.h"
#include "ContainerNode.h"
#include "Document.h"
#include "DocumentInlines.h"
#include "DocumentType.h"
#include "ElementInlines.h"
#include "FrameInlines.h"
#include "HTMLFrameOwnerElement.h"
#include "HTMLNames.h"
#include "HTMLScriptElement.h"
#include "HTMLSlotElement.h"
#include "HTMLStyleElement.h"
#include "HTMLTemplateElement.h"
#include "InspectorDOMAgent.h"
#include "InstrumentingAgents.h"
#include "LocalFrame.h"
#include "LocalFrameInlines.h"
#include "PseudoElement.h"
#include "ShadowRoot.h"
#include "Text.h"
#include "TextNodeTraversal.h"
#include <JavaScriptCore/InspectorProtocolObjects.h>
#include <pal/crypto/CryptoDigest.h>
#include <pal/text/TextEncoding.h>
#include <wtf/TZoneMallocInlines.h>
#include <wtf/text/Base64.h>
#include <wtf/text/MakeString.h>
#include <wtf/unicode/CharacterNames.h>

namespace WebCore {

using namespace Inspector;

WTF_MAKE_TZONE_ALLOCATED_IMPL(FrameDOMAgent);

// FIXME: <https://webkit.org/b/298980> Extract shared tree-building and node-binding logic into a base class shared with InspectorDOMAgent.

static const size_t maxTextSize = 10000;
static const char16_t horizontalEllipsisUTF16[] = { horizontalEllipsis, 0 };

static bool containsOnlyASCIIWhitespace(Node* node)
{
    auto* text = dynamicDowncast<Text>(node);
    return text && text->containsOnlyASCIIWhitespace();
}

static Inspector::Protocol::DOM::ShadowRootType shadowRootType(ShadowRootMode mode)
{
    switch (mode) {
    case ShadowRootMode::UserAgent:
        return Inspector::Protocol::DOM::ShadowRootType::UserAgent;
    case ShadowRootMode::Closed:
        return Inspector::Protocol::DOM::ShadowRootType::Closed;
    case ShadowRootMode::Open:
        return Inspector::Protocol::DOM::ShadowRootType::Open;
    }
    ASSERT_NOT_REACHED();
    return Inspector::Protocol::DOM::ShadowRootType::UserAgent;
}

static Inspector::Protocol::DOM::CustomElementState customElementState(const Element& element)
{
    if (element.isDefinedCustomElement())
        return Inspector::Protocol::DOM::CustomElementState::Custom;
    if (element.isFailedOrPrecustomizedCustomElement())
        return Inspector::Protocol::DOM::CustomElementState::Failed;
    if (element.isCustomElementUpgradeCandidate())
        return Inspector::Protocol::DOM::CustomElementState::Waiting;
    return Inspector::Protocol::DOM::CustomElementState::Builtin;
}

static bool pseudoElementType(PseudoElementType pseudoElementType, Inspector::Protocol::DOM::PseudoType* type)
{
    switch (pseudoElementType) {
    case PseudoElementType::Before:
        *type = Inspector::Protocol::DOM::PseudoType::Before;
        return true;
    case PseudoElementType::After:
        *type = Inspector::Protocol::DOM::PseudoType::After;
        return true;
    default:
        return false;
    }
}

static String computeContentSecurityPolicySHA256Hash(const Element& element)
{
    Ref document = element.document();
    PAL::TextEncoding documentEncoding = document->textEncoding();
    const PAL::TextEncoding& encodingToUse = documentEncoding.isValid() ? documentEncoding : PAL::UTF8Encoding();
    auto content = encodingToUse.encode(TextNodeTraversal::contentsAsString(element), PAL::UnencodableHandling::Entities);
    auto cryptoDigest = PAL::Crypto::CryptoDigest::create(PAL::Crypto::CryptoDigest::Algorithm::SHA_256);
    cryptoDigest->addBytes(content.span());
    auto digest = cryptoDigest->computeHash();
    return makeString("sha256-"_s, base64Encoded(digest));
}

// MARK: - FrameDOMAgent

FrameDOMAgent::FrameDOMAgent(FrameAgentContext& context)
    : InspectorAgentBase("DOM"_s, context)
    , m_frontendDispatcher(makeUniqueRef<Inspector::DOMFrontendDispatcher>(context.frontendRouter))
    , m_backendDispatcher(Inspector::DOMBackendDispatcher::create(Ref { context.backendDispatcher }, this))
    , m_instrumentingAgents(context.instrumentingAgents)
    , m_inspectedFrame(context.inspectedFrame)
    , m_destroyedNodesTimer(*this, &FrameDOMAgent::destroyedNodesTimerFired)
{
}

FrameDOMAgent::~FrameDOMAgent() = default;

void FrameDOMAgent::didCreateFrontendAndBackend()
{
    Ref { m_instrumentingAgents.get() }->setPersistentFrameDOMAgent(this);

    RefPtr frame = m_inspectedFrame.get();
    if (frame)
        m_document = frame->document();
}

void FrameDOMAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason)
{
    Ref { m_instrumentingAgents.get() }->setPersistentFrameDOMAgent(nullptr);
    m_documentRequested = false;
    reset();
}

// MARK: - Node Binding

Inspector::Protocol::DOM::NodeId FrameDOMAgent::bind(Node& node)
{
    return m_nodeToId.ensure(node, [&] {
        auto id = m_lastNodeId++;
        m_idToNode.set(id, node);
        return id;
    }).iterator->value;
}

void FrameDOMAgent::unbind(Node& node)
{
    auto id = m_nodeToId.take(node);
    if (!id)
        return;

    m_idToNode.remove(id);

    if (auto* frameOwner = dynamicDowncast<HTMLFrameOwnerElement>(node)) {
        // FIXME: <https://webkit.org/b/298980> Skip if child frame has its own FrameDOMAgent.
        if (RefPtr contentDocument = frameOwner->contentDocument())
            unbind(*contentDocument);
    }

    if (RefPtr element = dynamicDowncast<Element>(node)) {
        if (RefPtr root = element->shadowRoot())
            unbind(*root);
        if (RefPtr before = element->beforePseudoElement())
            unbind(*before);
        if (RefPtr after = element->afterPseudoElement())
            unbind(*after);
    }

    if (m_childrenRequested.remove(id)) {
        for (RefPtr child = InspectorDOMAgent::innerFirstChild(&node); child; child = InspectorDOMAgent::innerNextSibling(child.get()))
            unbind(*child);
    }
}

Inspector::Protocol::DOM::NodeId FrameDOMAgent::boundNodeId(const Node* node)
{
    if (!node)
        return 0;
    return m_nodeToId.get(*node);
}

Node* FrameDOMAgent::nodeForId(Inspector::Protocol::DOM::NodeId id)
{
    if (!m_idToNode.isValidKey(id))
        return nullptr;
    return m_idToNode.get(id);
}

void FrameDOMAgent::discardBindings()
{
    m_nodeToId.clear();
    m_idToNode.clear();
    m_childrenRequested.clear();
}

RefPtr<Node> FrameDOMAgent::assertNode(Inspector::Protocol::ErrorString& errorString, Inspector::Protocol::DOM::NodeId nodeId)
{
    RefPtr node = nodeForId(nodeId);
    if (!node)
        errorString = "Missing node for given nodeId"_s;
    return node;
}

RefPtr<Element> FrameDOMAgent::assertElement(Inspector::Protocol::ErrorString& errorString, Inspector::Protocol::DOM::NodeId nodeId)
{
    RefPtr node = assertNode(errorString, nodeId);
    if (!node)
        return nullptr;
    RefPtr element = dynamicDowncast<Element>(*node);
    if (!element)
        errorString = "Node for given nodeId is not an element"_s;
    return element;
}

// MARK: - Tree Building

Ref<Inspector::Protocol::DOM::Node> FrameDOMAgent::buildObjectForNode(Node* node, int depth)
{
    auto id = bind(*node);
    String nodeName;
    String localName;
    String nodeValue;

    switch (node->nodeType()) {
    case NodeType::ProcessingInstruction:
        nodeName = node->nodeName();
        localName = node->localName();
        [[fallthrough]];
    case NodeType::Text:
    case NodeType::Comment:
    case NodeType::CDATASection:
        nodeValue = node->nodeValue();
        if (nodeValue.length() > maxTextSize)
            nodeValue = makeString(StringView(nodeValue).left(maxTextSize), horizontalEllipsisUTF16);
        break;
    case NodeType::Attribute:
        localName = node->localName();
        break;
    case NodeType::DocumentFragment:
    case NodeType::Document:
    case NodeType::Element:
    default:
        nodeName = node->nodeName();
        localName = node->localName();
        break;
    }

    auto value = Inspector::Protocol::DOM::Node::create()
        .setNodeId(id)
        .setNodeType(std::to_underlying(node->nodeType()))
        .setNodeName(nodeName)
        .setLocalName(localName)
        .setNodeValue(nodeValue)
        .release();

    if (node->isContainerNode()) {
        int nodeCount = InspectorDOMAgent::innerChildNodeCount(node);
        value->setChildNodeCount(nodeCount);
        auto children = buildArrayForContainerChildren(node, depth);
        if (children->length() > 0)
            value->setChildren(WTF::move(children));
    }

    if (RefPtr element = dynamicDowncast<Element>(*node)) {
        value->setAttributes(buildArrayForElementAttributes(element.get()));

        // FIXME: <https://webkit.org/b/298980> Skip if child frame has its own FrameDOMAgent.
        if (RefPtr frameOwner = dynamicDowncast<HTMLFrameOwnerElement>(*element)) {
            if (RefPtr document = frameOwner->contentDocument())
                value->setContentDocument(buildObjectForNode(document.get(), 0));
        }

        if (RefPtr root = element->shadowRoot()) {
            auto shadowRoots = JSON::ArrayOf<Inspector::Protocol::DOM::Node>::create();
            shadowRoots->addItem(buildObjectForNode(root.get(), 0));
            value->setShadowRoots(WTF::move(shadowRoots));
        }

        if (RefPtr templateElement = dynamicDowncast<HTMLTemplateElement>(*element))
            value->setTemplateContent(buildObjectForNode(protect(templateElement->content()).ptr(), 0));

        if (is<HTMLStyleElement>(element) || (is<HTMLScriptElement>(element) && !element->hasAttributeWithoutSynchronization(HTMLNames::srcAttr)))
            value->setContentSecurityPolicyHash(computeContentSecurityPolicySHA256Hash(*element));

        auto state = customElementState(*element);
        if (state != Inspector::Protocol::DOM::CustomElementState::Builtin)
            value->setCustomElementState(state);

        if (element->pseudoElementIdentifier()) {
            Inspector::Protocol::DOM::PseudoType pseudoType;
            if (pseudoElementType(element->pseudoElementIdentifier()->type, &pseudoType))
                value->setPseudoType(pseudoType);
        } else {
            if (auto pseudoElements = buildArrayForPseudoElements(*element))
                value->setPseudoElements(pseudoElements.releaseNonNull());
        }
    } else if (RefPtr document = dynamicDowncast<Document>(*node)) {
        value->setDocumentURL(InspectorDOMAgent::documentURLString(document.get()));
        // FIXME: <https://webkit.org/b/298980> Set frameId for frame targets to enable frontend frame-to-target association.
    } else if (RefPtr doctype = dynamicDowncast<DocumentType>(*node)) {
        value->setPublicId(doctype->publicId());
        value->setSystemId(doctype->systemId());
    } else if (RefPtr attribute = dynamicDowncast<Attr>(*node)) {
        value->setName(attribute->name());
        value->setValue(attribute->value());
    } else if (RefPtr shadowRoot = dynamicDowncast<ShadowRoot>(*node))
        value->setShadowRootType(shadowRootType(shadowRoot->mode()));

    return value;
}

Ref<JSON::ArrayOf<String>> FrameDOMAgent::buildArrayForElementAttributes(Element* element)
{
    auto attributesValue = JSON::ArrayOf<String>::create();
    if (!element->hasAttributes())
        return attributesValue;
    for (auto& attribute : element->attributes()) {
        attributesValue->addItem(attribute.name().toString());
        attributesValue->addItem(attribute.value());
    }
    return attributesValue;
}

Ref<JSON::ArrayOf<Inspector::Protocol::DOM::Node>> FrameDOMAgent::buildArrayForContainerChildren(Node* container, int depth)
{
    auto children = JSON::ArrayOf<Inspector::Protocol::DOM::Node>::create();
    if (!depth) {
        RefPtr firstChild = container->firstChild();
        if (firstChild && firstChild->nodeType() == NodeType::Text && !firstChild->nextSibling()) {
            children->addItem(buildObjectForNode(firstChild.get(), 0));
            m_childrenRequested.add(bind(*container));
        }
        return children;
    }

    RefPtr child = InspectorDOMAgent::innerFirstChild(container);
    depth--;
    m_childrenRequested.add(bind(*container));

    while (child) {
        children->addItem(buildObjectForNode(child.get(), depth));
        child = InspectorDOMAgent::innerNextSibling(child.get());
    }
    return children;
}

RefPtr<JSON::ArrayOf<Inspector::Protocol::DOM::Node>> FrameDOMAgent::buildArrayForPseudoElements(const Element& element)
{
    RefPtr beforeElement = element.beforePseudoElement();
    RefPtr afterElement = element.afterPseudoElement();
    if (!beforeElement && !afterElement)
        return nullptr;

    auto pseudoElements = JSON::ArrayOf<Inspector::Protocol::DOM::Node>::create();
    if (beforeElement)
        pseudoElements->addItem(buildObjectForNode(beforeElement.get(), 0));
    if (afterElement)
        pseudoElements->addItem(buildObjectForNode(afterElement.get(), 0));
    return pseudoElements;
}

void FrameDOMAgent::pushChildNodesToFrontend(Inspector::Protocol::DOM::NodeId nodeId, int depth)
{
    RefPtr node = nodeForId(nodeId);
    if (!node || (node->nodeType() != NodeType::Element && node->nodeType() != NodeType::Document && node->nodeType() != NodeType::DocumentFragment))
        return;

    if (m_childrenRequested.contains(nodeId)) {
        if (depth <= 1)
            return;

        depth--;
        for (node = InspectorDOMAgent::innerFirstChild(node.get()); node; node = InspectorDOMAgent::innerNextSibling(node.get())) {
            auto childNodeId = boundNodeId(node.get());
            ASSERT(childNodeId);
            pushChildNodesToFrontend(childNodeId, depth);
        }
        return;
    }

    auto children = buildArrayForContainerChildren(node.get(), depth);
    m_frontendDispatcher->setChildNodes(nodeId, WTF::move(children));
}

Inspector::Protocol::DOM::NodeId FrameDOMAgent::pushNodePathToFrontend(Node* nodeToPush)
{
    Inspector::Protocol::ErrorString ignored;
    return pushNodePathToFrontend(ignored, nodeToPush);
}

Inspector::Protocol::DOM::NodeId FrameDOMAgent::pushNodePathToFrontend(Inspector::Protocol::ErrorString& errorString, Node* nodeToPush)
{
    ASSERT(nodeToPush);

    if (!m_document) {
        errorString = "Missing document"_s;
        return 0;
    }

    if (!m_nodeToId.contains(*m_document)) {
        errorString = "Document must have been requested"_s;
        return 0;
    }

    if (auto result = boundNodeId(nodeToPush))
        return result;

    RefPtr node = nodeToPush;
    Vector<Node*> path;

    while (true) {
        RefPtr parent = InspectorDOMAgent::innerParentNode(node.get());
        if (!parent) {
            auto children = JSON::ArrayOf<Inspector::Protocol::DOM::Node>::create();
            children->addItem(buildObjectForNode(node.get(), 0));
            m_frontendDispatcher->setChildNodes(0, WTF::move(children));
            break;
        }

        path.append(parent.get());
        if (boundNodeId(parent.get()))
            break;
        node = parent;
    }

    for (int i = path.size() - 1; i >= 0; --i) {
        RefPtr pathNode = path.at(i);
        auto nodeId = boundNodeId(pathNode.get());
        ASSERT(nodeId);
        pushChildNodesToFrontend(nodeId);
    }
    return boundNodeId(nodeToPush);
}

void FrameDOMAgent::setDocument(Document* document)
{
    if (document == m_document.get())
        return;

    reset();
    m_document = document;

    if (!m_documentRequested)
        return;

    if (!document || !document->parsing())
        m_frontendDispatcher->documentUpdated();
}

void FrameDOMAgent::reset()
{
    discardBindings();
    m_document = nullptr;

    m_destroyedDetachedNodeIdentifiers.clear();
    m_destroyedAttachedNodeIdentifiers.clear();
    if (m_destroyedNodesTimer.isActive())
        m_destroyedNodesTimer.stop();
}

// MARK: - Protocol Commands

Inspector::CommandResult<Ref<Inspector::Protocol::DOM::Node>> FrameDOMAgent::getDocument()
{
    m_documentRequested = true;

    if (!m_document)
        return makeUnexpected("Internal error: missing document"_s);

    RefPtr<Document> document = m_document;
    reset();
    m_document = document;

    return buildObjectForNode(m_document.get(), 2);
}

Inspector::CommandResult<void> FrameDOMAgent::requestChildNodes(int nodeId, std::optional<int>&& depth)
{
    int sanitizedDepth;
    if (!depth)
        sanitizedDepth = 1;
    else if (*depth == -1)
        sanitizedDepth = INT_MAX;
    else if (*depth > 0)
        sanitizedDepth = *depth;
    else
        return makeUnexpected("Unexpected value below -1 for given depth"_s);

    pushChildNodesToFrontend(nodeId, sanitizedDepth);
    return { };
}

Inspector::CommandResult<Ref<JSON::ArrayOf<String>>> FrameDOMAgent::getAttributes(int nodeId)
{
    Inspector::Protocol::ErrorString errorString;
    RefPtr element = assertElement(errorString, nodeId);
    if (!element)
        return makeUnexpected(errorString);

    return buildArrayForElementAttributes(element.get());
}

Inspector::CommandResult<std::optional<int>> FrameDOMAgent::requestAssignedSlot(int nodeId)
{
    Inspector::Protocol::ErrorString errorString;

    RefPtr node = assertNode(errorString, nodeId);
    if (!node)
        return makeUnexpected(errorString);

    RefPtr slotElement = node->assignedSlot();
    if (!slotElement)
        return { std::optional<int>() };

    auto slotElementId = pushNodePathToFrontend(errorString, slotElement.get());
    if (!slotElementId)
        return makeUnexpected(errorString);

    return { std::optional<int>(slotElementId) };
}

Inspector::CommandResult<Ref<JSON::ArrayOf<int>>> FrameDOMAgent::requestAssignedNodes(int slotElementId)
{
    Inspector::Protocol::ErrorString errorString;

    RefPtr node = assertNode(errorString, slotElementId);
    if (!node)
        return makeUnexpected(errorString);

    RefPtr slotElement = dynamicDowncast<HTMLSlotElement>(node);
    if (!slotElement)
        return makeUnexpected("Node for given nodeId is not a slot element"_s);

    auto assignedNodeIds = JSON::ArrayOf<int>::create();
    if (const auto* weakAssignedNodes = slotElement->assignedNodes()) {
        for (const auto& weakAssignedNode : *weakAssignedNodes) {
            if (RefPtr assignedNode = weakAssignedNode.get()) {
                auto assignedNodeId = pushNodePathToFrontend(errorString, assignedNode.get());
                if (!assignedNodeId)
                    return makeUnexpected(errorString);
                assignedNodeIds->addItem(assignedNodeId);
            }
        }
    }
    return assignedNodeIds;
}

// MARK: - InspectorInstrumentation Hooks

void FrameDOMAgent::didInsertDOMNode(Node& node)
{
    if (containsOnlyASCIIWhitespace(&node))
        return;

    unbind(node);

    RefPtr parent = node.parentNode();
    auto parentId = boundNodeId(parent.get());
    if (!parentId)
        return;

    if (!m_childrenRequested.contains(parentId)) {
        // No children are mapped yet -> only notify on changes of hasChildren.
        m_frontendDispatcher->childNodeCountUpdated(parentId, InspectorDOMAgent::innerChildNodeCount(parent.get()));
    } else {
        RefPtr prevSibling = InspectorDOMAgent::innerPreviousSibling(&node);
        auto prevId = boundNodeId(prevSibling.get());
        auto value = buildObjectForNode(&node, 0);
        m_frontendDispatcher->childNodeInserted(parentId, prevId, WTF::move(value));
    }
}

void FrameDOMAgent::didRemoveDOMNode(Node& node)
{
    if (containsOnlyASCIIWhitespace(&node))
        return;

    RefPtr parent = node.parentNode();
    auto parentId = boundNodeId(parent.get());
    if (!parentId)
        return;

    if (!m_childrenRequested.contains(parentId)) {
        if (InspectorDOMAgent::innerChildNodeCount(parent.get()) == 1)
            m_frontendDispatcher->childNodeCountUpdated(parentId, 0);
    } else
        m_frontendDispatcher->childNodeRemoved(parentId, boundNodeId(&node));
    unbind(node);
}

void FrameDOMAgent::willDestroyDOMNode(Node& node)
{
    if (containsOnlyASCIIWhitespace(&node))
        return;

    auto nodeId = m_nodeToId.take(node);
    if (!nodeId)
        return;

    m_idToNode.remove(nodeId);
    m_childrenRequested.remove(nodeId);

    RefPtr parentNode = node.parentNode();
    if (auto parentId = boundNodeId(parentNode.get()))
        m_destroyedAttachedNodeIdentifiers.append({ parentId, nodeId });
    else
        m_destroyedDetachedNodeIdentifiers.append(nodeId);

    if (!m_destroyedNodesTimer.isActive())
        m_destroyedNodesTimer.startOneShot(0_s);
}

void FrameDOMAgent::destroyedNodesTimerFired()
{
    for (auto& [parentId, nodeId] : std::exchange(m_destroyedAttachedNodeIdentifiers, { })) {
        if (!m_childrenRequested.contains(parentId)) {
            RefPtr parent = nodeForId(parentId);
            if (parent && InspectorDOMAgent::innerChildNodeCount(parent.get()) == 1)
                m_frontendDispatcher->childNodeCountUpdated(parentId, 0);
        } else
            m_frontendDispatcher->childNodeRemoved(parentId, nodeId);
    }

    for (auto nodeId : std::exchange(m_destroyedDetachedNodeIdentifiers, { }))
        m_frontendDispatcher->willDestroyDOMNode(nodeId);
}

void FrameDOMAgent::willModifyDOMAttr(Element&, const AtomString& oldValue, const AtomString& newValue)
{
    m_suppressAttributeModifiedEvent = (oldValue == newValue);
}

void FrameDOMAgent::didModifyDOMAttr(Element& element, const AtomString& name, const AtomString& value)
{
    bool shouldSuppressEvent = m_suppressAttributeModifiedEvent;
    m_suppressAttributeModifiedEvent = false;
    if (shouldSuppressEvent)
        return;

    auto id = boundNodeId(&element);
    if (!id)
        return;

    m_frontendDispatcher->attributeModified(id, name, value);
}

void FrameDOMAgent::didRemoveDOMAttr(Element& element, const AtomString& name)
{
    auto id = boundNodeId(&element);
    if (!id)
        return;

    m_frontendDispatcher->attributeRemoved(id, name);
}

void FrameDOMAgent::characterDataModified(CharacterData& characterData)
{
    auto id = boundNodeId(&characterData);
    if (!id) {
        didInsertDOMNode(characterData);
        return;
    }
    m_frontendDispatcher->characterDataModified(id, characterData.data());
}

void FrameDOMAgent::didInvalidateStyleAttr(Element& element)
{
    auto id = boundNodeId(&element);
    if (!id)
        return;

    auto nodeIds = JSON::ArrayOf<Inspector::Protocol::DOM::NodeId>::create();
    nodeIds->addItem(id);
    m_frontendDispatcher->inlineStyleInvalidated(WTF::move(nodeIds));
}

void FrameDOMAgent::didPushShadowRoot(Element& host, ShadowRoot& root)
{
    auto hostId = boundNodeId(&host);
    if (hostId)
        m_frontendDispatcher->shadowRootPushed(hostId, buildObjectForNode(&root, 0));
}

void FrameDOMAgent::willPopShadowRoot(Element& host, ShadowRoot& root)
{
    auto hostId = boundNodeId(&host);
    auto rootId = boundNodeId(&root);
    if (hostId && rootId)
        m_frontendDispatcher->shadowRootPopped(hostId, rootId);
}

void FrameDOMAgent::didChangeCustomElementState(Element& element)
{
    auto elementId = boundNodeId(&element);
    if (!elementId)
        return;

    m_frontendDispatcher->customElementStateChanged(elementId, customElementState(element));
}

void FrameDOMAgent::pseudoElementCreated(PseudoElement& pseudoElement)
{
    RefPtr parent = pseudoElement.hostElement();
    if (!parent)
        return;

    auto parentId = boundNodeId(parent.get());
    if (!parentId)
        return;

    pushChildNodesToFrontend(parentId, 1);
    m_frontendDispatcher->pseudoElementAdded(parentId, buildObjectForNode(&pseudoElement, 0));
}

void FrameDOMAgent::pseudoElementDestroyed(PseudoElement& pseudoElement)
{
    auto pseudoElementId = boundNodeId(&pseudoElement);
    if (!pseudoElementId)
        return;

    RefPtr parent = pseudoElement.hostElement();
    ASSERT(parent);
    auto parentId = boundNodeId(parent.get());
    ASSERT(parentId);

    unbind(pseudoElement);
    m_frontendDispatcher->pseudoElementRemoved(parentId, pseudoElementId);
}

void FrameDOMAgent::frameDocumentUpdated(LocalFrame& frame)
{
    RefPtr inspectedFrame = m_inspectedFrame.get();
    if (!inspectedFrame || &frame != inspectedFrame.get())
        return;

    RefPtr document = frame.document();
    setDocument(document.get());
}

} // namespace WebCore
