| /* |
| * Copyright (C) 2009-2017 Apple Inc. All rights reserved. |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 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. |
| */ |
| |
| #pragma once |
| |
| #include "EventTarget.h" |
| #include "InspectorOverlay.h" |
| #include "InspectorWebAgentBase.h" |
| #include "Timer.h" |
| #include <JavaScriptCore/Breakpoint.h> |
| #include <JavaScriptCore/InspectorBackendDispatchers.h> |
| #include <JavaScriptCore/InspectorFrontendDispatchers.h> |
| #include <wtf/HashMap.h> |
| #include <wtf/HashSet.h> |
| #include <wtf/JSONValues.h> |
| #include <wtf/RefPtr.h> |
| #include <wtf/Vector.h> |
| #include <wtf/WeakHashMap.h> |
| #include <wtf/text/AtomString.h> |
| |
| namespace Inspector { |
| class InjectedScriptManager; |
| } |
| |
| namespace JSC { |
| class CallFrame; |
| class JSValue; |
| } |
| |
| namespace WebCore { |
| |
| class AXCoreObject; |
| class CharacterData; |
| class DOMEditor; |
| class Document; |
| class Element; |
| class Event; |
| class Exception; |
| class FloatQuad; |
| class Frame; |
| class InspectorHistory; |
| #if ENABLE(VIDEO) |
| class HTMLMediaElement; |
| #endif |
| class HitTestResult; |
| class Node; |
| class Page; |
| class PseudoElement; |
| class RenderObject; |
| class RevalidateStyleAttributeTask; |
| class ShadowRoot; |
| |
| struct Styleable; |
| |
| class InspectorDOMAgent final : public InspectorAgentBase, public Inspector::DOMBackendDispatcherHandler { |
| WTF_MAKE_NONCOPYABLE(InspectorDOMAgent); |
| WTF_MAKE_FAST_ALLOCATED; |
| public: |
| InspectorDOMAgent(PageAgentContext&, InspectorOverlay*); |
| ~InspectorDOMAgent(); |
| |
| static String toErrorString(ExceptionCode); |
| static String toErrorString(Exception&&); |
| |
| static String documentURLString(Document*); |
| |
| // We represent embedded doms as a part of the same hierarchy. Hence we treat children of frame owners differently. |
| // We also skip whitespace text nodes conditionally. Following methods encapsulate these specifics. |
| static Node* innerFirstChild(Node*); |
| static Node* innerNextSibling(Node*); |
| static Node* innerPreviousSibling(Node*); |
| static unsigned innerChildNodeCount(Node*); |
| static Node* innerParentNode(Node*); |
| |
| static Node* scriptValueAsNode(JSC::JSValue); |
| static JSC::JSValue nodeAsScriptValue(JSC::JSGlobalObject&, Node*); |
| |
| // InspectorAgentBase |
| void didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*); |
| void willDestroyFrontendAndBackend(Inspector::DisconnectReason); |
| |
| // DOMBackendDispatcherHandler |
| Inspector::Protocol::ErrorStringOr<std::optional<Inspector::Protocol::DOM::NodeId>> querySelector(Inspector::Protocol::DOM::NodeId, const String& selector); |
| Inspector::Protocol::ErrorStringOr<Ref<JSON::ArrayOf<Inspector::Protocol::DOM::NodeId>>> querySelectorAll(Inspector::Protocol::DOM::NodeId, const String& selector); |
| Inspector::Protocol::ErrorStringOr<Ref<Inspector::Protocol::DOM::Node>> getDocument(); |
| Inspector::Protocol::ErrorStringOr<void> requestChildNodes(Inspector::Protocol::DOM::NodeId, std::optional<int>&& depth); |
| Inspector::Protocol::ErrorStringOr<void> setAttributeValue(Inspector::Protocol::DOM::NodeId, const String& name, const String& value); |
| Inspector::Protocol::ErrorStringOr<void> setAttributesAsText(Inspector::Protocol::DOM::NodeId, const String& text, const String& name); |
| Inspector::Protocol::ErrorStringOr<void> removeAttribute(Inspector::Protocol::DOM::NodeId, const String& name); |
| Inspector::Protocol::ErrorStringOr<void> removeNode(Inspector::Protocol::DOM::NodeId); |
| Inspector::Protocol::ErrorStringOr<Inspector::Protocol::DOM::NodeId> setNodeName(Inspector::Protocol::DOM::NodeId, const String&); |
| Inspector::Protocol::ErrorStringOr<String> getOuterHTML(Inspector::Protocol::DOM::NodeId); |
| Inspector::Protocol::ErrorStringOr<void> setOuterHTML(Inspector::Protocol::DOM::NodeId, const String&); |
| Inspector::Protocol::ErrorStringOr<void> insertAdjacentHTML(Inspector::Protocol::DOM::NodeId, const String& position, const String& html); |
| Inspector::Protocol::ErrorStringOr<void> setNodeValue(Inspector::Protocol::DOM::NodeId, const String&); |
| Inspector::Protocol::ErrorStringOr<Ref<JSON::ArrayOf<String>>> getSupportedEventNames(); |
| #if ENABLE(INSPECTOR_ALTERNATE_DISPATCHERS) |
| Inspector::Protocol::ErrorStringOr<Ref<JSON::ArrayOf<Inspector::Protocol::DOM::DataBinding>>> getDataBindingsForNode(Inspector::Protocol::DOM::NodeId); |
| Inspector::Protocol::ErrorStringOr<String> getAssociatedDataForNode(Inspector::Protocol::DOM::NodeId); |
| #endif |
| Inspector::Protocol::ErrorStringOr<Ref<JSON::ArrayOf<Inspector::Protocol::DOM::EventListener>>> getEventListenersForNode(Inspector::Protocol::DOM::NodeId); |
| Inspector::Protocol::ErrorStringOr<void> setEventListenerDisabled(Inspector::Protocol::DOM::EventListenerId, bool); |
| Inspector::Protocol::ErrorStringOr<void> setBreakpointForEventListener(Inspector::Protocol::DOM::EventListenerId, RefPtr<JSON::Object>&& options); |
| Inspector::Protocol::ErrorStringOr<void> removeBreakpointForEventListener(Inspector::Protocol::DOM::EventListenerId); |
| Inspector::Protocol::ErrorStringOr<Ref<Inspector::Protocol::DOM::AccessibilityProperties>> getAccessibilityPropertiesForNode(Inspector::Protocol::DOM::NodeId); |
| Inspector::Protocol::ErrorStringOr<std::tuple<String /* searchId */, int /* resultCount */>> performSearch(const String& query, RefPtr<JSON::Array>&& nodeIds, std::optional<bool>&& caseSensitive); |
| Inspector::Protocol::ErrorStringOr<Ref<JSON::ArrayOf<Inspector::Protocol::DOM::NodeId>>> getSearchResults(const String& searchId, int fromIndex, int toIndex); |
| Inspector::Protocol::ErrorStringOr<void> discardSearchResults(const String& searchId); |
| Inspector::Protocol::ErrorStringOr<Ref<Inspector::Protocol::Runtime::RemoteObject>> resolveNode(Inspector::Protocol::DOM::NodeId, const String& objectGroup); |
| Inspector::Protocol::ErrorStringOr<Ref<JSON::ArrayOf<String>>> getAttributes(Inspector::Protocol::DOM::NodeId); |
| #if PLATFORM(IOS_FAMILY) |
| Inspector::Protocol::ErrorStringOr<void> setInspectModeEnabled(bool, RefPtr<JSON::Object>&& highlightConfig); |
| #else |
| Inspector::Protocol::ErrorStringOr<void> setInspectModeEnabled(bool, RefPtr<JSON::Object>&& highlightConfig, std::optional<bool>&& showRulers); |
| #endif |
| Inspector::Protocol::ErrorStringOr<Inspector::Protocol::DOM::NodeId> requestNode(const Inspector::Protocol::Runtime::RemoteObjectId&); |
| Inspector::Protocol::ErrorStringOr<Inspector::Protocol::DOM::NodeId> pushNodeByPathToFrontend(const String& path); |
| Inspector::Protocol::ErrorStringOr<void> hideHighlight(); |
| Inspector::Protocol::ErrorStringOr<void> highlightRect(int x, int y, int width, int height, RefPtr<JSON::Object>&& color, RefPtr<JSON::Object>&& outlineColor, std::optional<bool>&& usePageCoordinates); |
| Inspector::Protocol::ErrorStringOr<void> highlightQuad(Ref<JSON::Array>&& quad, RefPtr<JSON::Object>&& color, RefPtr<JSON::Object>&& outlineColor, std::optional<bool>&& usePageCoordinates); |
| Inspector::Protocol::ErrorStringOr<void> highlightSelector(Ref<JSON::Object>&& highlightConfig, const String& selectorString, const Inspector::Protocol::Network::FrameId&); |
| Inspector::Protocol::ErrorStringOr<void> highlightNode(Ref<JSON::Object>&& highlightConfig, std::optional<Inspector::Protocol::DOM::NodeId>&&, const Inspector::Protocol::Runtime::RemoteObjectId&); |
| Inspector::Protocol::ErrorStringOr<void> highlightNodeList(Ref<JSON::Array>&& nodeIds, Ref<JSON::Object>&& highlightConfig); |
| Inspector::Protocol::ErrorStringOr<void> highlightFrame(const Inspector::Protocol::Network::FrameId&, RefPtr<JSON::Object>&& color, RefPtr<JSON::Object>&& outlineColor); |
| Inspector::Protocol::ErrorStringOr<void> showGridOverlay(Inspector::Protocol::DOM::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); |
| Inspector::Protocol::ErrorStringOr<void> hideGridOverlay(std::optional<Inspector::Protocol::DOM::NodeId>&&); |
| Inspector::Protocol::ErrorStringOr<void> showFlexOverlay(Inspector::Protocol::DOM::NodeId, Ref<JSON::Object>&& flexColor, std::optional<bool>&& showOrderNumbers); |
| Inspector::Protocol::ErrorStringOr<void> hideFlexOverlay(std::optional<Inspector::Protocol::DOM::NodeId>&&); |
| Inspector::Protocol::ErrorStringOr<Inspector::Protocol::DOM::NodeId> moveTo(Inspector::Protocol::DOM::NodeId nodeId, Inspector::Protocol::DOM::NodeId targetNodeId, std::optional<Inspector::Protocol::DOM::NodeId>&& insertBeforeNodeId); |
| Inspector::Protocol::ErrorStringOr<void> undo(); |
| Inspector::Protocol::ErrorStringOr<void> redo(); |
| Inspector::Protocol::ErrorStringOr<void> markUndoableState(); |
| Inspector::Protocol::ErrorStringOr<void> focus(Inspector::Protocol::DOM::NodeId); |
| Inspector::Protocol::ErrorStringOr<void> setInspectedNode(Inspector::Protocol::DOM::NodeId); |
| Inspector::Protocol::ErrorStringOr<void> setAllowEditingUserAgentShadowTrees(bool); |
| |
| // InspectorInstrumentation |
| Inspector::Protocol::DOM::NodeId identifierForNode(Node&); |
| void addEventListenersToNode(Node&); |
| void didInsertDOMNode(Node&); |
| void didRemoveDOMNode(Node&); |
| void willDestroyDOMNode(Node&); |
| void willModifyDOMAttr(Element&, const AtomString& oldValue, const AtomString& newValue); |
| void didModifyDOMAttr(Element&, const AtomString& name, const AtomString& value); |
| void didRemoveDOMAttr(Element&, const AtomString& name); |
| void characterDataModified(CharacterData&); |
| void didInvalidateStyleAttr(Element&); |
| void didPushShadowRoot(Element& host, ShadowRoot&); |
| void willPopShadowRoot(Element& host, ShadowRoot&); |
| void didChangeCustomElementState(Element&); |
| bool handleTouchEvent(Node&); |
| void didCommitLoad(Document*); |
| void frameDocumentUpdated(Frame&); |
| void pseudoElementCreated(PseudoElement&); |
| void pseudoElementDestroyed(PseudoElement&); |
| void didAddEventListener(EventTarget&); |
| void willRemoveEventListener(EventTarget&, const AtomString& eventType, EventListener&, bool capture); |
| bool isEventListenerDisabled(EventTarget&, const AtomString& eventType, EventListener&, bool capture); |
| void eventDidResetAfterDispatch(const Event&); |
| void flexibleBoxRendererBeganLayout(const RenderObject&); |
| void flexibleBoxRendererWrappedToNextLine(const RenderObject&, size_t lineStartItemIndex); |
| |
| // Callbacks that don't directly correspond to an instrumentation entry point. |
| void setDocument(Document*); |
| |
| void styleAttributeInvalidated(const Vector<Element*>& elements); |
| |
| Inspector::Protocol::DOM::NodeId pushStyleableElementToFrontend(const Styleable&); |
| Ref<Inspector::Protocol::DOM::Styleable> pushStyleablePathToFrontend(Inspector::Protocol::ErrorString, const Styleable&); |
| Inspector::Protocol::DOM::NodeId pushNodeToFrontend(Node*); |
| Inspector::Protocol::DOM::NodeId pushNodeToFrontend(Inspector::Protocol::ErrorString&, Inspector::Protocol::DOM::NodeId documentNodeId, Node*); |
| Inspector::Protocol::DOM::NodeId pushNodePathToFrontend(Node*); |
| Inspector::Protocol::DOM::NodeId pushNodePathToFrontend(Inspector::Protocol::ErrorString, Node*); |
| Node* nodeForId(Inspector::Protocol::DOM::NodeId); |
| Inspector::Protocol::DOM::NodeId boundNodeId(const Node*); |
| |
| RefPtr<Inspector::Protocol::Runtime::RemoteObject> resolveNode(Node*, const String& objectGroup); |
| bool handleMousePress(); |
| void mouseDidMoveOverElement(const HitTestResult&, unsigned modifierFlags); |
| void inspect(Node*); |
| void focusNode(); |
| |
| InspectorHistory* history() { return m_history.get(); } |
| Vector<Document*> documents(); |
| Vector<size_t> flexibleBoxRendererCachedItemsAtStartOfLine(const RenderObject&); |
| void reset(); |
| |
| Node* assertNode(Inspector::Protocol::ErrorString&, Inspector::Protocol::DOM::NodeId); |
| Element* assertElement(Inspector::Protocol::ErrorString&, Inspector::Protocol::DOM::NodeId); |
| Document* assertDocument(Inspector::Protocol::ErrorString&, Inspector::Protocol::DOM::NodeId); |
| |
| RefPtr<JSC::Breakpoint> breakpointForEventListener(EventTarget&, const AtomString& eventType, EventListener&, bool capture); |
| Inspector::Protocol::DOM::EventListenerId idForEventListener(EventTarget&, const AtomString& eventType, EventListener&, bool capture); |
| |
| private: |
| #if ENABLE(VIDEO) |
| void mediaMetricsTimerFired(); |
| #endif |
| |
| void highlightMousedOverNode(); |
| void setSearchingForNode(Inspector::Protocol::ErrorString&, bool enabled, RefPtr<JSON::Object>&& highlightConfig, bool showRulers); |
| std::unique_ptr<InspectorOverlay::Highlight::Config> highlightConfigFromInspectorObject(Inspector::Protocol::ErrorString&, RefPtr<JSON::Object>&& highlightInspectorObject); |
| std::unique_ptr<InspectorOverlay::Grid::Config> gridOverlayConfigFromInspectorObject(Inspector::Protocol::ErrorString&, RefPtr<JSON::Object>&& gridOverlayInspectorObject); |
| |
| // Node-related methods. |
| Inspector::Protocol::DOM::NodeId bind(Node&); |
| void unbind(Node&); |
| |
| Node* assertEditableNode(Inspector::Protocol::ErrorString&, Inspector::Protocol::DOM::NodeId); |
| Element* assertEditableElement(Inspector::Protocol::ErrorString&, Inspector::Protocol::DOM::NodeId); |
| |
| void pushChildNodesToFrontend(Inspector::Protocol::DOM::NodeId, int depth = 1); |
| |
| Ref<Inspector::Protocol::DOM::Node> buildObjectForNode(Node*, int depth); |
| Ref<JSON::ArrayOf<String>> buildArrayForElementAttributes(Element*); |
| Ref<JSON::ArrayOf<Inspector::Protocol::DOM::Node>> buildArrayForContainerChildren(Node* container, int depth); |
| RefPtr<JSON::ArrayOf<Inspector::Protocol::DOM::Node>> buildArrayForPseudoElements(const Element&); |
| Ref<Inspector::Protocol::DOM::EventListener> buildObjectForEventListener(const RegisteredEventListener&, Inspector::Protocol::DOM::EventListenerId identifier, EventTarget&, const AtomString& eventType, bool disabled, const RefPtr<JSC::Breakpoint>&); |
| Ref<Inspector::Protocol::DOM::AccessibilityProperties> buildObjectForAccessibilityProperties(Node&); |
| void processAccessibilityChildren(AXCoreObject&, JSON::ArrayOf<Inspector::Protocol::DOM::NodeId>&); |
| |
| Node* nodeForPath(const String& path); |
| Node* nodeForObjectId(const Inspector::Protocol::Runtime::RemoteObjectId&); |
| |
| void discardBindings(); |
| |
| void innerHighlightQuad(std::unique_ptr<FloatQuad>, RefPtr<JSON::Object>&& color, RefPtr<JSON::Object>&& outlineColor, std::optional<bool>&& usePageCoordinates); |
| |
| void destroyedNodesTimerFired(); |
| |
| void relayoutDocument(); |
| |
| Inspector::InjectedScriptManager& m_injectedScriptManager; |
| std::unique_ptr<Inspector::DOMFrontendDispatcher> m_frontendDispatcher; |
| RefPtr<Inspector::DOMBackendDispatcher> m_backendDispatcher; |
| Page& m_inspectedPage; |
| InspectorOverlay* m_overlay { nullptr }; |
| WeakHashMap<Node, Inspector::Protocol::DOM::NodeId> m_nodeToId; |
| HashMap<Inspector::Protocol::DOM::NodeId, WeakPtr<Node>> m_idToNode; |
| HashSet<Inspector::Protocol::DOM::NodeId> m_childrenRequested; |
| Inspector::Protocol::DOM::NodeId m_lastNodeId { 1 }; |
| RefPtr<Document> m_document; |
| typedef HashMap<String, Vector<RefPtr<Node>>> SearchResults; |
| SearchResults m_searchResults; |
| std::unique_ptr<RevalidateStyleAttributeTask> m_revalidateStyleAttrTask; |
| RefPtr<Node> m_nodeToFocus; |
| RefPtr<Node> m_mousedOverNode; |
| RefPtr<Node> m_inspectedNode; |
| std::unique_ptr<InspectorOverlay::Highlight::Config> m_inspectModeHighlightConfig; |
| std::unique_ptr<InspectorHistory> m_history; |
| std::unique_ptr<DOMEditor> m_domEditor; |
| WeakHashMap<RenderObject, Vector<size_t>> m_flexibleBoxRendererCachedItemsAtStartOfLine; |
| |
| Vector<Inspector::Protocol::DOM::NodeId> m_destroyedDetachedNodeIdentifiers; |
| Vector<std::pair<Inspector::Protocol::DOM::NodeId, Inspector::Protocol::DOM::NodeId>> m_destroyedAttachedNodeIdentifiers; |
| Timer m_destroyedNodesTimer; |
| |
| #if ENABLE(VIDEO) |
| Timer m_mediaMetricsTimer; |
| struct MediaMetrics { |
| unsigned displayCompositedFrames { 0 }; |
| bool isPowerEfficient { false }; |
| |
| MediaMetrics() { } |
| |
| MediaMetrics(unsigned displayCompositedFrames) |
| : displayCompositedFrames(displayCompositedFrames) |
| { |
| } |
| }; |
| |
| // The pointer key for this map should not be used for anything other than matching. |
| WeakHashMap<HTMLMediaElement, MediaMetrics> m_mediaMetrics; |
| #endif |
| |
| struct InspectorEventListener { |
| Inspector::Protocol::DOM::EventListenerId identifier { 1 }; |
| RefPtr<EventTarget> eventTarget; |
| RefPtr<EventListener> eventListener; |
| AtomString eventType; |
| bool useCapture { false }; |
| bool disabled { false }; |
| RefPtr<JSC::Breakpoint> breakpoint; |
| |
| InspectorEventListener() { } |
| |
| InspectorEventListener(Inspector::Protocol::DOM::EventListenerId identifier, EventTarget& target, const AtomString& type, EventListener& listener, bool capture) |
| : identifier(identifier) |
| , eventTarget(&target) |
| , eventListener(&listener) |
| , eventType(type) |
| , useCapture(capture) |
| { |
| } |
| |
| bool matches(EventTarget& target, const AtomString& type, EventListener& listener, bool capture) |
| { |
| if (eventTarget.get() != &target) |
| return false; |
| if (eventListener.get() != &listener) |
| return false; |
| if (eventType != type) |
| return false; |
| if (useCapture != capture) |
| return false; |
| return true; |
| } |
| }; |
| |
| friend class EventFiredCallback; |
| |
| HashSet<const Event*> m_dispatchedEvents; |
| HashMap<Inspector::Protocol::DOM::EventListenerId, InspectorEventListener> m_eventListenerEntries; |
| Inspector::Protocol::DOM::EventListenerId m_lastEventListenerId { 1 }; |
| |
| bool m_searchingForNode { false }; |
| bool m_suppressAttributeModifiedEvent { false }; |
| bool m_suppressEventListenerChangedEvent { false }; |
| bool m_documentRequested { false }; |
| bool m_allowEditingUserAgentShadowTrees { false }; |
| }; |
| |
| } // namespace WebCore |