blob: 19b82d5098da96d66489c84c5309fc7225b8a725 [file] [log] [blame]
/*
* Copyright (C) 2009-2020 Apple Inc. All rights reserved.
* Copyright (C) 2011 Google Inc. All rights reserved.
* Copyright (C) 2009 Joseph Pecoraro
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "InspectorDOMAgent.h"
#include "AXObjectCache.h"
#include "AccessibilityNodeObject.h"
#include "AddEventListenerOptions.h"
#include "Attr.h"
#include "CSSComputedStyleDeclaration.h"
#include "CSSParser.h"
#include "CSSPropertyNames.h"
#include "CSSPropertySourceData.h"
#include "CSSRule.h"
#include "CSSRuleList.h"
#include "CSSSelector.h"
#include "CSSSelectorList.h"
#include "CSSStyleRule.h"
#include "CSSStyleSheet.h"
#include "CharacterData.h"
#include "CommandLineAPIHost.h"
#include "ComposedTreeIterator.h"
#include "ContainerNode.h"
#include "Cookie.h"
#include "CookieJar.h"
#include "DOMEditor.h"
#include "DOMException.h"
#include "DOMPatchSupport.h"
#include "DOMWindow.h"
#include "DocumentInlines.h"
#include "DocumentType.h"
#include "Editing.h"
#include "Element.h"
#include "Event.h"
#include "EventListener.h"
#include "EventNames.h"
#include "Frame.h"
#include "FrameTree.h"
#include "FrameView.h"
#include "FullscreenManager.h"
#include "HTMLElement.h"
#include "HTMLFrameOwnerElement.h"
#include "HTMLMediaElement.h"
#include "HTMLNames.h"
#include "HTMLParserIdioms.h"
#include "HTMLScriptElement.h"
#include "HTMLStyleElement.h"
#include "HTMLTemplateElement.h"
#include "HTMLVideoElement.h"
#include "HitTestResult.h"
#include "InspectorCSSAgent.h"
#include "InspectorClient.h"
#include "InspectorController.h"
#include "InspectorHistory.h"
#include "InspectorNodeFinder.h"
#include "InspectorPageAgent.h"
#include "InstrumentingAgents.h"
#include "IntRect.h"
#include "JSDOMBindingSecurity.h"
#include "JSDOMWindowCustom.h"
#include "JSEventListener.h"
#include "JSNode.h"
#include "MutationEvent.h"
#include "Node.h"
#include "NodeList.h"
#include "Page.h"
#include "Pasteboard.h"
#include "PseudoElement.h"
#include "RenderGrid.h"
#include "RenderObject.h"
#include "RenderStyle.h"
#include "RenderStyleConstants.h"
#include "ScriptController.h"
#include "SelectorChecker.h"
#include "ShadowRoot.h"
#include "StaticNodeList.h"
#include "StyleProperties.h"
#include "StyleResolver.h"
#include "StyleSheetList.h"
#include "Styleable.h"
#include "Text.h"
#include "TextNodeTraversal.h"
#include "Timer.h"
#include "VideoPlaybackQuality.h"
#include "WebInjectedScriptManager.h"
#include "XPathResult.h"
#include "markup.h"
#include <JavaScriptCore/IdentifiersFactory.h>
#include <JavaScriptCore/InjectedScript.h>
#include <JavaScriptCore/InjectedScriptManager.h>
#include <JavaScriptCore/InspectorDebuggerAgent.h>
#include <JavaScriptCore/JSCInlines.h>
#include <pal/crypto/CryptoDigest.h>
#include <wtf/Function.h>
#include <wtf/text/Base64.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringToIntegerConversion.h>
#include <wtf/text/WTFString.h>
namespace WebCore {
using namespace Inspector;
using namespace HTMLNames;
static const size_t maxTextSize = 10000;
static const UChar ellipsisUChar[] = { 0x2026, 0 };
static std::optional<Color> parseColor(RefPtr<JSON::Object>&& colorObject)
{
if (!colorObject)
return std::nullopt;
auto r = colorObject->getInteger(Protocol::DOM::RGBAColor::rKey);
auto g = colorObject->getInteger(Protocol::DOM::RGBAColor::gKey);
auto b = colorObject->getInteger(Protocol::DOM::RGBAColor::bKey);
if (!r || !g || !b)
return std::nullopt;
auto a = colorObject->getDouble(Protocol::DOM::RGBAColor::aKey);
if (!a)
return { makeFromComponentsClamping<SRGBA<uint8_t>>(*r, *g, *b) };
return { makeFromComponentsClampingExceptAlpha<SRGBA<uint8_t>>(*r, *g, *b, convertFloatAlphaTo<uint8_t>(*a)) };
}
static Color parseConfigColor(const String& fieldName, JSON::Object& configObject)
{
return parseColor(configObject.getObject(fieldName)).value_or(Color::transparentBlack);
}
static bool parseQuad(Ref<JSON::Array>&& quadArray, FloatQuad* quad)
{
const size_t coordinatesInQuad = 8;
double coordinates[coordinatesInQuad];
if (quadArray->length() != coordinatesInQuad)
return false;
for (size_t i = 0; i < coordinatesInQuad; ++i) {
auto coordinate = quadArray->get(i)->asDouble();
if (!coordinate)
return false;
coordinates[i] = *coordinate;
}
quad->setP1(FloatPoint(coordinates[0], coordinates[1]));
quad->setP2(FloatPoint(coordinates[2], coordinates[3]));
quad->setP3(FloatPoint(coordinates[4], coordinates[5]));
quad->setP4(FloatPoint(coordinates[6], coordinates[7]));
return true;
}
class RevalidateStyleAttributeTask {
WTF_MAKE_FAST_ALLOCATED;
public:
RevalidateStyleAttributeTask(InspectorDOMAgent*);
void scheduleFor(Element*);
void reset() { m_timer.stop(); }
void timerFired();
private:
InspectorDOMAgent* m_domAgent;
Timer m_timer;
HashSet<RefPtr<Element>> m_elements;
};
RevalidateStyleAttributeTask::RevalidateStyleAttributeTask(InspectorDOMAgent* domAgent)
: m_domAgent(domAgent)
, m_timer(*this, &RevalidateStyleAttributeTask::timerFired)
{
}
void RevalidateStyleAttributeTask::scheduleFor(Element* element)
{
m_elements.add(element);
if (!m_timer.isActive())
m_timer.startOneShot(0_s);
}
void RevalidateStyleAttributeTask::timerFired()
{
// The timer is stopped on m_domAgent destruction, so this method will never be called after m_domAgent has been destroyed.
Vector<Element*> elements;
for (auto& element : m_elements)
elements.append(element.get());
m_domAgent->styleAttributeInvalidated(elements);
m_elements.clear();
}
class InspectableNode final : public CommandLineAPIHost::InspectableObject {
public:
explicit InspectableNode(Node* node)
: m_node(node)
{
}
JSC::JSValue get(JSC::JSGlobalObject& state) final
{
return InspectorDOMAgent::nodeAsScriptValue(state, m_node.get());
}
private:
RefPtr<Node> m_node;
};
class EventFiredCallback final : public EventListener {
public:
static Ref<EventFiredCallback> create(InspectorDOMAgent& domAgent)
{
return adoptRef(*new EventFiredCallback(domAgent));
}
bool operator==(const EventListener& other) const final
{
return this == &other;
}
void handleEvent(ScriptExecutionContext&, Event& event) final
{
if (!is<Node>(event.target()) || m_domAgent.m_dispatchedEvents.contains(&event))
return;
auto* node = downcast<Node>(event.target());
auto nodeId = m_domAgent.pushNodePathToFrontend(node);
if (!nodeId)
return;
m_domAgent.m_dispatchedEvents.add(&event);
RefPtr<JSON::Object> data = JSON::Object::create();
#if ENABLE(FULLSCREEN_API)
if (event.type() == eventNames().webkitfullscreenchangeEvent)
data->setBoolean("enabled"_s, !!node->document().fullscreenManager().fullscreenElement());
#endif // ENABLE(FULLSCREEN_API)
auto timestamp = m_domAgent.m_environment.executionStopwatch().elapsedTime().seconds();
m_domAgent.m_frontendDispatcher->didFireEvent(nodeId, event.type(), timestamp, data->size() ? WTFMove(data) : nullptr);
}
private:
EventFiredCallback(InspectorDOMAgent& domAgent)
: EventListener(EventListener::CPPEventListenerType)
, m_domAgent(domAgent)
{
}
InspectorDOMAgent& m_domAgent;
};
String InspectorDOMAgent::toErrorString(ExceptionCode ec)
{
return ec ? String(DOMException::name(ec)) : emptyString();
}
String InspectorDOMAgent::toErrorString(Exception&& exception)
{
return DOMException::name(exception.code());
}
InspectorDOMAgent::InspectorDOMAgent(PageAgentContext& context, InspectorOverlay* overlay)
: InspectorAgentBase("DOM"_s, context)
, m_injectedScriptManager(context.injectedScriptManager)
, m_frontendDispatcher(makeUnique<Inspector::DOMFrontendDispatcher>(context.frontendRouter))
, m_backendDispatcher(Inspector::DOMBackendDispatcher::create(context.backendDispatcher, this))
, m_inspectedPage(context.inspectedPage)
, m_overlay(overlay)
, m_destroyedNodesTimer(*this, &InspectorDOMAgent::destroyedNodesTimerFired)
#if ENABLE(VIDEO)
, m_mediaMetricsTimer(*this, &InspectorDOMAgent::mediaMetricsTimerFired)
#endif
{
}
InspectorDOMAgent::~InspectorDOMAgent() = default;
void InspectorDOMAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
{
m_history = makeUnique<InspectorHistory>();
m_domEditor = makeUnique<DOMEditor>(*m_history);
m_instrumentingAgents.setPersistentDOMAgent(this);
m_document = m_inspectedPage.mainFrame().document();
// Force a layout so that we can collect additional information from the layout process.
relayoutDocument();
#if ENABLE(VIDEO)
if (m_document)
addEventListenersToNode(*m_document);
for (auto* mediaElement : HTMLMediaElement::allMediaElements())
addEventListenersToNode(*mediaElement);
#endif
}
void InspectorDOMAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason)
{
m_history.reset();
m_domEditor.reset();
m_nodeToFocus = nullptr;
m_mousedOverNode = nullptr;
m_inspectedNode = nullptr;
Protocol::ErrorString ignored;
setSearchingForNode(ignored, false, nullptr, false);
hideHighlight();
m_overlay->clearAllGridOverlays();
m_overlay->clearAllFlexOverlays();
m_instrumentingAgents.setPersistentDOMAgent(nullptr);
m_documentRequested = false;
reset();
}
Vector<Document*> InspectorDOMAgent::documents()
{
Vector<Document*> result;
for (Frame* frame = m_document->frame(); frame; frame = frame->tree().traverseNext()) {
Document* document = frame->document();
if (!document)
continue;
result.append(document);
}
return result;
}
void InspectorDOMAgent::reset()
{
if (m_history)
m_history->reset();
m_searchResults.clear();
discardBindings();
if (m_revalidateStyleAttrTask)
m_revalidateStyleAttrTask->reset();
m_document = nullptr;
m_destroyedDetachedNodeIdentifiers.clear();
m_destroyedAttachedNodeIdentifiers.clear();
if (m_destroyedNodesTimer.isActive())
m_destroyedNodesTimer.stop();
}
void InspectorDOMAgent::setDocument(Document* document)
{
if (document == m_document.get())
return;
reset();
m_document = document;
// Force a layout so that we can collect additional information from the layout process.
relayoutDocument();
if (!m_documentRequested)
return;
// Immediately communicate null document or document that has finished loading.
if (!document || !document->parsing())
m_frontendDispatcher->documentUpdated();
}
void InspectorDOMAgent::relayoutDocument()
{
if (!m_document)
return;
m_flexibleBoxRendererCachedItemsAtStartOfLine.clear();
m_document->updateLayout();
}
Protocol::DOM::NodeId InspectorDOMAgent::bind(Node& node)
{
return m_nodeToId.ensure(node, [&] {
auto id = m_lastNodeId++;
m_idToNode.set(id, node);
return id;
}).iterator->value;
}
void InspectorDOMAgent::unbind(Node& node)
{
auto id = m_nodeToId.take(node);
if (!id)
return;
m_idToNode.remove(id);
if (node.isFrameOwnerElement()) {
const HTMLFrameOwnerElement* frameOwner = static_cast<const HTMLFrameOwnerElement*>(&node);
if (Document* contentDocument = frameOwner->contentDocument())
unbind(*contentDocument);
}
if (is<Element>(node)) {
Element& element = downcast<Element>(node);
if (ShadowRoot* root = element.shadowRoot())
unbind(*root);
if (PseudoElement* beforeElement = element.beforePseudoElement())
unbind(*beforeElement);
if (PseudoElement* afterElement = element.afterPseudoElement())
unbind(*afterElement);
}
if (auto* cssAgent = m_instrumentingAgents.enabledCSSAgent())
cssAgent->didRemoveDOMNode(node, id);
if (m_childrenRequested.remove(id)) {
// FIXME: Would be better to do this iteratively rather than recursively.
for (Node* child = innerFirstChild(&node); child; child = innerNextSibling(child))
unbind(*child);
}
}
Node* InspectorDOMAgent::assertNode(Protocol::ErrorString& errorString, Protocol::DOM::NodeId nodeId)
{
Node* node = nodeForId(nodeId);
if (!node) {
errorString = "Missing node for given nodeId"_s;
return nullptr;
}
return node;
}
Document* InspectorDOMAgent::assertDocument(Protocol::ErrorString& errorString, Protocol::DOM::NodeId nodeId)
{
Node* node = assertNode(errorString, nodeId);
if (!node)
return nullptr;
if (!is<Document>(node)) {
errorString = "Node for given nodeId is not a document"_s;
return nullptr;
}
return downcast<Document>(node);
}
Element* InspectorDOMAgent::assertElement(Protocol::ErrorString& errorString, Protocol::DOM::NodeId nodeId)
{
Node* node = assertNode(errorString, nodeId);
if (!node)
return nullptr;
if (!is<Element>(node)) {
errorString = "Node for given nodeId is not an element"_s;
return nullptr;
}
return downcast<Element>(node);
}
Node* InspectorDOMAgent::assertEditableNode(Protocol::ErrorString& errorString, Protocol::DOM::NodeId nodeId)
{
Node* node = assertNode(errorString, nodeId);
if (!node)
return nullptr;
if (node->isInUserAgentShadowTree() && !m_allowEditingUserAgentShadowTrees) {
errorString = "Node for given nodeId is in a shadow tree"_s;
return nullptr;
}
if (node->isPseudoElement()) {
errorString = "Node for given nodeId is a pseudo-element"_s;
return nullptr;
}
return node;
}
Element* InspectorDOMAgent::assertEditableElement(Protocol::ErrorString& errorString, Protocol::DOM::NodeId nodeId)
{
Node* node = assertEditableNode(errorString, nodeId);
if (!node)
return nullptr;
if (!is<Element>(node)) {
errorString = "Node for given nodeId is not an element"_s;
return nullptr;
}
return downcast<Element>(node);
}
Protocol::ErrorStringOr<Ref<Protocol::DOM::Node>> InspectorDOMAgent::getDocument()
{
m_documentRequested = true;
if (!m_document)
return makeUnexpected("Internal error: missing document"_s);
// Reset backend state.
RefPtr<Document> document = m_document;
reset();
m_document = document;
auto root = buildObjectForNode(m_document.get(), 2);
if (m_nodeToFocus)
focusNode();
return root;
}
void InspectorDOMAgent::pushChildNodesToFrontend(Protocol::DOM::NodeId nodeId, int depth)
{
Node* node = nodeForId(nodeId);
if (!node || (node->nodeType() != Node::ELEMENT_NODE && node->nodeType() != Node::DOCUMENT_NODE && node->nodeType() != Node::DOCUMENT_FRAGMENT_NODE))
return;
if (m_childrenRequested.contains(nodeId)) {
if (depth <= 1)
return;
depth--;
for (node = innerFirstChild(node); node; node = innerNextSibling(node)) {
auto childNodeId = boundNodeId(node);
ASSERT(childNodeId);
pushChildNodesToFrontend(childNodeId, depth);
}
return;
}
auto children = buildArrayForContainerChildren(node, depth);
m_frontendDispatcher->setChildNodes(nodeId, WTFMove(children));
}
void InspectorDOMAgent::discardBindings()
{
m_nodeToId.clear();
m_idToNode.clear();
m_dispatchedEvents.clear();
m_eventListenerEntries.clear();
m_childrenRequested.clear();
}
static Element* elementToPushForStyleable(const Styleable& styleable)
{
// FIXME: We want to get rid of PseudoElement.
auto* element = &styleable.element;
if (styleable.pseudoId == PseudoId::Before)
element = element->beforePseudoElement();
else if (styleable.pseudoId == PseudoId::After)
element = element->afterPseudoElement();
return element;
}
Protocol::DOM::NodeId InspectorDOMAgent::pushStyleableElementToFrontend(const Styleable& styleable)
{
auto* element = elementToPushForStyleable(styleable);
return pushNodeToFrontend(element ? element : &styleable.element);
}
Protocol::DOM::NodeId InspectorDOMAgent::pushNodeToFrontend(Node* nodeToPush)
{
if (!nodeToPush)
return 0;
// FIXME: <https://webkit.org/b/213499> Web Inspector: allow DOM nodes to be instrumented at any point, regardless of whether the main document has also been instrumented
Protocol::ErrorString ignored;
return pushNodeToFrontend(ignored, boundNodeId(&nodeToPush->document()), nodeToPush);
}
Protocol::DOM::NodeId InspectorDOMAgent::pushNodeToFrontend(Protocol::ErrorString& errorString, Protocol::DOM::NodeId documentNodeId, Node* nodeToPush)
{
Document* document = assertDocument(errorString, documentNodeId);
if (!document)
return 0;
if (&nodeToPush->document() != document) {
errorString = "nodeToPush is not part of the document with given documentNodeId"_s;
return 0;
}
return pushNodePathToFrontend(errorString, nodeToPush);
}
Node* InspectorDOMAgent::nodeForId(Protocol::DOM::NodeId id)
{
if (!m_idToNode.isValidKey(id))
return nullptr;
return m_idToNode.get(id).get();
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::requestChildNodes(Protocol::DOM::NodeId 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 { };
}
Protocol::ErrorStringOr<std::optional<Protocol::DOM::NodeId>> InspectorDOMAgent::querySelector(Protocol::DOM::NodeId nodeId, const String& selector)
{
Protocol::ErrorString errorString;
Node* node = assertNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
if (!is<ContainerNode>(*node))
return makeUnexpected("Node for given nodeId is not a container node"_s);
auto queryResult = downcast<ContainerNode>(*node).querySelector(selector);
if (queryResult.hasException())
return makeUnexpected(InspectorDOMAgent::toErrorString(queryResult.releaseException()));
auto* queryResultNode = queryResult.releaseReturnValue();
if (!queryResultNode)
return { };
auto resultNodeId = pushNodePathToFrontend(errorString, queryResultNode);
if (!resultNodeId)
return makeUnexpected(errorString);
return { resultNodeId };
}
Protocol::ErrorStringOr<Ref<JSON::ArrayOf<Protocol::DOM::NodeId>>> InspectorDOMAgent::querySelectorAll(Protocol::DOM::NodeId nodeId, const String& selector)
{
Protocol::ErrorString errorString;
Node* node = assertNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
if (!is<ContainerNode>(*node))
return makeUnexpected("Node for given nodeId is not a container node"_s);
auto queryResult = downcast<ContainerNode>(*node).querySelectorAll(selector);
if (queryResult.hasException())
return makeUnexpected(InspectorDOMAgent::toErrorString(queryResult.releaseException()));
auto nodes = queryResult.releaseReturnValue();
auto nodeIds = JSON::ArrayOf<Protocol::DOM::NodeId>::create();
for (unsigned i = 0; i < nodes->length(); ++i)
nodeIds->addItem(pushNodePathToFrontend(nodes->item(i)));
return nodeIds;
}
Protocol::DOM::NodeId InspectorDOMAgent::pushNodePathToFrontend(Node* nodeToPush)
{
Protocol::ErrorString ignored;
return pushNodePathToFrontend(ignored, nodeToPush);
}
Ref<Protocol::DOM::Styleable> InspectorDOMAgent::pushStyleablePathToFrontend(Protocol::ErrorString errorString, const Styleable& styleable)
{
auto* element = elementToPushForStyleable(styleable);
auto nodeId = pushNodePathToFrontend(errorString, element ? element : &styleable.element);
auto protocolStyleable = Protocol::DOM::Styleable::create()
.setNodeId(nodeId)
.release();
if (styleable.pseudoId != PseudoId::None) {
if (auto pseudoId = InspectorCSSAgent::protocolValueForPseudoId(styleable.pseudoId))
protocolStyleable->setPseudoId(*pseudoId);
}
return protocolStyleable;
}
Protocol::DOM::NodeId InspectorDOMAgent::pushNodePathToFrontend(Protocol::ErrorString errorString, Node* nodeToPush)
{
ASSERT(nodeToPush); // Invalid input
if (!m_document) {
errorString = "Missing document"_s;
return 0;
}
// FIXME: <https://webkit.org/b/213499> Web Inspector: allow DOM nodes to be instrumented at any point, regardless of whether the main document has also been instrumented
if (!m_nodeToId.contains(*m_document)) {
errorString = "Document must have been requested"_s;
return 0;
}
// Return id in case the node is known.
if (auto result = boundNodeId(nodeToPush))
return result;
Node* node = nodeToPush;
Vector<Node*> path;
while (true) {
Node* parent = innerParentNode(node);
if (!parent) {
// Node being pushed is detached -> push subtree root.
auto children = JSON::ArrayOf<Protocol::DOM::Node>::create();
children->addItem(buildObjectForNode(node, 0));
m_frontendDispatcher->setChildNodes(0, WTFMove(children));
break;
} else {
path.append(parent);
if (boundNodeId(parent))
break;
node = parent;
}
}
for (int i = path.size() - 1; i >= 0; --i) {
auto nodeId = boundNodeId(path.at(i));
ASSERT(nodeId);
pushChildNodesToFrontend(nodeId);
}
return boundNodeId(nodeToPush);
}
Protocol::DOM::NodeId InspectorDOMAgent::boundNodeId(const Node* node)
{
if (!node)
return 0;
return m_nodeToId.get(*node);
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::setAttributeValue(Protocol::DOM::NodeId nodeId, const String& name, const String& value)
{
Protocol::ErrorString errorString;
Element* element = assertEditableElement(errorString, nodeId);
if (!element)
return makeUnexpected(errorString);
if (!m_domEditor->setAttribute(*element, AtomString { name }, AtomString { value }, errorString))
return makeUnexpected(errorString);
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::setAttributesAsText(Protocol::DOM::NodeId nodeId, const String& text, const String& name)
{
Protocol::ErrorString errorString;
Element* element = assertEditableElement(errorString, nodeId);
if (!element)
return makeUnexpected(errorString);
auto parsedElement = createHTMLElement(element->document(), spanTag);
auto result = parsedElement.get().setInnerHTML("<span " + text + "></span>");
if (result.hasException())
return makeUnexpected(InspectorDOMAgent::toErrorString(result.releaseException()));
Node* child = parsedElement->firstChild();
if (!child)
return makeUnexpected("Could not parse given text"_s);
Element* childElement = downcast<Element>(child);
if (!childElement->hasAttributes() && !!name) {
if (!m_domEditor->removeAttribute(*element, AtomString { name }, errorString))
return makeUnexpected(errorString);
return { };
}
bool foundOriginalAttribute = false;
for (const Attribute& attribute : childElement->attributesIterator()) {
// Add attribute pair
auto attributeName = attribute.name().toAtomString();
foundOriginalAttribute = foundOriginalAttribute || attributeName == name;
if (!m_domEditor->setAttribute(*element, attributeName, attribute.value(), errorString))
return makeUnexpected(errorString);
}
if (!foundOriginalAttribute && name.find(isNotSpaceOrNewline) != notFound) {
if (!m_domEditor->removeAttribute(*element, AtomString { name }, errorString))
return makeUnexpected(errorString);
}
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::removeAttribute(Protocol::DOM::NodeId nodeId, const String& name)
{
Protocol::ErrorString errorString;
Element* element = assertEditableElement(errorString, nodeId);
if (!element)
return makeUnexpected(errorString);
if (!m_domEditor->removeAttribute(*element, AtomString { name }, errorString))
return makeUnexpected(errorString);
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::removeNode(Protocol::DOM::NodeId nodeId)
{
Protocol::ErrorString errorString;
Node* node = assertEditableNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
ContainerNode* parentNode = node->parentNode();
if (!parentNode)
return makeUnexpected("Cannot remove detached node"_s);
if (!m_domEditor->removeChild(*parentNode, *node, errorString))
return makeUnexpected(errorString);
return { };
}
Protocol::ErrorStringOr<Protocol::DOM::NodeId> InspectorDOMAgent::setNodeName(Protocol::DOM::NodeId nodeId, const String& tagName)
{
Protocol::ErrorString errorString;
auto oldNode = assertElement(errorString, nodeId);
if (!oldNode)
return makeUnexpected(errorString);
auto createElementResult = oldNode->document().createElementForBindings(AtomString { tagName });
if (createElementResult.hasException())
return makeUnexpected(InspectorDOMAgent::toErrorString(createElementResult.releaseException()));
auto newElement = createElementResult.releaseReturnValue();
// Copy over the original node's attributes.
newElement->cloneAttributesFromElement(*oldNode);
// Copy over the original node's children.
RefPtr<Node> child;
while ((child = oldNode->firstChild())) {
if (!m_domEditor->insertBefore(newElement, *child, 0, errorString))
return makeUnexpected(errorString);
}
// Replace the old node with the new node
RefPtr<ContainerNode> parent = oldNode->parentNode();
if (!m_domEditor->insertBefore(*parent, newElement.copyRef(), oldNode->nextSibling(), errorString))
return makeUnexpected(errorString);
if (!m_domEditor->removeChild(*parent, *oldNode, errorString))
return makeUnexpected(errorString);
auto resultNodeId = pushNodePathToFrontend(errorString, newElement.ptr());
if (m_childrenRequested.contains(nodeId))
pushChildNodesToFrontend(resultNodeId);
return resultNodeId;
}
Protocol::ErrorStringOr<String> InspectorDOMAgent::getOuterHTML(Protocol::DOM::NodeId nodeId)
{
Protocol::ErrorString errorString;
Node* node = assertNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
return serializeFragment(*node, SerializedNodes::SubtreeIncludingNode);
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::setOuterHTML(Protocol::DOM::NodeId nodeId, const String& outerHTML)
{
Protocol::ErrorString errorString;
if (!nodeId) {
DOMPatchSupport { *m_domEditor, *m_document }.patchDocument(outerHTML);
return { };
}
Node* node = assertEditableNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
Document& document = node->document();
if (!document.isHTMLDocument() && !document.isXMLDocument())
return makeUnexpected("Document of node for given nodeId is not HTML/XML"_s);
Node* newNode = nullptr;
if (!m_domEditor->setOuterHTML(*node, outerHTML, newNode, errorString))
return makeUnexpected(errorString);
if (!newNode) {
// The only child node has been deleted.
return { };
}
auto newId = pushNodePathToFrontend(errorString, newNode);
bool childrenRequested = m_childrenRequested.contains(nodeId);
if (childrenRequested)
pushChildNodesToFrontend(newId);
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::insertAdjacentHTML(Protocol::DOM::NodeId nodeId, const String& position, const String& html)
{
Protocol::ErrorString errorString;
Node* node = assertEditableNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
if (!is<Element>(node))
return makeUnexpected("Node for given nodeId is not an element"_s);
if (!m_domEditor->insertAdjacentHTML(downcast<Element>(*node), position, html, errorString))
return makeUnexpected(errorString);
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::setNodeValue(Protocol::DOM::NodeId nodeId, const String& value)
{
Protocol::ErrorString errorString;
Node* node = assertEditableNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
if (!is<Text>(*node))
return makeUnexpected("Node for given nodeId is not text"_s);
if (!m_domEditor->replaceWholeText(downcast<Text>(*node), value, errorString))
return makeUnexpected(errorString);
return { };
}
Protocol::ErrorStringOr<Ref<JSON::ArrayOf<String>>> InspectorDOMAgent::getSupportedEventNames()
{
auto eventNames = JSON::ArrayOf<String>::create();
#define DOM_EVENT_NAMES_ADD(name) eventNames->addItem(#name""_s);
DOM_EVENT_NAMES_FOR_EACH(DOM_EVENT_NAMES_ADD)
#undef DOM_EVENT_NAMES_ADD
return eventNames;
}
#if ENABLE(INSPECTOR_ALTERNATE_DISPATCHERS)
Protocol::ErrorStringOr<Ref<JSON::ArrayOf<Protocol::DOM::DataBinding>>> InspectorDOMAgent::getDataBindingsForNode(Protocol::DOM::NodeId)
{
return makeUnexpected("Not supported"_s);
}
Protocol::ErrorStringOr<String> InspectorDOMAgent::getAssociatedDataForNode(Protocol::DOM::NodeId)
{
return makeUnexpected("Not supported"_s);
}
#endif
Protocol::ErrorStringOr<Ref<JSON::ArrayOf<Protocol::DOM::EventListener>>> InspectorDOMAgent::getEventListenersForNode(Protocol::DOM::NodeId nodeId)
{
Protocol::ErrorString errorString;
auto* node = assertNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
Vector<RefPtr<EventTarget>> ancestors;
ancestors.append(node);
for (auto* ancestor = node->parentOrShadowHostNode(); ancestor; ancestor = ancestor->parentOrShadowHostNode())
ancestors.append(ancestor);
if (auto* window = node->document().domWindow())
ancestors.append(window);
struct EventListenerInfo {
RefPtr<EventTarget> eventTarget;
const AtomString eventType;
const EventListenerVector eventListeners;
};
Vector<EventListenerInfo> eventInformation;
for (size_t i = ancestors.size(); i; --i) {
auto& ancestor = ancestors[i - 1];
for (auto& eventType : ancestor->eventTypes()) {
EventListenerVector filteredListeners;
for (auto& listener : ancestor->eventListeners(eventType)) {
if (listener->callback().type() == EventListener::JSEventListenerType)
filteredListeners.append(listener);
}
if (!filteredListeners.isEmpty())
eventInformation.append({ ancestor, eventType, WTFMove(filteredListeners) });
}
}
auto listeners = JSON::ArrayOf<Protocol::DOM::EventListener>::create();
auto addListener = [&] (RegisteredEventListener& listener, const EventListenerInfo& info) {
Protocol::DOM::EventListenerId identifier = 0;
bool disabled = false;
RefPtr<JSC::Breakpoint> breakpoint;
for (auto& inspectorEventListener : m_eventListenerEntries.values()) {
if (inspectorEventListener.matches(*info.eventTarget, info.eventType, listener.callback(), listener.useCapture())) {
identifier = inspectorEventListener.identifier;
disabled = inspectorEventListener.disabled;
breakpoint = inspectorEventListener.breakpoint;
break;
}
}
if (!identifier) {
InspectorEventListener inspectorEventListener(m_lastEventListenerId++, *info.eventTarget, info.eventType, listener.callback(), listener.useCapture());
identifier = inspectorEventListener.identifier;
disabled = inspectorEventListener.disabled;
breakpoint = inspectorEventListener.breakpoint;
m_eventListenerEntries.add(identifier, inspectorEventListener);
}
listeners->addItem(buildObjectForEventListener(listener, identifier, *info.eventTarget, info.eventType, disabled, breakpoint));
};
// Get Capturing Listeners (in this order)
size_t eventInformationLength = eventInformation.size();
for (auto& info : eventInformation) {
for (auto& listener : info.eventListeners) {
if (listener->useCapture())
addListener(*listener, info);
}
}
// Get Bubbling Listeners (reverse order)
for (size_t i = eventInformationLength; i; --i) {
const EventListenerInfo& info = eventInformation[i - 1];
for (auto& listener : info.eventListeners) {
if (!listener->useCapture())
addListener(*listener, info);
}
}
if (m_inspectedNode == node)
m_suppressEventListenerChangedEvent = false;
return listeners;
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::setEventListenerDisabled(Protocol::DOM::EventListenerId eventListenerId, bool disabled)
{
auto it = m_eventListenerEntries.find(eventListenerId);
if (it == m_eventListenerEntries.end())
return makeUnexpected("Missing event listener for given eventListenerId"_s);
it->value.disabled = disabled;
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::setBreakpointForEventListener(Protocol::DOM::EventListenerId eventListenerId, RefPtr<JSON::Object>&& options)
{
Protocol::ErrorString errorString;
auto it = m_eventListenerEntries.find(eventListenerId);
if (it == m_eventListenerEntries.end())
return makeUnexpected("Missing event listener for given eventListenerId"_s);
if (it->value.breakpoint)
return makeUnexpected("Breakpoint for given eventListenerId already exists"_s);
it->value.breakpoint = InspectorDebuggerAgent::debuggerBreakpointFromPayload(errorString, WTFMove(options));
if (!it->value.breakpoint)
return makeUnexpected(errorString);
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::removeBreakpointForEventListener(Protocol::DOM::EventListenerId eventListenerId)
{
auto it = m_eventListenerEntries.find(eventListenerId);
if (it == m_eventListenerEntries.end())
return makeUnexpected("Missing event listener for given eventListenerId"_s);
if (!it->value.breakpoint)
return makeUnexpected("Breakpoint for given eventListenerId missing"_s);
it->value.breakpoint = nullptr;
return { };
}
Protocol::ErrorStringOr<Ref<Protocol::DOM::AccessibilityProperties>> InspectorDOMAgent::getAccessibilityPropertiesForNode(Protocol::DOM::NodeId nodeId)
{
Protocol::ErrorString errorString;
Node* node = assertNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
return buildObjectForAccessibilityProperties(*node);
}
Protocol::ErrorStringOr<std::tuple<String /* searchId */, int /* resultCount */>> InspectorDOMAgent::performSearch(const String& query, RefPtr<JSON::Array>&& nodeIds, std::optional<bool>&& caseSensitive)
{
Protocol::ErrorString errorString;
// FIXME: Search works with node granularity - number of matches within node is not calculated.
InspectorNodeFinder finder(query, caseSensitive && *caseSensitive);
if (nodeIds) {
for (auto& nodeValue : *nodeIds) {
auto nodeId = nodeValue->asInteger();
if (!nodeId)
return makeUnexpected("Unexpected non-integer item in given nodeIds"_s);
Node* node = assertNode(errorString, *nodeId);
if (!node)
return makeUnexpected(errorString);
finder.performSearch(node);
}
} else {
// There's no need to iterate the frames tree because
// the search helper will go inside the frame owner elements.
finder.performSearch(m_document.get());
}
auto searchId = IdentifiersFactory::createIdentifier();
auto& resultsVector = m_searchResults.add(searchId, Vector<RefPtr<Node>>()).iterator->value;
for (auto& result : finder.results())
resultsVector.append(result);
return { { searchId, resultsVector.size() } };
}
Protocol::ErrorStringOr<Ref<JSON::ArrayOf<Protocol::DOM::NodeId>>> InspectorDOMAgent::getSearchResults(const String& searchId, int fromIndex, int toIndex)
{
SearchResults::iterator it = m_searchResults.find(searchId);
if (it == m_searchResults.end())
return makeUnexpected("Missing search result for given searchId"_s);
int size = it->value.size();
if (fromIndex < 0 || toIndex > size || fromIndex >= toIndex)
return makeUnexpected("Invalid search result range for given fromIndex and toIndex"_s);
auto nodeIds = JSON::ArrayOf<Protocol::DOM::NodeId>::create();
for (int i = fromIndex; i < toIndex; ++i)
nodeIds->addItem(pushNodePathToFrontend((it->value)[i].get()));
return nodeIds;
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::discardSearchResults(const String& searchId)
{
m_searchResults.remove(searchId);
return { };
}
bool InspectorDOMAgent::handleMousePress()
{
if (!m_searchingForNode)
return false;
if (Node* node = m_overlay->highlightedNode()) {
inspect(node);
return true;
}
return false;
}
bool InspectorDOMAgent::handleTouchEvent(Node& node)
{
if (!m_searchingForNode)
return false;
if (m_inspectModeHighlightConfig) {
m_overlay->highlightNode(&node, *m_inspectModeHighlightConfig);
inspect(&node);
return true;
}
return false;
}
void InspectorDOMAgent::inspect(Node* inspectedNode)
{
Protocol::ErrorString ignored;
RefPtr<Node> node = inspectedNode;
setSearchingForNode(ignored, false, nullptr, false);
if (!node->isElementNode() && !node->isDocumentNode())
node = node->parentNode();
m_nodeToFocus = node;
if (!m_nodeToFocus)
return;
focusNode();
}
void InspectorDOMAgent::focusNode()
{
// FIXME: <https://webkit.org/b/213499> Web Inspector: allow DOM nodes to be instrumented at any point, regardless of whether the main document has also been instrumented
if (!m_documentRequested)
return;
ASSERT(m_nodeToFocus);
auto node = std::exchange(m_nodeToFocus, nullptr);
auto frame = node->document().frame();
if (!frame)
return;
auto& globalObject = mainWorldGlobalObject(*frame);
auto injectedScript = m_injectedScriptManager.injectedScriptFor(&globalObject);
if (injectedScript.hasNoValue())
return;
injectedScript.inspectObject(nodeAsScriptValue(globalObject, node.get()));
}
void InspectorDOMAgent::mouseDidMoveOverElement(const HitTestResult& result, unsigned)
{
m_mousedOverNode = result.innerNode();
if (!m_searchingForNode)
return;
highlightMousedOverNode();
}
void InspectorDOMAgent::highlightMousedOverNode()
{
Node* node = m_mousedOverNode.get();
if (node && node->isTextNode())
node = node->parentNode();
if (node && m_inspectModeHighlightConfig)
m_overlay->highlightNode(node, *m_inspectModeHighlightConfig);
}
void InspectorDOMAgent::setSearchingForNode(Protocol::ErrorString& errorString, bool enabled, RefPtr<JSON::Object>&& highlightInspectorObject, bool showRulers)
{
if (m_searchingForNode == enabled)
return;
m_searchingForNode = enabled;
m_overlay->setShowRulersDuringElementSelection(m_searchingForNode && showRulers);
if (m_searchingForNode) {
m_inspectModeHighlightConfig = highlightConfigFromInspectorObject(errorString, WTFMove(highlightInspectorObject));
if (!m_inspectModeHighlightConfig)
return;
highlightMousedOverNode();
} else
hideHighlight();
m_overlay->didSetSearchingForNode(m_searchingForNode);
if (InspectorClient* client = m_inspectedPage.inspectorController().inspectorClient())
client->elementSelectionChanged(m_searchingForNode);
}
std::unique_ptr<InspectorOverlay::Highlight::Config> InspectorDOMAgent::highlightConfigFromInspectorObject(Protocol::ErrorString& errorString, RefPtr<JSON::Object>&& highlightInspectorObject)
{
if (!highlightInspectorObject) {
errorString = "Internal error: highlight configuration parameter is missing"_s;
return nullptr;
}
auto highlightConfig = makeUnique<InspectorOverlay::Highlight::Config>();
highlightConfig->showInfo = highlightInspectorObject->getBoolean(Protocol::DOM::HighlightConfig::showInfoKey).value_or(false);
highlightConfig->content = parseConfigColor(Protocol::DOM::HighlightConfig::contentColorKey, *highlightInspectorObject);
highlightConfig->padding = parseConfigColor(Protocol::DOM::HighlightConfig::paddingColorKey, *highlightInspectorObject);
highlightConfig->border = parseConfigColor(Protocol::DOM::HighlightConfig::borderColorKey, *highlightInspectorObject);
highlightConfig->margin = parseConfigColor(Protocol::DOM::HighlightConfig::marginColorKey, *highlightInspectorObject);
return highlightConfig;
}
#if PLATFORM(IOS_FAMILY)
Protocol::ErrorStringOr<void> InspectorDOMAgent::setInspectModeEnabled(bool enabled, RefPtr<JSON::Object>&& highlightConfig)
{
Protocol::ErrorString errorString;
setSearchingForNode(errorString, enabled, WTFMove(highlightConfig), false);
if (!!errorString)
return makeUnexpected(errorString);
return { };
}
#else
Protocol::ErrorStringOr<void> InspectorDOMAgent::setInspectModeEnabled(bool enabled, RefPtr<JSON::Object>&& highlightConfig, std::optional<bool>&& showRulers)
{
Protocol::ErrorString errorString;
setSearchingForNode(errorString, enabled, WTFMove(highlightConfig), showRulers && *showRulers);
if (!!errorString)
return makeUnexpected(errorString);
return { };
}
#endif
Protocol::ErrorStringOr<void> InspectorDOMAgent::highlightRect(int x, int y, int width, int height, RefPtr<JSON::Object>&& color, RefPtr<JSON::Object>&& outlineColor, std::optional<bool>&& usePageCoordinates)
{
auto quad = makeUnique<FloatQuad>(FloatRect(x, y, width, height));
innerHighlightQuad(WTFMove(quad), WTFMove(color), WTFMove(outlineColor), WTFMove(usePageCoordinates));
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::highlightQuad(Ref<JSON::Array>&& quadObject, RefPtr<JSON::Object>&& color, RefPtr<JSON::Object>&& outlineColor, std::optional<bool>&& usePageCoordinates)
{
auto quad = makeUnique<FloatQuad>();
if (!parseQuad(WTFMove(quadObject), quad.get()))
return makeUnexpected("Unexpected invalid quad"_s);
innerHighlightQuad(WTFMove(quad), WTFMove(color), WTFMove(outlineColor), WTFMove(usePageCoordinates));
return { };
}
void InspectorDOMAgent::innerHighlightQuad(std::unique_ptr<FloatQuad> quad, RefPtr<JSON::Object>&& color, RefPtr<JSON::Object>&& outlineColor, std::optional<bool>&& usePageCoordinates)
{
auto highlightConfig = makeUnique<InspectorOverlay::Highlight::Config>();
highlightConfig->content = parseColor(WTFMove(color)).value_or(Color::transparentBlack);
highlightConfig->contentOutline = parseColor(WTFMove(outlineColor)).value_or(Color::transparentBlack);
highlightConfig->usePageCoordinates = usePageCoordinates ? *usePageCoordinates : false;
m_overlay->highlightQuad(WTFMove(quad), *highlightConfig);
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::highlightSelector(Ref<JSON::Object>&& highlightInspectorObject, const String& selectorString, const Protocol::Network::FrameId& frameId)
{
Protocol::ErrorString errorString;
auto highlightConfig = highlightConfigFromInspectorObject(errorString, WTFMove(highlightInspectorObject));
if (!highlightConfig)
return makeUnexpected(errorString);
RefPtr<Document> document;
if (!!frameId) {
auto* pageAgent = m_instrumentingAgents.enabledPageAgent();
if (!pageAgent)
return makeUnexpected("Page domain must be enabled"_s);
auto* frame = pageAgent->assertFrame(errorString, frameId);
if (!frame)
return makeUnexpected(errorString);
document = frame->document();
} else
document = m_document;
if (!document)
return makeUnexpected("Missing document of frame for given frameId"_s);
CSSParser parser(*document);
auto selectorList = parser.parseSelector(selectorString);
if (!selectorList)
return { };
SelectorChecker selectorChecker(*document);
Vector<Ref<Node>> nodeList;
HashSet<Ref<Node>> seenNodes;
for (auto& descendant : composedTreeDescendants(*document)) {
if (!is<Element>(descendant))
continue;
auto& descendantElement = downcast<Element>(descendant);
auto isInUserAgentShadowTree = descendantElement.isInUserAgentShadowTree();
auto pseudoId = descendantElement.pseudoId();
auto& pseudo = descendantElement.pseudo();
for (const auto* selector = selectorList->first(); selector; selector = CSSSelectorList::next(selector)) {
if (isInUserAgentShadowTree && (selector->match() != CSSSelector::PseudoElement || selector->value() != pseudo))
continue;
SelectorChecker::CheckingContext context(SelectorChecker::Mode::ResolvingStyle);
context.pseudoId = pseudoId;
if (selectorChecker.match(*selector, descendantElement, context)) {
if (seenNodes.add(descendantElement))
nodeList.append(descendantElement);
}
if (context.pseudoIDSet) {
auto pseudoIDs = PseudoIdSet::fromMask(context.pseudoIDSet.data());
if (pseudoIDs.has(PseudoId::Before)) {
pseudoIDs.remove(PseudoId::Before);
if (auto* beforePseudoElement = descendantElement.beforePseudoElement()) {
if (seenNodes.add(*beforePseudoElement))
nodeList.append(*beforePseudoElement);
}
}
if (pseudoIDs.has(PseudoId::After)) {
pseudoIDs.remove(PseudoId::After);
if (auto* afterPseudoElement = descendantElement.afterPseudoElement()) {
if (seenNodes.add(*afterPseudoElement))
nodeList.append(*afterPseudoElement);
}
}
if (pseudoIDs) {
if (seenNodes.add(descendantElement))
nodeList.append(descendantElement);
}
}
}
}
m_overlay->highlightNodeList(StaticNodeList::create(WTFMove(nodeList)), *highlightConfig);
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::highlightNode(Ref<JSON::Object>&& highlightInspectorObject, std::optional<Protocol::DOM::NodeId>&& nodeId, const Protocol::Runtime::RemoteObjectId& objectId)
{
Protocol::ErrorString errorString;
Node* node = nullptr;
if (nodeId)
node = assertNode(errorString, *nodeId);
else if (!!objectId) {
node = nodeForObjectId(objectId);
errorString = "Missing node for given objectId"_s;
} else
errorString = "Either nodeId or objectId must be specified"_s;
if (!node)
return makeUnexpected(errorString);
std::unique_ptr<InspectorOverlay::Highlight::Config> highlightConfig = highlightConfigFromInspectorObject(errorString, WTFMove(highlightInspectorObject));
if (!highlightConfig)
return makeUnexpected(errorString);
m_overlay->highlightNode(node, *highlightConfig);
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::highlightNodeList(Ref<JSON::Array>&& nodeIds, Ref<JSON::Object>&& highlightInspectorObject)
{
Protocol::ErrorString errorString;
Vector<Ref<Node>> nodes;
for (auto& nodeIdValue : nodeIds.get()) {
auto nodeId = nodeIdValue->asInteger();
if (!nodeId)
return makeUnexpected("Unexpected non-integer item in given nodeIds"_s);
// In the case that a node is removed in the time between when highlightNodeList is invoked
// by the frontend and it is executed by the backend, we should still attempt to highlight
// as many nodes as possible. As such, we should ignore any errors generated when attempting
// to get a Node from a given nodeId.
Protocol::ErrorString ignored;
Node* node = assertNode(ignored, *nodeId);
if (!node)
continue;
nodes.append(*node);
}
std::unique_ptr<InspectorOverlay::Highlight::Config> highlightConfig = highlightConfigFromInspectorObject(errorString, WTFMove(highlightInspectorObject));
if (!highlightConfig)
return makeUnexpected(errorString);
m_overlay->highlightNodeList(StaticNodeList::create(WTFMove(nodes)), *highlightConfig);
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::highlightFrame(const Protocol::Network::FrameId& frameId, RefPtr<JSON::Object>&& color, RefPtr<JSON::Object>&& outlineColor)
{
Protocol::ErrorString errorString;
auto* pageAgent = m_instrumentingAgents.enabledPageAgent();
if (!pageAgent)
return makeUnexpected("Page domain must be enabled"_s);
auto* frame = pageAgent->assertFrame(errorString, frameId);
if (!frame)
return makeUnexpected(errorString);
if (frame->ownerElement()) {
auto highlightConfig = makeUnique<InspectorOverlay::Highlight::Config>();
highlightConfig->showInfo = true; // Always show tooltips for frames.
highlightConfig->content = parseColor(WTFMove(color)).value_or(Color::transparentBlack);
highlightConfig->contentOutline = parseColor(WTFMove(outlineColor)).value_or(Color::transparentBlack);
m_overlay->highlightNode(frame->ownerElement(), *highlightConfig);
}
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::hideHighlight()
{
m_overlay->hideHighlight();
return { };
}
Inspector::Protocol::ErrorStringOr<void> InspectorDOMAgent::showGridOverlay(Inspector::Protocol::DOM::NodeId nodeId, Ref<JSON::Object>&& gridColor, std::optional<bool>&& showLineNames, std::optional<bool>&& showLineNumbers, std::optional<bool>&& showExtendedGridLines, std::optional<bool>&& showTrackSizes, std::optional<bool>&& showAreaNames)
{
Protocol::ErrorString errorString;
Node* node = assertNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
auto parsedColor = parseColor(WTFMove(gridColor));
if (!parsedColor)
return makeUnexpected("Invalid color could not be parsed."_s);
InspectorOverlay::Grid::Config config;
config.gridColor = *parsedColor;
config.showLineNames = showLineNames.value_or(false);
config.showLineNumbers = showLineNumbers.value_or(false);
config.showExtendedGridLines = showExtendedGridLines.value_or(false);
config.showTrackSizes = showTrackSizes.value_or(false);
config.showAreaNames = showAreaNames.value_or(false);
m_overlay->setGridOverlayForNode(*node, config);
return { };
}
Inspector::Protocol::ErrorStringOr<void> InspectorDOMAgent::hideGridOverlay(std::optional<Protocol::DOM::NodeId>&& nodeId)
{
if (nodeId) {
Protocol::ErrorString errorString;
auto node = assertNode(errorString, *nodeId);
if (!node)
return makeUnexpected(errorString);
return m_overlay->clearGridOverlayForNode(*node);
}
m_overlay->clearAllGridOverlays();
return { };
}
Inspector::Protocol::ErrorStringOr<void> InspectorDOMAgent::showFlexOverlay(Inspector::Protocol::DOM::NodeId nodeId, Ref<JSON::Object>&& flexColor, std::optional<bool>&& showOrderNumbers)
{
Protocol::ErrorString errorString;
Node* node = assertNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
auto parsedColor = parseColor(WTFMove(flexColor));
if (!parsedColor)
return makeUnexpected("Invalid color could not be parsed."_s);
InspectorOverlay::Flex::Config config;
config.flexColor = *parsedColor;
config.showOrderNumbers = showOrderNumbers.value_or(false);
m_overlay->setFlexOverlayForNode(*node, config);
return { };
}
Inspector::Protocol::ErrorStringOr<void> InspectorDOMAgent::hideFlexOverlay(std::optional<Protocol::DOM::NodeId>&& nodeId)
{
if (nodeId) {
Protocol::ErrorString errorString;
auto node = assertNode(errorString, *nodeId);
if (!node)
return makeUnexpected(errorString);
return m_overlay->clearFlexOverlayForNode(*node);
}
m_overlay->clearAllFlexOverlays();
return { };
}
Protocol::ErrorStringOr<Protocol::DOM::NodeId> InspectorDOMAgent::moveTo(Protocol::DOM::NodeId nodeId, Protocol::DOM::NodeId targetNodeId, std::optional<Protocol::DOM::NodeId>&& insertBeforeNodeId)
{
Protocol::ErrorString errorString;
Node* node = assertEditableNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
Element* targetElement = assertEditableElement(errorString, targetNodeId);
if (!targetElement)
return makeUnexpected(errorString);
Node* anchorNode = nullptr;
if (insertBeforeNodeId && *insertBeforeNodeId) {
anchorNode = assertEditableNode(errorString, *insertBeforeNodeId);
if (!anchorNode)
return makeUnexpected(errorString);
if (anchorNode->parentNode() != targetElement)
return makeUnexpected("Given insertBeforeNodeId must be a child of given targetNodeId"_s);
}
if (!m_domEditor->insertBefore(*targetElement, *node, anchorNode, errorString))
return makeUnexpected(errorString);
return pushNodePathToFrontend(errorString, node);
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::undo()
{
auto result = m_history->undo();
if (result.hasException())
return makeUnexpected(InspectorDOMAgent::toErrorString(result.releaseException()));
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::redo()
{
auto result = m_history->redo();
if (result.hasException())
return makeUnexpected(InspectorDOMAgent::toErrorString(result.releaseException()));
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::markUndoableState()
{
m_history->markUndoableState();
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::focus(Protocol::DOM::NodeId nodeId)
{
Protocol::ErrorString errorString;
Element* element = assertElement(errorString, nodeId);
if (!element)
return makeUnexpected(errorString);
if (!element->isFocusable())
return makeUnexpected("Element for given nodeId is not focusable"_s);
element->focus();
return { };
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::setInspectedNode(Protocol::DOM::NodeId nodeId)
{
Protocol::ErrorString errorString;
Node* node = assertNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
if (node->isInUserAgentShadowTree() && !m_allowEditingUserAgentShadowTrees)
return makeUnexpected("Node for given nodeId is in a shadow tree"_s);
m_inspectedNode = node;
if (auto& commandLineAPIHost = static_cast<WebInjectedScriptManager&>(m_injectedScriptManager).commandLineAPIHost())
commandLineAPIHost->addInspectedObject(makeUnique<InspectableNode>(node));
m_suppressEventListenerChangedEvent = false;
return { };
}
Protocol::ErrorStringOr<Ref<Protocol::Runtime::RemoteObject>> InspectorDOMAgent::resolveNode(Protocol::DOM::NodeId nodeId, const String& objectGroup)
{
Protocol::ErrorString errorString;
Node* node = assertNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
auto object = resolveNode(node, objectGroup);
if (!object)
return makeUnexpected("Missing injected script for given nodeId"_s);
return object.releaseNonNull();
}
Protocol::ErrorStringOr<Ref<JSON::ArrayOf<String>>> InspectorDOMAgent::getAttributes(Protocol::DOM::NodeId nodeId)
{
Protocol::ErrorString errorString;
Element* element = assertElement(errorString, nodeId);
if (!element)
return makeUnexpected(errorString);
return buildArrayForElementAttributes(element);
}
Protocol::ErrorStringOr<Protocol::DOM::NodeId> InspectorDOMAgent::requestNode(const Protocol::Runtime::RemoteObjectId& objectId)
{
Protocol::ErrorString errorString;
Node* node = nodeForObjectId(objectId);
if (!node)
return makeUnexpected("Missing node for given objectId"_s);
auto nodeId = pushNodePathToFrontend(errorString, node);
if (!nodeId)
return makeUnexpected(errorString);
return nodeId;
}
String InspectorDOMAgent::documentURLString(Document* document)
{
if (!document || document->url().isNull())
return emptyString();
return document->url().string();
}
static String documentBaseURLString(Document* document)
{
return document->completeURL(emptyString()).string();
}
static bool pseudoElementType(PseudoId pseudoId, Protocol::DOM::PseudoType* type)
{
switch (pseudoId) {
case PseudoId::Before:
*type = Protocol::DOM::PseudoType::Before;
return true;
case PseudoId::After:
*type = Protocol::DOM::PseudoType::After;
return true;
default:
return false;
}
}
static Protocol::DOM::ShadowRootType shadowRootType(ShadowRootMode mode)
{
switch (mode) {
case ShadowRootMode::UserAgent:
return Protocol::DOM::ShadowRootType::UserAgent;
case ShadowRootMode::Closed:
return Protocol::DOM::ShadowRootType::Closed;
case ShadowRootMode::Open:
return Protocol::DOM::ShadowRootType::Open;
}
ASSERT_NOT_REACHED();
return Protocol::DOM::ShadowRootType::UserAgent;
}
static Protocol::DOM::CustomElementState customElementState(const Element& element)
{
if (element.isDefinedCustomElement())
return Protocol::DOM::CustomElementState::Custom;
if (element.isFailedCustomElement())
return Protocol::DOM::CustomElementState::Failed;
if (element.isUndefinedCustomElement() || element.isCustomElementUpgradeCandidate())
return Protocol::DOM::CustomElementState::Waiting;
return Protocol::DOM::CustomElementState::Builtin;
}
static String computeContentSecurityPolicySHA256Hash(const Element& element)
{
// FIXME: Compute the digest with respect to the raw bytes received from the page.
// See <https://bugs.webkit.org/show_bug.cgi?id=155184>.
PAL::TextEncoding documentEncoding = element.document().textEncoding();
const PAL::TextEncoding& encodingToUse = documentEncoding.isValid() ? documentEncoding : PAL::UTF8Encoding();
auto content = encodingToUse.encode(TextNodeTraversal::contentsAsString(element), PAL::UnencodableHandling::Entities);
auto cryptoDigest = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256);
cryptoDigest->addBytes(content.data(), content.size());
auto digest = cryptoDigest->computeHash();
return makeString("sha256-", base64Encoded(digest.data(), digest.size()));
}
Ref<Protocol::DOM::Node> InspectorDOMAgent::buildObjectForNode(Node* node, int depth)
{
auto id = bind(*node);
String nodeName;
String localName;
String nodeValue;
switch (node->nodeType()) {
case Node::PROCESSING_INSTRUCTION_NODE:
nodeName = node->nodeName();
localName = node->localName();
FALLTHROUGH;
case Node::TEXT_NODE:
case Node::COMMENT_NODE:
case Node::CDATA_SECTION_NODE:
nodeValue = node->nodeValue();
if (nodeValue.length() > maxTextSize)
nodeValue = makeString(StringView(nodeValue).left(maxTextSize), ellipsisUChar);
break;
case Node::ATTRIBUTE_NODE:
localName = node->localName();
break;
case Node::DOCUMENT_FRAGMENT_NODE:
case Node::DOCUMENT_NODE:
case Node::ELEMENT_NODE:
default:
nodeName = node->nodeName();
localName = node->localName();
break;
}
auto value = Protocol::DOM::Node::create()
.setNodeId(id)
.setNodeType(static_cast<int>(node->nodeType()))
.setNodeName(nodeName)
.setLocalName(localName)
.setNodeValue(nodeValue)
.release();
if (node->isContainerNode()) {
int nodeCount = innerChildNodeCount(node);
value->setChildNodeCount(nodeCount);
auto children = buildArrayForContainerChildren(node, depth);
if (children->length() > 0)
value->setChildren(WTFMove(children));
}
if (auto layoutContextType = InspectorCSSAgent::layoutContextTypeForRenderer(node->renderer()))
value->setLayoutContextType(layoutContextType.value());
auto* pageAgent = m_instrumentingAgents.enabledPageAgent();
if (pageAgent) {
if (auto* frameView = node->document().view())
value->setFrameId(pageAgent->frameId(&frameView->frame()));
}
if (is<Element>(*node)) {
Element& element = downcast<Element>(*node);
value->setAttributes(buildArrayForElementAttributes(&element));
if (is<HTMLFrameOwnerElement>(element)) {
if (auto* document = downcast<HTMLFrameOwnerElement>(element).contentDocument())
value->setContentDocument(buildObjectForNode(document, 0));
}
if (ShadowRoot* root = element.shadowRoot()) {
auto shadowRoots = JSON::ArrayOf<Protocol::DOM::Node>::create();
shadowRoots->addItem(buildObjectForNode(root, 0));
value->setShadowRoots(WTFMove(shadowRoots));
}
if (is<HTMLTemplateElement>(element))
value->setTemplateContent(buildObjectForNode(&downcast<HTMLTemplateElement>(element).content(), 0));
if (is<HTMLStyleElement>(element) || (is<HTMLScriptElement>(element) && !element.hasAttributeWithoutSynchronization(HTMLNames::srcAttr)))
value->setContentSecurityPolicyHash(computeContentSecurityPolicySHA256Hash(element));
auto state = customElementState(element);
if (state != Protocol::DOM::CustomElementState::Builtin)
value->setCustomElementState(state);
if (element.pseudoId() != PseudoId::None) {
Protocol::DOM::PseudoType pseudoType;
if (pseudoElementType(element.pseudoId(), &pseudoType))
value->setPseudoType(pseudoType);
} else {
if (auto pseudoElements = buildArrayForPseudoElements(element))
value->setPseudoElements(pseudoElements.releaseNonNull());
}
} else if (is<Document>(*node)) {
Document& document = downcast<Document>(*node);
if (pageAgent)
value->setFrameId(pageAgent->frameId(document.frame()));
value->setDocumentURL(documentURLString(&document));
value->setBaseURL(documentBaseURLString(&document));
value->setXmlVersion(document.xmlVersion());
} else if (is<DocumentType>(*node)) {
DocumentType& docType = downcast<DocumentType>(*node);
value->setPublicId(docType.publicId());
value->setSystemId(docType.systemId());
} else if (is<Attr>(*node)) {
Attr& attribute = downcast<Attr>(*node);
value->setName(attribute.name());
value->setValue(attribute.value());
} else if (is<ShadowRoot>(*node)) {
ShadowRoot& shadowRoot = downcast<ShadowRoot>(*node);
value->setShadowRootType(shadowRootType(shadowRoot.mode()));
}
return value;
}
Ref<JSON::ArrayOf<String>> InspectorDOMAgent::buildArrayForElementAttributes(Element* element)
{
auto attributesValue = JSON::ArrayOf<String>::create();
// Go through all attributes and serialize them.
if (!element->hasAttributes())
return attributesValue;
for (const Attribute& attribute : element->attributesIterator()) {
// Add attribute pair
attributesValue->addItem(attribute.name().toString());
attributesValue->addItem(attribute.value());
}
return attributesValue;
}
Ref<JSON::ArrayOf<Protocol::DOM::Node>> InspectorDOMAgent::buildArrayForContainerChildren(Node* container, int depth)
{
auto children = JSON::ArrayOf<Protocol::DOM::Node>::create();
if (depth == 0) {
// Special-case the only text child - pretend that container's children have been requested.
Node* firstChild = container->firstChild();
if (firstChild && firstChild->nodeType() == Node::TEXT_NODE && !firstChild->nextSibling()) {
children->addItem(buildObjectForNode(firstChild, 0));
m_childrenRequested.add(bind(*container));
}
return children;
}
Node* child = innerFirstChild(container);
depth--;
m_childrenRequested.add(bind(*container));
while (child) {
children->addItem(buildObjectForNode(child, depth));
child = innerNextSibling(child);
}
return children;
}
RefPtr<JSON::ArrayOf<Protocol::DOM::Node>> InspectorDOMAgent::buildArrayForPseudoElements(const Element& element)
{
PseudoElement* beforeElement = element.beforePseudoElement();
PseudoElement* afterElement = element.afterPseudoElement();
if (!beforeElement && !afterElement)
return nullptr;
auto pseudoElements = JSON::ArrayOf<Protocol::DOM::Node>::create();
if (beforeElement)
pseudoElements->addItem(buildObjectForNode(beforeElement, 0));
if (afterElement)
pseudoElements->addItem(buildObjectForNode(afterElement, 0));
return pseudoElements;
}
Ref<Protocol::DOM::EventListener> InspectorDOMAgent::buildObjectForEventListener(const RegisteredEventListener& registeredEventListener, Protocol::DOM::EventListenerId identifier, EventTarget& eventTarget, const AtomString& eventType, bool disabled, const RefPtr<JSC::Breakpoint>& breakpoint)
{
Ref<EventListener> eventListener = registeredEventListener.callback();
String handlerName;
int lineNumber = 0;
int columnNumber = 0;
String scriptID;
if (is<JSEventListener>(eventListener)) {
auto& scriptListener = downcast<JSEventListener>(eventListener.get());
Document* document = nullptr;
if (auto* scriptExecutionContext = eventTarget.scriptExecutionContext()) {
if (is<Document>(scriptExecutionContext))
document = downcast<Document>(scriptExecutionContext);
} else if (is<Node>(eventTarget))
document = &downcast<Node>(eventTarget).document();
JSC::JSObject* handlerObject = nullptr;
JSC::JSGlobalObject* globalObject = nullptr;
JSC::JSLockHolder lock(scriptListener.isolatedWorld().vm());
if (document) {
handlerObject = scriptListener.ensureJSFunction(*document);
if (auto frame = document->frame()) {
// FIXME: Why do we need the canExecuteScripts check here?
if (frame->script().canExecuteScripts(NotAboutToExecuteScript))
globalObject = frame->script().globalObject(scriptListener.isolatedWorld());
}
}
if (handlerObject && globalObject) {
JSC::VM& vm = globalObject->vm();
JSC::JSFunction* handlerFunction = JSC::jsDynamicCast<JSC::JSFunction*>(handlerObject);
if (!handlerFunction) {
auto scope = DECLARE_CATCH_SCOPE(vm);
// If the handler is not actually a function, see if it implements the EventListener interface and use that.
auto handleEventValue = handlerObject->get(globalObject, JSC::Identifier::fromString(vm, "handleEvent"_s));
if (UNLIKELY(scope.exception()))
scope.clearException();
if (handleEventValue)
handlerFunction = JSC::jsDynamicCast<JSC::JSFunction*>(handleEventValue);
}
if (handlerFunction && !handlerFunction->isHostOrBuiltinFunction()) {
// If the listener implements the EventListener interface, use the class name instead of
// "handleEvent", unless it is a plain object.
if (handlerFunction != handlerObject)
handlerName = JSC::JSObject::calculatedClassName(handlerObject);
if (handlerName.isEmpty() || handlerName == "Object"_s)
handlerName = handlerFunction->calculatedDisplayName(vm);
if (auto executable = handlerFunction->jsExecutable()) {
lineNumber = executable->firstLine() - 1;
columnNumber = executable->startColumn() - 1;
scriptID = executable->sourceID() == JSC::SourceProvider::nullID ? emptyString() : String::number(executable->sourceID());
}
}
}
}
auto value = Protocol::DOM::EventListener::create()
.setEventListenerId(identifier)
.setType(eventType)
.setUseCapture(registeredEventListener.useCapture())
.setIsAttribute(eventListener->isAttribute())
.release();
if (is<Node>(eventTarget))
value->setNodeId(pushNodePathToFrontend(&downcast<Node>(eventTarget)));
else if (is<DOMWindow>(eventTarget))
value->setOnWindow(true);
if (!scriptID.isNull()) {
auto location = Protocol::Debugger::Location::create()
.setScriptId(scriptID)
.setLineNumber(lineNumber)
.release();
location->setColumnNumber(columnNumber);
value->setLocation(WTFMove(location));
}
if (!handlerName.isEmpty())
value->setHandlerName(handlerName);
if (registeredEventListener.isPassive())
value->setPassive(true);
if (registeredEventListener.isOnce())
value->setOnce(true);
if (disabled)
value->setDisabled(disabled);
if (breakpoint)
value->setHasBreakpoint(breakpoint);
return value;
}
void InspectorDOMAgent::processAccessibilityChildren(AXCoreObject& axObject, JSON::ArrayOf<Protocol::DOM::NodeId>& childNodeIds)
{
const auto& children = axObject.children();
if (!children.size())
return;
for (const auto& childObject : children) {
if (Node* childNode = childObject->node())
childNodeIds.addItem(pushNodePathToFrontend(childNode));
else
processAccessibilityChildren(*childObject, childNodeIds);
}
}
Ref<Protocol::DOM::AccessibilityProperties> InspectorDOMAgent::buildObjectForAccessibilityProperties(Node& node)
{
if (!WebCore::AXObjectCache::accessibilityEnabled())
WebCore::AXObjectCache::enableAccessibility();
Node* activeDescendantNode = nullptr;
bool busy = false;
auto checked = Protocol::DOM::AccessibilityProperties::Checked::False;
RefPtr<JSON::ArrayOf<Protocol::DOM::NodeId>> childNodeIds;
RefPtr<JSON::ArrayOf<Protocol::DOM::NodeId>> controlledNodeIds;
auto currentState = Protocol::DOM::AccessibilityProperties::Current::False;
bool exists = false;
bool expanded = false;
bool disabled = false;
RefPtr<JSON::ArrayOf<Protocol::DOM::NodeId>> flowedNodeIds;
bool focused = false;
bool ignored = true;
bool ignoredByDefault = false;
auto invalid = Protocol::DOM::AccessibilityProperties::Invalid::False;
bool hidden = false;
String label;
bool liveRegionAtomic = false;
RefPtr<JSON::ArrayOf<String>> liveRegionRelevant;
auto liveRegionStatus = Protocol::DOM::AccessibilityProperties::LiveRegionStatus::Off;
Node* mouseEventNode = nullptr;
RefPtr<JSON::ArrayOf<Protocol::DOM::NodeId>> ownedNodeIds;
Node* parentNode = nullptr;
bool pressed = false;
bool readonly = false;
bool required = false;
String role;
bool selected = false;
RefPtr<JSON::ArrayOf<Protocol::DOM::NodeId>> selectedChildNodeIds;
bool supportsChecked = false;
bool supportsExpanded = false;
bool supportsLiveRegion = false;
bool supportsPressed = false;
bool supportsRequired = false;
bool supportsFocused = false;
bool isPopupButton = false;
int headingLevel = 0;
unsigned hierarchicalLevel = 0;
unsigned level = 0;
if (auto* axObjectCache = node.document().axObjectCache()) {
if (auto* axObject = axObjectCache->getOrCreate(&node)) {
if (AXCoreObject* activeDescendant = axObject->activeDescendant())
activeDescendantNode = activeDescendant->node();
// An AX object is "busy" if it or any ancestor has aria-busy="true" set.
AXCoreObject* current = axObject;
while (!busy && current) {
busy = current->isBusy();
current = current->parentObject();
}
supportsChecked = axObject->supportsChecked();
if (supportsChecked) {
AccessibilityButtonState checkValue = axObject->checkboxOrRadioValue(); // Element using aria-checked.
if (checkValue == AccessibilityButtonState::On)
checked = Protocol::DOM::AccessibilityProperties::Checked::True;
else if (checkValue == AccessibilityButtonState::Mixed)
checked = Protocol::DOM::AccessibilityProperties::Checked::Mixed;
else if (axObject->isChecked()) // Native checkbox.
checked = Protocol::DOM::AccessibilityProperties::Checked::True;
}
if (!axObject->children().isEmpty()) {
childNodeIds = JSON::ArrayOf<Protocol::DOM::NodeId>::create();
processAccessibilityChildren(*axObject, *childNodeIds);
}
auto controlledElements = axObject->elementsFromAttribute(aria_controlsAttr);
if (controlledElements.size()) {
controlledNodeIds = JSON::ArrayOf<Protocol::DOM::NodeId>::create();
for (Element* controlledElement : controlledElements) {
if (auto controlledElementId = pushNodePathToFrontend(controlledElement))
controlledNodeIds->addItem(controlledElementId);
}
}
switch (axObject->currentState()) {
case AccessibilityCurrentState::False:
currentState = Protocol::DOM::AccessibilityProperties::Current::False;
break;
case AccessibilityCurrentState::Page:
currentState = Protocol::DOM::AccessibilityProperties::Current::Page;
break;
case AccessibilityCurrentState::Step:
currentState = Protocol::DOM::AccessibilityProperties::Current::Step;
break;
case AccessibilityCurrentState::Location:
currentState = Protocol::DOM::AccessibilityProperties::Current::Location;
break;
case AccessibilityCurrentState::Date:
currentState = Protocol::DOM::AccessibilityProperties::Current::Date;
break;
case AccessibilityCurrentState::Time:
currentState = Protocol::DOM::AccessibilityProperties::Current::Time;
break;
case AccessibilityCurrentState::True:
currentState = Protocol::DOM::AccessibilityProperties::Current::True;
break;
}
disabled = !axObject->isEnabled();
exists = true;
supportsExpanded = axObject->supportsExpanded();
if (supportsExpanded)
expanded = axObject->isExpanded();
auto flowedElements = axObject->elementsFromAttribute(aria_flowtoAttr);
if (flowedElements.size()) {
flowedNodeIds = JSON::ArrayOf<Protocol::DOM::NodeId>::create();
for (Element* flowedElement : flowedElements) {
if (auto flowedElementId = pushNodePathToFrontend(flowedElement))
flowedNodeIds->addItem(flowedElementId);
}
}
if (is<Element>(node)) {
supportsFocused = axObject->canSetFocusAttribute();
if (supportsFocused)
focused = axObject->isFocused();
}
ignored = axObject->accessibilityIsIgnored();
ignoredByDefault = axObject->accessibilityIsIgnoredByDefault();
String invalidValue = axObject->invalidStatus();
if (invalidValue == "false"_s)
invalid = Protocol::DOM::AccessibilityProperties::Invalid::False;
else if (invalidValue == "grammar"_s)
invalid = Protocol::DOM::AccessibilityProperties::Invalid::Grammar;
else if (invalidValue == "spelling"_s)
invalid = Protocol::DOM::AccessibilityProperties::Invalid::Spelling;
else // Future versions of ARIA may allow additional truthy values. Ex. format, order, or size.
invalid = Protocol::DOM::AccessibilityProperties::Invalid::True;
if (axObject->isHidden())
hidden = true;
label = axObject->computedLabel();
if (axObject->supportsLiveRegion()) {
supportsLiveRegion = true;
liveRegionAtomic = axObject->liveRegionAtomic();
auto ariaRelevantAttrValue = axObject->liveRegionRelevant();
if (!ariaRelevantAttrValue.isEmpty()) {
// FIXME: Pass enum values rather than strings once unblocked. http://webkit.org/b/133711
String ariaRelevantAdditions = Protocol::Helpers::getEnumConstantValue(Protocol::DOM::LiveRegionRelevant::Additions);
String ariaRelevantRemovals = Protocol::Helpers::getEnumConstantValue(Protocol::DOM::LiveRegionRelevant::Removals);
String ariaRelevantText = Protocol::Helpers::getEnumConstantValue(Protocol::DOM::LiveRegionRelevant::Text);
liveRegionRelevant = JSON::ArrayOf<String>::create();
SpaceSplitString values(AtomString { ariaRelevantAttrValue }, SpaceSplitString::ShouldFoldCase::Yes);
// @aria-relevant="all" is exposed as ["additions","removals","text"], in order.
// This order is controlled in WebCore and expected in WebInspectorUI.
if (values.contains("all"_s)) {
liveRegionRelevant->addItem(ariaRelevantAdditions);
liveRegionRelevant->addItem(ariaRelevantRemovals);
liveRegionRelevant->addItem(ariaRelevantText);
} else {
if (values.contains(AtomString { ariaRelevantAdditions }))
liveRegionRelevant->addItem(ariaRelevantAdditions);
if (values.contains(AtomString { ariaRelevantRemovals }))
liveRegionRelevant->addItem(ariaRelevantRemovals);
if (values.contains(AtomString { ariaRelevantText }))
liveRegionRelevant->addItem(ariaRelevantText);
}
}
String ariaLive = axObject->liveRegionStatus();
if (ariaLive == "assertive"_s)
liveRegionStatus = Protocol::DOM::AccessibilityProperties::LiveRegionStatus::Assertive;
else if (ariaLive == "polite"_s)
liveRegionStatus = Protocol::DOM::AccessibilityProperties::LiveRegionStatus::Polite;
}
if (is<AccessibilityNodeObject>(*axObject))
mouseEventNode = downcast<AccessibilityNodeObject>(*axObject).mouseButtonListener(MouseButtonListenerResultFilter::IncludeBodyElement);
if (axObject->supportsARIAOwns()) {
auto ownedElements = axObject->elementsFromAttribute(aria_ownsAttr);
if (ownedElements.size()) {
ownedNodeIds = JSON::ArrayOf<Protocol::DOM::NodeId>::create();
for (Element* ownedElement : ownedElements) {
if (auto ownedElementId = pushNodePathToFrontend(ownedElement))
ownedNodeIds->addItem(ownedElementId);
}
}
}
if (AXCoreObject* parentObject = axObject->parentObjectUnignored())
parentNode = parentObject->node();
supportsPressed = axObject->pressedIsPresent();
if (supportsPressed)
pressed = axObject->isPressed();
if (axObject->isTextControl())
readonly = !axObject->canSetValueAttribute();
supportsRequired = axObject->supportsRequiredAttribute();
if (supportsRequired)
required = axObject->isRequired();
role = axObject->computedRoleString();
selected = axObject->isSelected();
AXCoreObject::AccessibilityChildrenVector selectedChildren;
axObject->selectedChildren(selectedChildren);
if (selectedChildren.size()) {
selectedChildNodeIds = JSON::ArrayOf<Protocol::DOM::NodeId>::create();
for (auto& selectedChildObject : selectedChildren) {
if (Node* selectedChildNode = selectedChildObject->node()) {
if (auto selectedChildNodeId = pushNodePathToFrontend(selectedChildNode))
selectedChildNodeIds->addItem(selectedChildNodeId);
}
}
}
headingLevel = axObject->headingLevel();
hierarchicalLevel = axObject->hierarchicalLevel();
level = hierarchicalLevel ? hierarchicalLevel : headingLevel;
isPopupButton = axObject->isPopUpButton() || axObject->hasPopup();
}
}
auto value = Protocol::DOM::AccessibilityProperties::create()
.setExists(exists)
.setLabel(label)
.setRole(role)
.setNodeId(pushNodePathToFrontend(&node))
.release();
if (exists) {
if (activeDescendantNode) {
if (auto activeDescendantNodeId = pushNodePathToFrontend(activeDescendantNode))
value->setActiveDescendantNodeId(activeDescendantNodeId);
}
if (busy)
value->setBusy(busy);
if (supportsChecked)
value->setChecked(checked);
if (childNodeIds)
value->setChildNodeIds(childNodeIds.releaseNonNull());
if (controlledNodeIds)
value->setControlledNodeIds(controlledNodeIds.releaseNonNull());
if (currentState != Protocol::DOM::AccessibilityProperties::Current::False)
value->setCurrent(currentState);
if (disabled)
value->setDisabled(disabled);
if (supportsExpanded)
value->setExpanded(expanded);
if (flowedNodeIds)
value->setFlowedNodeIds(flowedNodeIds.releaseNonNull());
if (supportsFocused)
value->setFocused(focused);
if (ignored)
value->setIgnored(ignored);
if (ignoredByDefault)
value->setIgnoredByDefault(ignoredByDefault);
if (invalid != Protocol::DOM::AccessibilityProperties::Invalid::False)
value->setInvalid(invalid);
if (hidden)
value->setHidden(hidden);
if (supportsLiveRegion) {
value->setLiveRegionAtomic(liveRegionAtomic);
if (liveRegionRelevant->length())
value->setLiveRegionRelevant(liveRegionRelevant.releaseNonNull());
value->setLiveRegionStatus(liveRegionStatus);
}
if (mouseEventNode) {
if (auto mouseEventNodeId = pushNodePathToFrontend(mouseEventNode))
value->setMouseEventNodeId(mouseEventNodeId);
}
if (ownedNodeIds)
value->setOwnedNodeIds(ownedNodeIds.releaseNonNull());
if (parentNode) {
if (auto parentNodeId = pushNodePathToFrontend(parentNode))
value->setParentNodeId(parentNodeId);
}
if (supportsPressed)
value->setPressed(pressed);
if (readonly)
value->setReadonly(readonly);
if (supportsRequired)
value->setRequired(required);
if (selected)
value->setSelected(selected);
if (selectedChildNodeIds)
value->setSelectedChildNodeIds(selectedChildNodeIds.releaseNonNull());
// H1 -- H6 always have a headingLevel property that can be complimented by a hierarchicalLevel
// property when aria-level is set on the element, in which case we want to remain calling
// this value the "Heading Level" in the inspector.
// Also, we do not want it to say Hierarchy Level: 0
if (headingLevel)
value->setHeadingLevel(level);
else if (level)
value->setHierarchyLevel(level);
if (isPopupButton)
value->setIsPopUpButton(isPopupButton);
}
return value;
}
static bool containsOnlyHTMLWhitespace(Node* node)
{
// FIXME: Respect ignoreWhitespace setting from inspector front end?
return is<Text>(node) && downcast<Text>(*node).data().isAllSpecialCharacters<isHTMLSpace>();
}
Node* InspectorDOMAgent::innerFirstChild(Node* node)
{
node = node->firstChild();
while (containsOnlyHTMLWhitespace(node))
node = node->nextSibling();
return node;
}
Node* InspectorDOMAgent::innerNextSibling(Node* node)
{
do {
node = node->nextSibling();
} while (containsOnlyHTMLWhitespace(node));
return node;
}
Node* InspectorDOMAgent::innerPreviousSibling(Node* node)
{
do {
node = node->previousSibling();
} while (containsOnlyHTMLWhitespace(node));
return node;
}
unsigned InspectorDOMAgent::innerChildNodeCount(Node* node)
{
unsigned count = 0;
for (Node* child = innerFirstChild(node); child; child = innerNextSibling(child))
++count;
return count;
}
Node* InspectorDOMAgent::innerParentNode(Node* node)
{
ASSERT(node);
if (is<Document>(*node))
return downcast<Document>(*node).ownerElement();
if (is<ShadowRoot>(*node))
return downcast<ShadowRoot>(*node).host();
return node->parentNode();
}
void InspectorDOMAgent::didCommitLoad(Document* document)
{
if (m_nodeToFocus && &m_nodeToFocus->document() == document)
m_nodeToFocus = nullptr;
if (m_mousedOverNode && &m_mousedOverNode->document() == document)
m_mousedOverNode = nullptr;
if (m_inspectedNode && &m_inspectedNode->document() == document)
m_inspectedNode = nullptr;
RefPtr<Element> frameOwner = document->ownerElement();
if (!frameOwner)
return;
auto frameOwnerId = boundNodeId(frameOwner.get());
if (!frameOwnerId)
return;
// Re-add frame owner element together with its new children.
auto parentId = boundNodeId(innerParentNode(frameOwner.get()));
m_frontendDispatcher->childNodeRemoved(parentId, frameOwnerId);
unbind(*frameOwner);
auto value = buildObjectForNode(frameOwner.get(), 0);
Node* previousSibling = innerPreviousSibling(frameOwner.get());
auto prevId = boundNodeId(previousSibling);
m_frontendDispatcher->childNodeInserted(parentId, prevId, WTFMove(value));
}
Protocol::DOM::NodeId InspectorDOMAgent::identifierForNode(Node& node)
{
return pushNodePathToFrontend(&node);
}
void InspectorDOMAgent::addEventListenersToNode(Node& node)
{
#if ENABLE(VIDEO)
auto callback = EventFiredCallback::create(*this);
auto createEventListener = [&] (const AtomString& eventName) {
node.addEventListener(eventName, callback.copyRef(), false);
};
#if ENABLE(FULLSCREEN_API)
if (is<Document>(node) || is<HTMLMediaElement>(node))
createEventListener(eventNames().webkitfullscreenchangeEvent);
#endif // ENABLE(FULLSCREEN_API)
if (is<HTMLMediaElement>(node)) {
createEventListener(eventNames().abortEvent);
createEventListener(eventNames().canplayEvent);
createEventListener(eventNames().canplaythroughEvent);
createEventListener(eventNames().emptiedEvent);
createEventListener(eventNames().endedEvent);
createEventListener(eventNames().loadeddataEvent);
createEventListener(eventNames().loadedmetadataEvent);
createEventListener(eventNames().loadstartEvent);
createEventListener(eventNames().pauseEvent);
createEventListener(eventNames().playEvent);
createEventListener(eventNames().playingEvent);
createEventListener(eventNames().seekedEvent);
createEventListener(eventNames().seekingEvent);
createEventListener(eventNames().stalledEvent);
createEventListener(eventNames().suspendEvent);
createEventListener(eventNames().waitingEvent);
if (!m_mediaMetricsTimer.isActive())
m_mediaMetricsTimer.start(0_s, 1_s / 15.);
}
#else
UNUSED_PARAM(node);
#endif // ENABLE(VIDEO)
}
void InspectorDOMAgent::didInsertDOMNode(Node& node)
{
if (containsOnlyHTMLWhitespace(&node))
return;
// We could be attaching existing subtree. Forget the bindings.
unbind(node);
ContainerNode* parent = node.parentNode();
auto parentId = boundNodeId(parent);
// Return if parent is not mapped yet.
if (!parentId)
return;
if (!m_childrenRequested.contains(parentId)) {
// No children are mapped yet -> only notify on changes of hasChildren.
m_frontendDispatcher->childNodeCountUpdated(parentId, innerChildNodeCount(parent));
} else {
// Children have been requested -> return value of a new child.
Node* prevSibling = innerPreviousSibling(&node);
auto prevId = boundNodeId(prevSibling);
auto value = buildObjectForNode(&node, 0);
m_frontendDispatcher->childNodeInserted(parentId, prevId, WTFMove(value));
}
}
void InspectorDOMAgent::didRemoveDOMNode(Node& node)
{
if (containsOnlyHTMLWhitespace(&node))
return;
ContainerNode* parent = node.parentNode();
auto parentId = boundNodeId(parent);
// If parent is not mapped yet -> ignore the event.
if (!parentId)
return;
// FIXME: <webkit.org/b/189687> Preserve DOM.NodeId if a node is removed and re-added
if (!m_childrenRequested.contains(parentId)) {
// No children are mapped yet -> only notify on changes of hasChildren.
if (innerChildNodeCount(parent) == 1)
m_frontendDispatcher->childNodeCountUpdated(parentId, 0);
} else
m_frontendDispatcher->childNodeRemoved(parentId, boundNodeId(&node));
unbind(node);
}
void InspectorDOMAgent::willDestroyDOMNode(Node& node)
{
if (containsOnlyHTMLWhitespace(&node))
return;
auto nodeId = m_nodeToId.take(node);
if (!nodeId)
return;
m_idToNode.remove(nodeId);
m_childrenRequested.remove(nodeId);
if (auto* cssAgent = m_instrumentingAgents.enabledCSSAgent())
cssAgent->didRemoveDOMNode(node, nodeId);
// This can be called in response to GC. Due to the single-process model used in WebKit1, the
// event must be dispatched from a timer to prevent the frontend from making JS allocations
// while the GC is still active.
// FIXME: <webkit.org/b/189687> Unify m_destroyedAttachedNodeIdentifiers and m_destroyedDetachedNodeIdentifiers.
if (auto parentId = boundNodeId(node.parentNode()))
m_destroyedAttachedNodeIdentifiers.append({ parentId, nodeId });
else
m_destroyedDetachedNodeIdentifiers.append(nodeId);
if (!m_destroyedNodesTimer.isActive())
m_destroyedNodesTimer.startOneShot(0_s);
}
void InspectorDOMAgent::destroyedNodesTimerFired()
{
for (auto& [parentId, nodeId] : std::exchange(m_destroyedAttachedNodeIdentifiers, { })) {
if (!m_childrenRequested.contains(parentId)) {
auto* parent = nodeForId(parentId);
if (parent && innerChildNodeCount(parent) == 1)
m_frontendDispatcher->childNodeCountUpdated(parentId, 0);
} else
m_frontendDispatcher->childNodeRemoved(parentId, nodeId);
}
for (auto nodeId : std::exchange(m_destroyedDetachedNodeIdentifiers, { }))
m_frontendDispatcher->willDestroyDOMNode(nodeId);
}
void InspectorDOMAgent::willModifyDOMAttr(Element&, const AtomString& oldValue, const AtomString& newValue)
{
m_suppressAttributeModifiedEvent = (oldValue == newValue);
}
void InspectorDOMAgent::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;
if (auto* cssAgent = m_instrumentingAgents.enabledCSSAgent())
cssAgent->didModifyDOMAttr(element);
m_frontendDispatcher->attributeModified(id, name, value);
}
void InspectorDOMAgent::didRemoveDOMAttr(Element& element, const AtomString& name)
{
auto id = boundNodeId(&element);
if (!id)
return;
if (auto* cssAgent = m_instrumentingAgents.enabledCSSAgent())
cssAgent->didModifyDOMAttr(element);
m_frontendDispatcher->attributeRemoved(id, name);
}
void InspectorDOMAgent::styleAttributeInvalidated(const Vector<Element*>& elements)
{
auto nodeIds = JSON::ArrayOf<Protocol::DOM::NodeId>::create();
for (auto& element : elements) {
auto id = boundNodeId(element);
if (!id)
continue;
if (auto* cssAgent = m_instrumentingAgents.enabledCSSAgent())
cssAgent->didModifyDOMAttr(*element);
nodeIds->addItem(id);
}
m_frontendDispatcher->inlineStyleInvalidated(WTFMove(nodeIds));
}
void InspectorDOMAgent::characterDataModified(CharacterData& characterData)
{
auto id = boundNodeId(&characterData);
if (!id) {
// Push text node if it is being created.
didInsertDOMNode(characterData);
return;
}
m_frontendDispatcher->characterDataModified(id, characterData.data());
}
void InspectorDOMAgent::didInvalidateStyleAttr(Element& element)
{
auto id = boundNodeId(&element);
if (!id)
return;
if (!m_revalidateStyleAttrTask)
m_revalidateStyleAttrTask = makeUnique<RevalidateStyleAttributeTask>(this);
m_revalidateStyleAttrTask->scheduleFor(&element);
}
void InspectorDOMAgent::didPushShadowRoot(Element& host, ShadowRoot& root)
{
auto hostId = boundNodeId(&host);
if (hostId)
m_frontendDispatcher->shadowRootPushed(hostId, buildObjectForNode(&root, 0));
}
void InspectorDOMAgent::willPopShadowRoot(Element& host, ShadowRoot& root)
{
auto hostId = boundNodeId(&host);
auto rootId = boundNodeId(&root);
if (hostId && rootId)
m_frontendDispatcher->shadowRootPopped(hostId, rootId);
}
void InspectorDOMAgent::didChangeCustomElementState(Element& element)
{
auto elementId = boundNodeId(&element);
if (!elementId)
return;
m_frontendDispatcher->customElementStateChanged(elementId, customElementState(element));
}
void InspectorDOMAgent::frameDocumentUpdated(Frame& frame)
{
Document* document = frame.document();
if (!document)
return;
if (!frame.isMainFrame())
return;
// Only update the main frame document, nested frame document updates are not required
// (will be handled by didCommitLoad()).
setDocument(document);
}
void InspectorDOMAgent::pseudoElementCreated(PseudoElement& pseudoElement)
{
Element* parent = pseudoElement.hostElement();
if (!parent)
return;
auto parentId = boundNodeId(parent);
if (!parentId)
return;
pushChildNodesToFrontend(parentId, 1);
m_frontendDispatcher->pseudoElementAdded(parentId, buildObjectForNode(&pseudoElement, 0));
}
void InspectorDOMAgent::pseudoElementDestroyed(PseudoElement& pseudoElement)
{
auto pseudoElementId = boundNodeId(&pseudoElement);
if (!pseudoElementId)
return;
// If a PseudoElement is bound, its parent element must have been bound.
Element* parent = pseudoElement.hostElement();
ASSERT(parent);
auto parentId = boundNodeId(parent);
ASSERT(parentId);
unbind(pseudoElement);
m_frontendDispatcher->pseudoElementRemoved(parentId, pseudoElementId);
}
void InspectorDOMAgent::didAddEventListener(EventTarget& target)
{
if (!is<Node>(target))
return;
auto& node = downcast<Node>(target);
if (!node.contains(m_inspectedNode.get()))
return;
auto nodeId = boundNodeId(&node);
if (!nodeId)
return;
if (m_suppressEventListenerChangedEvent)
return;
m_suppressEventListenerChangedEvent = true;
m_frontendDispatcher->didAddEventListener(nodeId);
}
void InspectorDOMAgent::willRemoveEventListener(EventTarget& target, const AtomString& eventType, EventListener& listener, bool capture)
{
if (!is<Node>(target))
return;
auto& node = downcast<Node>(target);
if (!node.contains(m_inspectedNode.get()))
return;
auto nodeId = boundNodeId(&node);
if (!nodeId)
return;
bool listenerExists = false;
for (auto& item : node.eventListeners(eventType)) {
if (item->callback() == listener && item->useCapture() == capture) {
listenerExists = true;
break;
}
}
if (!listenerExists)
return;
m_eventListenerEntries.removeIf([&] (auto& entry) {
return entry.value.matches(target, eventType, listener, capture);
});
if (m_suppressEventListenerChangedEvent)
return;
m_suppressEventListenerChangedEvent = true;
m_frontendDispatcher->willRemoveEventListener(nodeId);
}
bool InspectorDOMAgent::isEventListenerDisabled(EventTarget& target, const AtomString& eventType, EventListener& listener, bool capture)
{
for (auto& inspectorEventListener : m_eventListenerEntries.values()) {
if (inspectorEventListener.matches(target, eventType, listener, capture))
return inspectorEventListener.disabled;
}
return false;
}
void InspectorDOMAgent::eventDidResetAfterDispatch(const Event& event)
{
m_dispatchedEvents.remove(&event);
}
void InspectorDOMAgent::flexibleBoxRendererBeganLayout(const RenderObject& renderer)
{
m_flexibleBoxRendererCachedItemsAtStartOfLine.remove(renderer);
}
void InspectorDOMAgent::flexibleBoxRendererWrappedToNextLine(const RenderObject& renderer, size_t lineStartItemIndex)
{
m_flexibleBoxRendererCachedItemsAtStartOfLine.ensure(renderer, [] {
return Vector<size_t>();
}).iterator->value.append(lineStartItemIndex);
}
Vector<size_t> InspectorDOMAgent::flexibleBoxRendererCachedItemsAtStartOfLine(const RenderObject& renderer)
{
return m_flexibleBoxRendererCachedItemsAtStartOfLine.get(renderer);
}
RefPtr<JSC::Breakpoint> InspectorDOMAgent::breakpointForEventListener(EventTarget& target, const AtomString& eventType, EventListener& listener, bool capture)
{
for (auto& inspectorEventListener : m_eventListenerEntries.values()) {
if (inspectorEventListener.matches(target, eventType, listener, capture))
return inspectorEventListener.breakpoint;
}
return nullptr;
}
Protocol::DOM::EventListenerId InspectorDOMAgent::idForEventListener(EventTarget& target, const AtomString& eventType, EventListener& listener, bool capture)
{
for (auto& inspectorEventListener : m_eventListenerEntries.values()) {
if (inspectorEventListener.matches(target, eventType, listener, capture))
return inspectorEventListener.identifier;
}
return 0;
}
#if ENABLE(VIDEO)
void InspectorDOMAgent::mediaMetricsTimerFired()
{
// FIXME: remove metrics information for any media element when it's destroyed
if (HTMLMediaElement::allMediaElements().isEmpty()) {
if (m_mediaMetricsTimer.isActive())
m_mediaMetricsTimer.stop();
m_mediaMetrics.clear();
return;
}
for (auto* mediaElement : HTMLMediaElement::allMediaElements()) {
if (!is<HTMLVideoElement>(mediaElement) || !mediaElement->isPlaying())
continue;
auto videoPlaybackQuality = mediaElement->getVideoPlaybackQuality();
unsigned displayCompositedVideoFrames = videoPlaybackQuality->displayCompositedVideoFrames();
auto iterator = m_mediaMetrics.find(*mediaElement);
if (iterator == m_mediaMetrics.end()) {
m_mediaMetrics.set(*mediaElement, MediaMetrics(displayCompositedVideoFrames));
continue;
}
bool isPowerEfficient = (displayCompositedVideoFrames - iterator->value.displayCompositedFrames) > 0;
if (iterator->value.isPowerEfficient != isPowerEfficient) {
iterator->value.isPowerEfficient = isPowerEfficient;
if (auto nodeId = pushNodePathToFrontend(mediaElement)) {
auto timestamp = m_environment.executionStopwatch().elapsedTime().seconds();
m_frontendDispatcher->powerEfficientPlaybackStateChanged(nodeId, timestamp, iterator->value.isPowerEfficient);
}
}
iterator->value.displayCompositedFrames = displayCompositedVideoFrames;
}
m_mediaMetrics.removeIf([&] (auto& entry) {
return !HTMLMediaElement::allMediaElements().contains(&entry.key);
});
}
#endif
Node* InspectorDOMAgent::nodeForPath(const String& path)
{
// The path is of form "1,HTML,2,BODY,1,DIV"
if (!m_document)
return nullptr;
Node* node = m_document.get();
auto pathTokens = StringView(path).split(',');
auto it = pathTokens.begin();
if (it == pathTokens.end())
return nullptr;
for (; it != pathTokens.end(); ++it) {
auto childNumberView = *it;
if (++it == pathTokens.end())
break;
auto childNumber = parseIntegerAllowingTrailingJunk<unsigned>(childNumberView);
if (!childNumber)
return nullptr;
Node* child;
if (is<HTMLFrameOwnerElement>(*node)) {
ASSERT(!*childNumber);
auto& frameOwner = downcast<HTMLFrameOwnerElement>(*node);
child = frameOwner.contentDocument();
} else {
if (*childNumber >= innerChildNodeCount(node))
return nullptr;
child = innerFirstChild(node);
for (size_t j = 0; child && j < *childNumber; ++j)
child = innerNextSibling(child);
}
auto childName = *it;
if (!child || child->nodeName() != childName)
return nullptr;
node = child;
}
return node;
}
Node* InspectorDOMAgent::nodeForObjectId(const Protocol::Runtime::RemoteObjectId& objectId)
{
InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(objectId);
if (injectedScript.hasNoValue())
return nullptr;
return scriptValueAsNode(injectedScript.findObjectById(objectId));
}
Protocol::ErrorStringOr<Protocol::DOM::NodeId> InspectorDOMAgent::pushNodeByPathToFrontend(const String& path)
{
Protocol::ErrorString errorString;
if (Node* node = nodeForPath(path)) {
if (auto nodeId = pushNodePathToFrontend(errorString, node))
return nodeId;
return makeUnexpected(errorString);
}
return makeUnexpected("Missing node for given path"_s);
}
RefPtr<Protocol::Runtime::RemoteObject> InspectorDOMAgent::resolveNode(Node* node, const String& objectGroup)
{
Document* document = &node->document();
if (auto* templateHost = document->templateDocumentHost())
document = templateHost;
auto* frame = document->frame();
if (!frame)
return nullptr;
auto& globalObject = mainWorldGlobalObject(*frame);
auto injectedScript = m_injectedScriptManager.injectedScriptFor(&globalObject);
if (injectedScript.hasNoValue())
return nullptr;
return injectedScript.wrapObject(nodeAsScriptValue(globalObject, node), objectGroup);
}
Node* InspectorDOMAgent::scriptValueAsNode(JSC::JSValue value)
{
if (!value || !value.isObject())
return nullptr;
return JSNode::toWrapped(value.getObject()->vm(), value.getObject());
}
JSC::JSValue InspectorDOMAgent::nodeAsScriptValue(JSC::JSGlobalObject& state, Node* node)
{
JSC::JSLockHolder lock(&state);
return toJS(&state, deprecatedGlobalObjectForPrototype(&state), BindingSecurity::checkSecurityForNode(state, node));
}
Protocol::ErrorStringOr<void> InspectorDOMAgent::setAllowEditingUserAgentShadowTrees(bool allow)
{
m_allowEditingUserAgentShadowTrees = allow;
return { };
}
} // namespace WebCore