Changeset 187496 in webkit


Ignore:
Timestamp:
Jul 28, 2015 11:47:14 AM (9 years ago)
Author:
commit-queue@webkit.org
Message:

Web Inspector: Show Pseudo Elements in DOM Tree
https://bugs.webkit.org/show_bug.cgi?id=139612

Patch by Joseph Pecoraro <Joseph Pecoraro> on 2015-07-28
Reviewed by Timothy Hatcher.

Source/JavaScriptCore:

  • inspector/protocol/DOM.json:

Add new properties to DOMNode if it is a pseudo element or if it has
pseudo element children. Add new events for if a pseudo element is
added or removed dynamically to an existing DOMNode.

Source/WebCore:

Tests: inspector/css/pseudo-element-matches-for-pseudo-element-node.html

inspector/dom/pseudo-element-dynamic.html
inspector/dom/pseudo-element-static.html

Much of this patch was modelled after the Blink implementation of
pseudo element inspection.

  • dom/PseudoElement.h:
  • dom/PseudoElement.cpp:

(WebCore::PseudoElement::~PseudoElement):
(WebCore::PseudoElement::clearHostElement):
Since InspectorDOMAgent may hold a reference to this PseudoElement we
can't report it as destroyed in the destructor, as that wouldn't be
reached if the inspector holds a reference. Move this to when the
psuedo element is disconnected, which is immediately before destruction.

  • inspector/InspectorCSSAgent.h:
  • inspector/InspectorCSSAgent.cpp:

(WebCore::InspectorCSSAgent::getMatchedStylesForNode):
When computing styles for a pseudo element, compute styles from the
host element for just the pseudo element's pseudo type. Likewise
only include matched results, not inherited or others.

(WebCore::InspectorCSSAgent::buildArrayForMatchedRuleList):
Add the pseudo type to the checker context to try and detect exactly
which selector in a list of selectors matched the pseudo element.

  • inspector/InspectorDOMAgent.h:
  • inspector/InspectorDOMAgent.cpp:

(WebCore::InspectorDOMAgent::unbind):
When unbinding an element, also unbind any pseudo element children
it may have had and bound.

(WebCore::InspectorDOMAgent::assertEditableNode):
(WebCore::InspectorDOMAgent::assertEditableElement):
(WebCore::InspectorDOMAgent::removeNode):
Improve grammar in error message. Don't allow editing pseudo elements.

(WebCore::pseudoElementType):
(WebCore::InspectorDOMAgent::buildObjectForNode):
(WebCore::InspectorDOMAgent::buildArrayForPseudoElements):
If a node is a pseudo element include its pseudoType.
If a node has pseudo element children include them.

(WebCore::InspectorDOMAgent::pseudoElementCreated):
(WebCore::InspectorDOMAgent::pseudoElementDestroyed):
When pseudo elements are dynamically created or destroyed
push pseudo element nodes to the frontend if needed.

  • inspector/InspectorInstrumentation.cpp:

(WebCore::InspectorInstrumentation::pseudoElementCreatedImpl):
(WebCore::InspectorInstrumentation::pseudoElementDestroyedImpl):

  • inspector/InspectorInstrumentation.h:

(WebCore::InspectorInstrumentation::pseudoElementCreated):
(WebCore::InspectorInstrumentation::pseudoElementDestroyed):
(WebCore::InspectorInstrumentation::layerTreeDidChange):
(WebCore::InspectorInstrumentation::renderLayerDestroyed):
Plumbing for pseudo element created/destroyed events.

  • style/StyleResolveTree.cpp:

(WebCore::Style::attachBeforeOrAfterPseudoElementIfNeeded):
This is the only place a pseudo element is created, inform the inspector.

  • inspector/InspectorOverlay.cpp:

(WebCore::buildObjectForElementData):
Update the element data for the node highlight label to include the
host element's selector and the pseudo element selector.

Source/WebInspectorUI:

  • UserInterface/Controllers/DOMTreeManager.js:

(WebInspector.DOMTreeManager.prototype._pseudoElementAdded):
Hook up the new pseudo element DOMNode to the parent.

(WebInspector.DOMTreeManager.prototype._pseudoElementRemoved):
Unhook the pseudo element from its parent.

(WebInspector.DOMTreeManager.prototype._unbind):
When unbinding, unbind any pseudo element children we may have had.

  • UserInterface/Models/DOMNode.js:

(WebInspector.DOMNode.prototype.isPseudoElement):
(WebInspector.DOMNode.prototype.pseudoType):
(WebInspector.DOMNode.prototype.hasPseudoElements):
(WebInspector.DOMNode.prototype.pseudoElements):
New state of a DOMNode may include pseudo elements.

(WebInspector.DOMNode.prototype.appropriateSelectorFor):
A selector for this node includes the selector for the node above it.

  • UserInterface/Protocol/DOMObserver.js:

(WebInspector.DOMObserver.prototype.pseudoElementAdded):
(WebInspector.DOMObserver.prototype.pseudoElementRemoved):
Pass the message on to DOMTreeManager.

  • UserInterface/Views/DOMTreeElement.js:

(WebInspector.DOMTreeElement.prototype.get editable):
Pseudo element nodes are not editable.

(WebInspector.DOMTreeElement.prototype.showChildNode):
(WebInspector.DOMTreeElement.prototype.onpopulate):
(WebInspector.DOMTreeElement.prototype.updateChildren):
(WebInspector.DOMTreeElement.prototype._nodeTitleInfo):
(WebInspector.DOMTreeElement.prototype._singleTextChild):
(WebInspector.DOMTreeElement.prototype._hasVisibleChildren):
(WebInspector.DOMTreeElement.prototype._visibleChildren):
(WebInspector.DOMTreeElement.prototype._updateChildren):
(WebInspector.DOMTreeElement.prototype.adjustCollapsedRange):
(WebInspector.DOMTreeElement.prototype.handleLoadAllChildren):
A DOMTreeElement's children are no longer 1-to-1 to DOMNode's children.
Instead a DOMNode may have a before/after pseudo element child that
are not included in the children list. Update parts of DOMTreeElement
to respect this list of visible children.

  • UserInterface/Views/DOMTreeElementPathComponent.js:

(WebInspector.DOMTreeElementPathComponent):

  • UserInterface/Views/PathComponentIcons.css:

(.dom-pseudo-element-icon .icon):
Styling for the path component when a pseudo element is selected.

  • UserInterface/Views/DOMTreeOutline.css:

(.dom-tree-outline .html-pseudo-element):
(.dom-tree-outline .html-fragment.shadow):
(.webkit-html-fragment.shadow): Deleted.
Styles for pseudo elements in the DOM tree.

  • UserInterface/Views/DOMTreeOutline.js:

(WebInspector.DOMTreeOutline.prototype._hideElement):
Make the hide element selector hide the host element.

  • UserInterface/Views/CSSStyleDetailsSidebarPanel.js:

(WebInspector.CSSStyleDetailsSidebarPanel.prototype.addEventListeners):
(WebInspector.CSSStyleDetailsSidebarPanel.prototype.removeEventListeners):
(WebInspector.CSSStyleDetailsSidebarPanel.prototype._forcedPseudoClassCheckboxChanged):
(WebInspector.CSSStyleDetailsSidebarPanel.prototype._updatePseudoClassCheckboxes):
Pseudo class changes won't happen on pseudo elements, but will
happen on their host element, so listen to and make pseudo class
changes to the host element.

  • UserInterface/Views/RulesStyleDetailsPanel.css:

(.sidebar > .panel.details.css-style .rules > *:first-child:matches(.new-rule)):
Since a pseudo element does not have a style attribute,
give some margin in the style sidebar above the "New Rule"
button so it looks better.

LayoutTests:

  • inspector/css/pseudo-element-matches-for-pseudo-element-node-expected.txt: Added.
  • inspector/css/pseudo-element-matches-for-pseudo-element-node.html: Added.
  • inspector/dom/pseudo-element-dynamic-expected.txt: Added.
  • inspector/dom/pseudo-element-dynamic.html: Added.
  • inspector/dom/pseudo-element-static-expected.txt: Added.
  • inspector/dom/pseudo-element-static.html: Added.
Location:
trunk
Files:
6 added
25 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r187489 r187496  
     12015-07-28  Joseph Pecoraro  <pecoraro@apple.com>
     2
     3        Web Inspector: Show Pseudo Elements in DOM Tree
     4        https://bugs.webkit.org/show_bug.cgi?id=139612
     5
     6        Reviewed by Timothy Hatcher.
     7
     8        * inspector/css/pseudo-element-matches-for-pseudo-element-node-expected.txt: Added.
     9        * inspector/css/pseudo-element-matches-for-pseudo-element-node.html: Added.
     10        * inspector/dom/pseudo-element-dynamic-expected.txt: Added.
     11        * inspector/dom/pseudo-element-dynamic.html: Added.
     12        * inspector/dom/pseudo-element-static-expected.txt: Added.
     13        * inspector/dom/pseudo-element-static.html: Added.
     14
    1152015-07-28  Chris Dumez  <cdumez@apple.com>
    216
  • trunk/Source/JavaScriptCore/ChangeLog

    r187488 r187496  
     12015-07-28  Joseph Pecoraro  <pecoraro@apple.com>
     2
     3        Web Inspector: Show Pseudo Elements in DOM Tree
     4        https://bugs.webkit.org/show_bug.cgi?id=139612
     5
     6        Reviewed by Timothy Hatcher.
     7
     8        * inspector/protocol/DOM.json:
     9        Add new properties to DOMNode if it is a pseudo element or if it has
     10        pseudo element children. Add new events for if a pseudo element is
     11        added or removed dynamically to an existing DOMNode.
     12
    1132015-07-27  Filip Pizlo  <fpizlo@apple.com>
    214
  • trunk/Source/JavaScriptCore/inspector/protocol/DOM.json

    r185784 r187496  
    1313            "type": "integer",
    1414            "description": "Unique DOM node identifier used to reference a node that may not have been pushed to the front-end."
     15        },
     16        {
     17            "id": "PseudoType",
     18            "type": "string",
     19            "enum": ["before", "after"],
     20            "description": "Pseudo element type."
    1521        },
    1622        {
     
    4046                { "name": "name", "type": "string", "optional": true, "description": "<code>Attr</code>'s name." },
    4147                { "name": "value", "type": "string", "optional": true, "description": "<code>Attr</code>'s value." },
     48                { "name": "pseudoType", "$ref": "PseudoType", "optional": true, "description": "Pseudo element type for this node." },
    4249                { "name": "frameId", "$ref": "Network.FrameId", "optional": true, "description": "Frame ID for frame owner elements." },
    4350                { "name": "contentDocument", "$ref": "Node", "optional": true, "description": "Content document for frame owner elements." },
    4451                { "name": "shadowRoots", "type": "array", "optional": true, "items": { "$ref": "Node" }, "description": "Shadow root list for given element host." },
    4552                { "name": "templateContent", "$ref": "Node", "optional": true, "description": "Content document fragment for template elements" },
     53                { "name": "pseudoElements", "type": "array", "items": { "$ref": "Node" }, "optional": true, "description": "Pseudo elements associated with this node." },
    4654                { "name": "role", "type": "string", "optional": true, "description": "Computed value for first recognized role token, default role per element, or overridden role." }
    4755            ],
     
    527535            ],
    528536            "description": "Called when shadow root is popped from the element."
     537        },
     538        {
     539            "name": "pseudoElementAdded",
     540            "parameters": [
     541                { "name": "parentId", "$ref": "NodeId", "description": "Pseudo element's parent element id." },
     542                { "name": "pseudoElement", "$ref": "Node", "description": "The added pseudo element." }
     543            ],
     544            "description": "Called when a pseudo element is added to an element."
     545        },
     546        {
     547            "name": "pseudoElementRemoved",
     548            "parameters": [
     549                { "name": "parentId", "$ref": "NodeId", "description": "Pseudo element's parent element id." },
     550                { "name": "pseudoElementId", "$ref": "NodeId", "description": "The removed pseudo element id." }
     551            ],
     552            "description": "Called when a pseudo element is removed from an element."
    529553        }
    530554    ]
  • trunk/Source/WebCore/ChangeLog

    r187494 r187496  
     12015-07-28  Joseph Pecoraro  <pecoraro@apple.com>
     2
     3        Web Inspector: Show Pseudo Elements in DOM Tree
     4        https://bugs.webkit.org/show_bug.cgi?id=139612
     5
     6        Reviewed by Timothy Hatcher.
     7
     8        Tests: inspector/css/pseudo-element-matches-for-pseudo-element-node.html
     9               inspector/dom/pseudo-element-dynamic.html
     10               inspector/dom/pseudo-element-static.html
     11
     12        Much of this patch was modelled after the Blink implementation of
     13        pseudo element inspection.
     14
     15        * dom/PseudoElement.h:
     16        * dom/PseudoElement.cpp:
     17        (WebCore::PseudoElement::~PseudoElement):
     18        (WebCore::PseudoElement::clearHostElement):
     19        Since InspectorDOMAgent may hold a reference to this PseudoElement we
     20        can't report it as destroyed in the destructor, as that wouldn't be
     21        reached if the inspector holds a reference. Move this to when the
     22        psuedo element is disconnected, which is immediately before destruction.
     23
     24        * inspector/InspectorCSSAgent.h:
     25        * inspector/InspectorCSSAgent.cpp:
     26        (WebCore::InspectorCSSAgent::getMatchedStylesForNode):
     27        When computing styles for a pseudo element, compute styles from the
     28        host element for just the pseudo element's pseudo type. Likewise
     29        only include matched results, not inherited or others.
     30
     31        (WebCore::InspectorCSSAgent::buildArrayForMatchedRuleList):
     32        Add the pseudo type to the checker context to try and detect exactly
     33        which selector in a list of selectors matched the pseudo element.
     34
     35        * inspector/InspectorDOMAgent.h:
     36        * inspector/InspectorDOMAgent.cpp:
     37        (WebCore::InspectorDOMAgent::unbind):
     38        When unbinding an element, also unbind any pseudo element children
     39        it may have had and bound.
     40
     41        (WebCore::InspectorDOMAgent::assertEditableNode):
     42        (WebCore::InspectorDOMAgent::assertEditableElement):
     43        (WebCore::InspectorDOMAgent::removeNode):
     44        Improve grammar in error message. Don't allow editing pseudo elements.
     45
     46        (WebCore::pseudoElementType):
     47        (WebCore::InspectorDOMAgent::buildObjectForNode):
     48        (WebCore::InspectorDOMAgent::buildArrayForPseudoElements):
     49        If a node is a pseudo element include its pseudoType.
     50        If a node has pseudo element children include them.
     51
     52        (WebCore::InspectorDOMAgent::pseudoElementCreated):
     53        (WebCore::InspectorDOMAgent::pseudoElementDestroyed):
     54        When pseudo elements are dynamically created or destroyed
     55        push pseudo element nodes to the frontend if needed.
     56
     57        * inspector/InspectorInstrumentation.cpp:
     58        (WebCore::InspectorInstrumentation::pseudoElementCreatedImpl):
     59        (WebCore::InspectorInstrumentation::pseudoElementDestroyedImpl):
     60        * inspector/InspectorInstrumentation.h:
     61        (WebCore::InspectorInstrumentation::pseudoElementCreated):
     62        (WebCore::InspectorInstrumentation::pseudoElementDestroyed):
     63        (WebCore::InspectorInstrumentation::layerTreeDidChange):
     64        (WebCore::InspectorInstrumentation::renderLayerDestroyed):
     65        Plumbing for pseudo element created/destroyed events.
     66
     67        * style/StyleResolveTree.cpp:
     68        (WebCore::Style::attachBeforeOrAfterPseudoElementIfNeeded):
     69        This is the only place a pseudo element is created, inform the inspector.
     70
     71        * inspector/InspectorOverlay.cpp:
     72        (WebCore::buildObjectForElementData):
     73        Update the element data for the node highlight label to include the
     74        host element's selector and the pseudo element selector.
     75
    1762015-07-28  Tim Horton  <timothy_horton@apple.com>
    277
  • trunk/Source/WebCore/dom/PseudoElement.cpp

    r177925 r187496  
    6868{
    6969    ASSERT(!m_hostElement);
     70}
     71
     72void PseudoElement::clearHostElement()
     73{
    7074    InspectorInstrumentation::pseudoElementDestroyed(document().page(), *this);
     75
     76    m_hostElement = nullptr;
    7177}
    7278
  • trunk/Source/WebCore/dom/PseudoElement.h

    r177751 r187496  
    4444
    4545    Element* hostElement() const { return m_hostElement; }
    46     void clearHostElement() { m_hostElement = nullptr; }
     46    void clearHostElement();
    4747
    4848    virtual RefPtr<RenderStyle> customStyleForRenderer(RenderStyle& parentStyle) override;
  • trunk/Source/WebCore/inspector/InspectorCSSAgent.cpp

    r187249 r187496  
    4646#include "Node.h"
    4747#include "NodeList.h"
     48#include "PseudoElement.h"
    4849#include "RenderNamedFlowFragment.h"
    4950#include "SVGStyleElement.h"
     
    511512        return;
    512513
     514    Element* originalElement = element;
     515    PseudoId elementPseudoId = element->pseudoId();
     516    if (elementPseudoId) {
     517        element = downcast<PseudoElement>(*element).hostElement();
     518        if (!element) {
     519            errorString = ASCIILiteral("Pseudo element has no parent");
     520            return;
     521        }
     522    }
     523
    513524    // Matched rules.
    514525    StyleResolver& styleResolver = element->document().ensureStyleResolver();
    515     auto matchedRules = styleResolver.styleRulesForElement(element, StyleResolver::AllCSSRules);
    516     matchedCSSRules = buildArrayForMatchedRuleList(matchedRules, styleResolver, element);
    517 
    518     // Pseudo elements.
    519     if (!includePseudo || *includePseudo) {
    520         auto pseudoElements = Inspector::Protocol::Array<Inspector::Protocol::CSS::PseudoIdMatches>::create();
    521         for (PseudoId pseudoId = FIRST_PUBLIC_PSEUDOID; pseudoId < AFTER_LAST_INTERNAL_PSEUDOID; pseudoId = static_cast<PseudoId>(pseudoId + 1)) {
    522             auto matchedRules = styleResolver.pseudoStyleRulesForElement(element, pseudoId, StyleResolver::AllCSSRules);
    523             if (!matchedRules.isEmpty()) {
    524                 auto matches = Inspector::Protocol::CSS::PseudoIdMatches::create()
    525                     .setPseudoId(static_cast<int>(pseudoId))
    526                     .setMatches(buildArrayForMatchedRuleList(matchedRules, styleResolver, element))
     526    auto matchedRules = styleResolver.pseudoStyleRulesForElement(element, elementPseudoId, StyleResolver::AllCSSRules);
     527    matchedCSSRules = buildArrayForMatchedRuleList(matchedRules, styleResolver, element, elementPseudoId);
     528
     529    if (!originalElement->isPseudoElement()) {
     530        // Pseudo elements.
     531        if (!includePseudo || *includePseudo) {
     532            auto pseudoElements = Inspector::Protocol::Array<Inspector::Protocol::CSS::PseudoIdMatches>::create();
     533            for (PseudoId pseudoId = FIRST_PUBLIC_PSEUDOID; pseudoId < AFTER_LAST_INTERNAL_PSEUDOID; pseudoId = static_cast<PseudoId>(pseudoId + 1)) {
     534                auto matchedRules = styleResolver.pseudoStyleRulesForElement(element, pseudoId, StyleResolver::AllCSSRules);
     535                if (!matchedRules.isEmpty()) {
     536                    auto matches = Inspector::Protocol::CSS::PseudoIdMatches::create()
     537                        .setPseudoId(static_cast<int>(pseudoId))
     538                        .setMatches(buildArrayForMatchedRuleList(matchedRules, styleResolver, element, pseudoId))
     539                        .release();
     540                    pseudoElements->addItem(WTF::move(matches));
     541                }
     542            }
     543
     544            pseudoIdMatches = WTF::move(pseudoElements);
     545        }
     546
     547        // Inherited styles.
     548        if (!includeInherited || *includeInherited) {
     549            auto entries = Inspector::Protocol::Array<Inspector::Protocol::CSS::InheritedStyleEntry>::create();
     550            Element* parentElement = element->parentElement();
     551            while (parentElement) {
     552                StyleResolver& parentStyleResolver = parentElement->document().ensureStyleResolver();
     553                auto parentMatchedRules = parentStyleResolver.styleRulesForElement(parentElement, StyleResolver::AllCSSRules);
     554                auto entry = Inspector::Protocol::CSS::InheritedStyleEntry::create()
     555                    .setMatchedCSSRules(buildArrayForMatchedRuleList(parentMatchedRules, styleResolver, parentElement, NOPSEUDO))
    527556                    .release();
    528                 pseudoElements->addItem(WTF::move(matches));
     557                if (parentElement->style() && parentElement->style()->length()) {
     558                    if (InspectorStyleSheetForInlineStyle* styleSheet = asInspectorStyleSheet(parentElement))
     559                        entry->setInlineStyle(styleSheet->buildObjectForStyle(styleSheet->styleForId(InspectorCSSId(styleSheet->id(), 0))));
     560                }
     561
     562                entries->addItem(WTF::move(entry));
     563                parentElement = parentElement->parentElement();
    529564            }
     565
     566            inheritedEntries = WTF::move(entries);
    530567        }
    531 
    532         pseudoIdMatches = WTF::move(pseudoElements);
    533     }
    534 
    535     // Inherited styles.
    536     if (!includeInherited || *includeInherited) {
    537         auto entries = Inspector::Protocol::Array<Inspector::Protocol::CSS::InheritedStyleEntry>::create();
    538         Element* parentElement = element->parentElement();
    539         while (parentElement) {
    540             StyleResolver& parentStyleResolver = parentElement->document().ensureStyleResolver();
    541             auto parentMatchedRules = parentStyleResolver.styleRulesForElement(parentElement, StyleResolver::AllCSSRules);
    542             auto entry = Inspector::Protocol::CSS::InheritedStyleEntry::create()
    543                 .setMatchedCSSRules(buildArrayForMatchedRuleList(parentMatchedRules, styleResolver, parentElement))
    544                 .release();
    545             if (parentElement->style() && parentElement->style()->length()) {
    546                 InspectorStyleSheetForInlineStyle* styleSheet = asInspectorStyleSheet(parentElement);
    547                 if (styleSheet)
    548                     entry->setInlineStyle(styleSheet->buildObjectForStyle(styleSheet->styleForId(InspectorCSSId(styleSheet->id(), 0))));
    549             }
    550 
    551             entries->addItem(WTF::move(entry));
    552             parentElement = parentElement->parentElement();
    553         }
    554 
    555         inheritedEntries = WTF::move(entries);
    556568    }
    557569}
     
    925937}
    926938
    927 RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::RuleMatch>> InspectorCSSAgent::buildArrayForMatchedRuleList(const Vector<RefPtr<StyleRule>>& matchedRules, StyleResolver& styleResolver, Element* element)
     939RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::RuleMatch>> InspectorCSSAgent::buildArrayForMatchedRuleList(const Vector<RefPtr<StyleRule>>& matchedRules, StyleResolver& styleResolver, Element* element, PseudoId psuedoId)
    928940{
    929941    auto result = Inspector::Protocol::Array<Inspector::Protocol::CSS::RuleMatch>::create();
    930942
    931943    SelectorChecker::CheckingContext context(SelectorChecker::Mode::CollectingRules);
     944    context.pseudoId = psuedoId ? psuedoId : element->pseudoId();
    932945    SelectorChecker selectorChecker(element->document());
    933946
  • trunk/Source/WebCore/inspector/InspectorCSSAgent.h

    r187249 r187496  
    150150    RefPtr<Inspector::Protocol::CSS::CSSRule> buildObjectForRule(StyleRule*, StyleResolver&, Element*);
    151151    RefPtr<Inspector::Protocol::CSS::CSSRule> buildObjectForRule(CSSStyleRule*);
    152     RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::RuleMatch>> buildArrayForMatchedRuleList(const Vector<RefPtr<StyleRule>>&, StyleResolver&, Element*);
     152    RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::RuleMatch>> buildArrayForMatchedRuleList(const Vector<RefPtr<StyleRule>>&, StyleResolver&, Element*, PseudoId);
    153153    RefPtr<Inspector::Protocol::CSS::CSSStyle> buildObjectForAttributesStyle(Element*);
    154154    RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::Region>> buildArrayForRegions(ErrorString&, RefPtr<NodeList>&&, int documentNodeId);
  • trunk/Source/WebCore/inspector/InspectorDOMAgent.cpp

    r186279 r187496  
    7676#include "Page.h"
    7777#include "Pasteboard.h"
     78#include "PseudoElement.h"
    7879#include "RenderStyle.h"
    7980#include "RenderStyleConstants.h"
     
    333334
    334335    if (is<Element>(*node)) {
    335         if (ShadowRoot* root = downcast<Element>(*node).shadowRoot())
     336        Element& element = downcast<Element>(*node);
     337        if (ShadowRoot* root = element.shadowRoot())
    336338            unbind(root, nodesMap);
     339        if (PseudoElement* beforeElement = element.beforePseudoElement())
     340            unbind(beforeElement, nodesMap);
     341        if (PseudoElement* afterElement = element.afterPseudoElement())
     342            unbind(afterElement, nodesMap);
    337343    }
    338344
     
    393399        return nullptr;
    394400    if (node->isInShadowTree()) {
    395         errorString = ASCIILiteral("Can not edit nodes from shadow trees");
     401        errorString = ASCIILiteral("Cannot edit nodes from shadow trees");
     402        return nullptr;
     403    }
     404    if (node->isPseudoElement()) {
     405        errorString = ASCIILiteral("Cannot edit pseudo elements");
    396406        return nullptr;
    397407    }
     
    405415        return nullptr;
    406416    if (element->isInShadowTree()) {
    407         errorString = ASCIILiteral("Can not edit elements from shadow trees");
     417        errorString = ASCIILiteral("Cannot edit elements from shadow trees");
     418        return nullptr;
     419    }
     420    if (element->isPseudoElement()) {
     421        errorString = ASCIILiteral("Cannot edit pseudo elements");
    408422        return nullptr;
    409423    }
     
    699713    ContainerNode* parentNode = node->parentNode();
    700714    if (!parentNode) {
    701         errorString = ASCIILiteral("Can not remove detached node");
     715        errorString = ASCIILiteral("Cannot remove detached node");
    702716        return;
    703717    }
     
    12551269}
    12561270
     1271static bool pseudoElementType(PseudoId pseudoId, Inspector::Protocol::DOM::PseudoType* type)
     1272{
     1273    switch (pseudoId) {
     1274    case BEFORE:
     1275        *type = Inspector::Protocol::DOM::PseudoType::Before;
     1276        return true;
     1277    case AFTER:
     1278        *type = Inspector::Protocol::DOM::PseudoType::After;
     1279        return true;
     1280    default:
     1281        return false;
     1282    }
     1283}
     1284
    12571285Ref<Inspector::Protocol::DOM::Node> InspectorDOMAgent::buildObjectForNode(Node* node, int depth, NodeToIdMap* nodesMap)
    12581286{
     
    13281356#endif
    13291357
     1358        if (element.pseudoId()) {
     1359            Inspector::Protocol::DOM::PseudoType pseudoType;
     1360            if (pseudoElementType(element.pseudoId(), &pseudoType))
     1361                value->setPseudoType(pseudoType);
     1362        } else {
     1363            if (auto pseudoElements = buildArrayForPseudoElements(element, nodesMap))
     1364                value->setPseudoElements(WTF::move(pseudoElements));
     1365        }
     1366
    13301367    } else if (is<Document>(*node)) {
    13311368        Document& document = downcast<Document>(*node);
     
    13931430    }
    13941431    return WTF::move(children);
     1432}
     1433
     1434RefPtr<Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>> InspectorDOMAgent::buildArrayForPseudoElements(const Element& element, NodeToIdMap* nodesMap)
     1435{
     1436    PseudoElement* beforeElement = element.beforePseudoElement();
     1437    PseudoElement* afterElement = element.afterPseudoElement();
     1438    if (!beforeElement && !afterElement)
     1439        return nullptr;
     1440
     1441    auto pseudoElements = Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>::create();
     1442    if (beforeElement)
     1443        pseudoElements->addItem(buildObjectForNode(beforeElement, 0, nodesMap));
     1444    if (afterElement)
     1445        pseudoElements->addItem(buildObjectForNode(afterElement, 0, nodesMap));
     1446    return WTF::move(pseudoElements);
    13951447}
    13961448
     
    19582010}
    19592011
     2012void InspectorDOMAgent::pseudoElementCreated(PseudoElement& pseudoElement)
     2013{
     2014    Element* parent = pseudoElement.hostElement();
     2015    if (!parent)
     2016        return;
     2017
     2018    int parentId = m_documentNodeToIdMap.get(parent);
     2019    if (!parentId)
     2020        return;
     2021
     2022    pushChildNodesToFrontend(parentId, 1);
     2023    m_frontendDispatcher->pseudoElementAdded(parentId, buildObjectForNode(&pseudoElement, 0, &m_documentNodeToIdMap));
     2024}
     2025
     2026void InspectorDOMAgent::pseudoElementDestroyed(PseudoElement& pseudoElement)
     2027{
     2028    int pseudoElementId = m_documentNodeToIdMap.get(&pseudoElement);
     2029    if (!pseudoElementId)
     2030        return;
     2031
     2032    // If a PseudoElement is bound, its parent element must have been bound.
     2033    Element* parent = pseudoElement.hostElement();
     2034    ASSERT(parent);
     2035    int parentId = m_documentNodeToIdMap.get(parent);
     2036    ASSERT(parentId);
     2037
     2038    unbind(&pseudoElement, &m_documentNodeToIdMap);
     2039    m_frontendDispatcher->pseudoElementRemoved(parentId, pseudoElementId);
     2040}
     2041
    19602042Node* InspectorDOMAgent::nodeForPath(const String& path)
    19612043{
  • trunk/Source/WebCore/inspector/InspectorDOMAgent.h

    r185784 r187496  
    170170    void didCommitLoad(Document*);
    171171    void frameDocumentUpdated(Frame*);
     172    void pseudoElementCreated(PseudoElement&);
     173    void pseudoElementDestroyed(PseudoElement&);
    172174
    173175    // Callbacks that don't directly correspond to an instrumentation entry point.
     
    234236    Ref<Inspector::Protocol::Array<String>> buildArrayForElementAttributes(Element*);
    235237    Ref<Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>> buildArrayForContainerChildren(Node* container, int depth, NodeToIdMap* nodesMap);
     238    RefPtr<Inspector::Protocol::Array<Inspector::Protocol::DOM::Node>> buildArrayForPseudoElements(const Element&, NodeToIdMap* nodesMap);
    236239    Ref<Inspector::Protocol::DOM::EventListener> buildObjectForEventListener(const RegisteredEventListener&, const AtomicString& eventType, Node*, const String* objectGroupId);
    237240    RefPtr<Inspector::Protocol::DOM::AccessibilityProperties> buildObjectForAccessibilityProperties(Node*);
  • trunk/Source/WebCore/inspector/InspectorInstrumentation.cpp

    r186133 r187496  
    209209}
    210210
     211void InspectorInstrumentation::pseudoElementCreatedImpl(InstrumentingAgents& instrumentingAgents, PseudoElement& pseudoElement)
     212{
     213    if (InspectorDOMAgent* domAgent = instrumentingAgents.inspectorDOMAgent())
     214        domAgent->pseudoElementCreated(pseudoElement);
     215}
     216
     217void InspectorInstrumentation::pseudoElementDestroyedImpl(InstrumentingAgents& instrumentingAgents, PseudoElement& pseudoElement)
     218{
     219    if (InspectorDOMAgent* domAgent = instrumentingAgents.inspectorDOMAgent())
     220        domAgent->pseudoElementDestroyed(pseudoElement);
     221    if (InspectorLayerTreeAgent* layerTreeAgent = instrumentingAgents.inspectorLayerTreeAgent())
     222        layerTreeAgent->pseudoElementDestroyed(pseudoElement);
     223}
     224
    211225void InspectorInstrumentation::didCreateNamedFlowImpl(InstrumentingAgents& instrumentingAgents, Document* document, WebKitNamedFlow& namedFlow)
    212226{
     
    12751289}
    12761290
    1277 void InspectorInstrumentation::pseudoElementDestroyedImpl(InstrumentingAgents& instrumentingAgents, PseudoElement& pseudoElement)
    1278 {
    1279     if (InspectorLayerTreeAgent* layerTreeAgent = instrumentingAgents.inspectorLayerTreeAgent())
    1280         layerTreeAgent->pseudoElementDestroyed(pseudoElement);
    1281 }
    1282 
    12831291} // namespace WebCore
  • trunk/Source/WebCore/inspector/InspectorInstrumentation.h

    r186133 r187496  
    7676class Database;
    7777class Document;
    78 class Element;
    7978class DocumentLoader;
    8079class DocumentStyleSheetCollection;
     80class Element;
    8181class GraphicsContext;
    8282class HTTPHeaderMap;
     
    8686class InspectorTimelineAgent;
    8787class InstrumentingAgents;
    88 class URL;
    8988class Node;
    9089class PseudoElement;
     
    101100class StyleRule;
    102101class ThreadableLoaderClient;
     102class URL;
    103103class WorkerGlobalScope;
    104104class WorkerGlobalScopeProxy;
     
    127127    static void didPushShadowRoot(Element& host, ShadowRoot&);
    128128    static void willPopShadowRoot(Element& host, ShadowRoot&);
     129    static void pseudoElementCreated(Page*, PseudoElement&);
     130    static void pseudoElementDestroyed(Page*, PseudoElement&);
    129131    static void didCreateNamedFlow(Document*, WebKitNamedFlow&);
    130132    static void willRemoveNamedFlow(Document*, WebKitNamedFlow&);
     
    276278    static void layerTreeDidChange(Page*);
    277279    static void renderLayerDestroyed(Page*, const RenderLayer&);
    278     static void pseudoElementDestroyed(Page*, PseudoElement&);
    279280
    280281    static void frontendCreated() { s_frontendCounter += 1; }
     
    307308    static void didPushShadowRootImpl(InstrumentingAgents&, Element& host, ShadowRoot&);
    308309    static void willPopShadowRootImpl(InstrumentingAgents&, Element& host, ShadowRoot&);
     310    static void pseudoElementCreatedImpl(InstrumentingAgents&, PseudoElement&);
     311    static void pseudoElementDestroyedImpl(InstrumentingAgents&, PseudoElement&);
    309312    static void didCreateNamedFlowImpl(InstrumentingAgents&, Document*, WebKitNamedFlow&);
    310313    static void willRemoveNamedFlowImpl(InstrumentingAgents&, Document*, WebKitNamedFlow&);
     
    450453    static void layerTreeDidChangeImpl(InstrumentingAgents&);
    451454    static void renderLayerDestroyedImpl(InstrumentingAgents&, const RenderLayer&);
    452     static void pseudoElementDestroyedImpl(InstrumentingAgents&, PseudoElement&);
    453455
    454456    static InstrumentingAgents* instrumentingAgentsForPage(Page&);
     
    568570}
    569571
     572inline void InspectorInstrumentation::pseudoElementCreated(Page* page, PseudoElement& pseudoElement)
     573{
     574    FAST_RETURN_IF_NO_FRONTENDS(void());
     575    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForPage(page))
     576        pseudoElementCreatedImpl(*instrumentingAgents, pseudoElement);
     577}
     578
     579inline void InspectorInstrumentation::pseudoElementDestroyed(Page* page, PseudoElement& pseudoElement)
     580{
     581    FAST_RETURN_IF_NO_FRONTENDS(void());
     582    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForPage(page))
     583        pseudoElementDestroyedImpl(*instrumentingAgents, pseudoElement);
     584}
     585
    570586inline void InspectorInstrumentation::didCreateNamedFlow(Document* document, WebKitNamedFlow& namedFlow)
    571587{
     
    13091325inline void InspectorInstrumentation::layerTreeDidChange(Page* page)
    13101326{
     1327    FAST_RETURN_IF_NO_FRONTENDS(void());
    13111328    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForPage(page))
    13121329        layerTreeDidChangeImpl(*instrumentingAgents);
     
    13151332inline void InspectorInstrumentation::renderLayerDestroyed(Page* page, const RenderLayer& renderLayer)
    13161333{
     1334    FAST_RETURN_IF_NO_FRONTENDS(void());
    13171335    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForPage(page))
    13181336        renderLayerDestroyedImpl(*instrumentingAgents, renderLayer);
    1319 }
    1320 
    1321 inline void InspectorInstrumentation::pseudoElementDestroyed(Page* page, PseudoElement& pseudoElement)
    1322 {
    1323     if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForPage(page))
    1324         pseudoElementDestroyedImpl(*instrumentingAgents, pseudoElement);
    13251337}
    13261338
  • trunk/Source/WebCore/inspector/InspectorOverlay.cpp

    r187492 r187496  
    4242#include "PageConfiguration.h"
    4343#include "PolygonShape.h"
     44#include "PseudoElement.h"
    4445#include "RectangleShape.h"
    4546#include "RenderBoxModelObject.h"
     
    687688        return nullptr;
    688689
    689     Element& element = downcast<Element>(*node);
     690    Element* effectiveElement = downcast<Element>(node);
     691    if (node->isPseudoElement()) {
     692        Element* hostElement = downcast<PseudoElement>(*node).hostElement();
     693        if (!hostElement)
     694            return nullptr;
     695        effectiveElement = hostElement;
     696    }
     697
     698    Element& element = *effectiveElement;
    690699    bool isXHTML = element.document().isXHTMLDocument();
    691 
    692700    auto elementData = Inspector::Protocol::OverlayTypes::ElementData::create()
    693701        .setTagName(isXHTML ? element.nodeName() : element.nodeName().lower())
     
    695703        .release();
    696704
    697     HashSet<AtomicString> usedClassNames;
     705    StringBuilder classNames;
    698706    if (element.hasClass() && is<StyledElement>(element)) {
    699         StringBuilder classNames;
     707        HashSet<AtomicString> usedClassNames;
    700708        const SpaceSplitString& classNamesString = downcast<StyledElement>(element).classNames();
    701709        size_t classNameCount = classNamesString.size();
     
    708716            classNames.append(className);
    709717        }
     718    }
     719    if (node->isPseudoElement()) {
     720        if (node->pseudoId() == BEFORE)
     721            classNames.appendLiteral("::before");
     722        else if (node->pseudoId() == AFTER)
     723            classNames.appendLiteral("::after");
     724    }
     725    if (!classNames.isEmpty())
    710726        elementData->setClassName(classNames.toString());
    711     }
    712727
    713728    RenderElement* renderer = element.renderer();
  • trunk/Source/WebCore/style/StyleResolveTree.cpp

    r184990 r187496  
    3434#include "FlowThreadController.h"
    3535#include "InsertionPoint.h"
     36#include "InspectorInstrumentation.h"
    3637#include "LoaderStrategy.h"
    3738#include "MainFrame.h"
     
    465466        return;
    466467    Ref<PseudoElement> pseudoElement = PseudoElement::create(current, pseudoId);
     468    InspectorInstrumentation::pseudoElementCreated(pseudoElement->document().page(), pseudoElement.get());
    467469    setBeforeOrAfterPseudoElement(current, pseudoElement.copyRef(), pseudoId);
    468470    attachRenderTree(pseudoElement.get(), *current.renderStyle(), renderTreePosition, nullptr);
  • trunk/Source/WebInspectorUI/ChangeLog

    r187480 r187496  
     12015-07-28  Joseph Pecoraro  <pecoraro@apple.com>
     2
     3        Web Inspector: Show Pseudo Elements in DOM Tree
     4        https://bugs.webkit.org/show_bug.cgi?id=139612
     5
     6        Reviewed by Timothy Hatcher.
     7
     8        * UserInterface/Controllers/DOMTreeManager.js:
     9        (WebInspector.DOMTreeManager.prototype._pseudoElementAdded):
     10        Hook up the new pseudo element DOMNode to the parent.
     11
     12        (WebInspector.DOMTreeManager.prototype._pseudoElementRemoved):
     13        Unhook the pseudo element from its parent.
     14
     15        (WebInspector.DOMTreeManager.prototype._unbind):
     16        When unbinding, unbind any pseudo element children we may have had.
     17
     18        * UserInterface/Models/DOMNode.js:
     19        (WebInspector.DOMNode.prototype.isPseudoElement):
     20        (WebInspector.DOMNode.prototype.pseudoType):
     21        (WebInspector.DOMNode.prototype.hasPseudoElements):
     22        (WebInspector.DOMNode.prototype.pseudoElements):
     23        New state of a DOMNode may include pseudo elements.
     24
     25        (WebInspector.DOMNode.prototype.appropriateSelectorFor):
     26        A selector for this node includes the selector for the node above it.
     27
     28        * UserInterface/Protocol/DOMObserver.js:
     29        (WebInspector.DOMObserver.prototype.pseudoElementAdded):
     30        (WebInspector.DOMObserver.prototype.pseudoElementRemoved):
     31        Pass the message on to DOMTreeManager.
     32
     33        * UserInterface/Views/DOMTreeElement.js:
     34        (WebInspector.DOMTreeElement.prototype.get editable):
     35        Pseudo element nodes are not editable.
     36
     37        (WebInspector.DOMTreeElement.prototype.showChildNode):
     38        (WebInspector.DOMTreeElement.prototype.onpopulate):
     39        (WebInspector.DOMTreeElement.prototype.updateChildren):
     40        (WebInspector.DOMTreeElement.prototype._nodeTitleInfo):
     41        (WebInspector.DOMTreeElement.prototype._singleTextChild):
     42        (WebInspector.DOMTreeElement.prototype._hasVisibleChildren):
     43        (WebInspector.DOMTreeElement.prototype._visibleChildren):
     44        (WebInspector.DOMTreeElement.prototype._updateChildren):
     45        (WebInspector.DOMTreeElement.prototype.adjustCollapsedRange):
     46        (WebInspector.DOMTreeElement.prototype.handleLoadAllChildren):
     47        A DOMTreeElement's children are no longer 1-to-1 to DOMNode's children.
     48        Instead a DOMNode may have a before/after pseudo element child that
     49        are not included in the children list. Update parts of DOMTreeElement
     50        to respect this list of visible children.
     51
     52        * UserInterface/Views/DOMTreeElementPathComponent.js:
     53        (WebInspector.DOMTreeElementPathComponent):
     54        * UserInterface/Views/PathComponentIcons.css:
     55        (.dom-pseudo-element-icon .icon):
     56        Styling for the path component when a pseudo element is selected.
     57
     58        * UserInterface/Views/DOMTreeOutline.css:
     59        (.dom-tree-outline .html-pseudo-element):
     60        (.dom-tree-outline .html-fragment.shadow):
     61        (.webkit-html-fragment.shadow): Deleted.
     62        Styles for pseudo elements in the DOM tree.
     63
     64        * UserInterface/Views/DOMTreeOutline.js:
     65        (WebInspector.DOMTreeOutline.prototype._hideElement):
     66        Make the hide element selector hide the host element.
     67
     68        * UserInterface/Views/CSSStyleDetailsSidebarPanel.js:
     69        (WebInspector.CSSStyleDetailsSidebarPanel.prototype.addEventListeners):
     70        (WebInspector.CSSStyleDetailsSidebarPanel.prototype.removeEventListeners):
     71        (WebInspector.CSSStyleDetailsSidebarPanel.prototype._forcedPseudoClassCheckboxChanged):
     72        (WebInspector.CSSStyleDetailsSidebarPanel.prototype._updatePseudoClassCheckboxes):
     73        Pseudo class changes won't happen on pseudo elements, but will
     74        happen on their host element, so listen to and make pseudo class
     75        changes to the host element.
     76
     77        * UserInterface/Views/RulesStyleDetailsPanel.css:
     78        (.sidebar > .panel.details.css-style .rules > *:first-child:matches(.new-rule)):
     79        Since a pseudo element does not have a style attribute,
     80        give some margin in the style sidebar above the "New Rule"
     81        button so it looks better.
     82
    1832015-07-27  Matt Baker  <mattbaker@apple.com>
    284
  • trunk/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js

    r187450 r187496  
    247247        parent._removeChild(node);
    248248        this._unbind(node);
    249         this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeRemoved, {node:node, parent});
     249        this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeRemoved, {node, parent});
     250    }
     251
     252    _pseudoElementAdded(parentId, pseudoElement)
     253    {
     254        var parent = this._idToDOMNode[parentId];
     255        if (!parent)
     256            return;
     257
     258        var node = new WebInspector.DOMNode(this, parent.ownerDocument, false, pseudoElement);
     259        node.parentNode = parent;
     260        this._idToDOMNode[node.id] = node;
     261        console.assert(!parent.pseudoElements().get(node.pseudoType()));
     262        parent.pseudoElements().set(node.pseudoType(), node);
     263        this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeInserted, {node, parent});
     264    }
     265
     266    _pseudoElementRemoved(parentId, pseudoElementId)
     267    {
     268        var pseudoElement = this._idToDOMNode[pseudoElementId];
     269        if (!pseudoElement)
     270            return;
     271
     272        var parent = pseudoElement.parentNode;
     273        console.assert(parent);
     274        console.assert(parent.id === parentId);
     275        if (!parent)
     276            return;
     277
     278        parent._removeChild(pseudoElement);
     279        this._unbind(pseudoElement);
     280        this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeRemoved, {node: pseudoElement, parent});
    250281    }
    251282
     
    255286
    256287        delete this._idToDOMNode[node.id];
     288
    257289        for (var i = 0; node.children && i < node.children.length; ++i)
    258290            this._unbind(node.children[i]);
     291
     292        var pseudoElements = node.pseudoElements();
     293        for (var pseudoElement of pseudoElements)
     294            this._unbind(pseudoElement);
     295
     296        // FIXME: Handle shadow roots.
     297        // FIXME: Handle template content.
    259298    }
    260299
  • trunk/Source/WebInspectorUI/UserInterface/Models/DOMNode.js

    r185784 r187496  
    4747        this._localName = payload.localName;
    4848        this._nodeValue = payload.nodeValue;
     49        this._pseudoType = payload.pseudoType;
    4950        this._computedRole = payload.role;
    5051
     
    7980        }
    8081
     82        // FIXME: Handle templateContent.
     83
    8184        if (payload.children)
    8285            this._setChildrenPayload(payload.children);
     86
     87        this._pseudoElements = new Map;
     88        if (payload.pseudoElements) {
     89            for (var i = 0; i < payload.pseudoElements.length; ++i) {
     90                var node = new WebInspector.DOMNode(this._domAgent, this.ownerDocument, this._isInShadowTree, payload.pseudoElements[i]);
     91                node.parentNode = this;
     92                this._pseudoElements.set(node.pseudoType(), node);
     93            }
     94        }
    8395
    8496        if (payload.contentDocument) {
     
    222234    }
    223235
     236    isPseudoElement()
     237    {
     238        return this._pseudoType !== undefined;
     239    }
     240
    224241    nodeType()
    225242    {
     
    245262    {
    246263        return this._localName;
     264    }
     265
     266    pseudoType()
     267    {
     268        return this._pseudoType;
     269    }
     270
     271    hasPseudoElements()
     272    {
     273        return this._pseudoElements.size > 0;
     274    }
     275
     276    pseudoElements()
     277    {
     278        return this._pseudoElements;
     279    }
     280
     281    beforePseudoElement()
     282    {
     283        return this._pseudoElements.get(WebInspector.DOMNode.PseudoElementType.Before) || null;
     284    }
     285
     286    afterPseudoElement()
     287    {
     288        return this._pseudoElements.get(WebInspector.DOMNode.PseudoElementType.After) || null;
    247289    }
    248290
     
    408450    appropriateSelectorFor(justSelector)
    409451    {
     452        if (this.isPseudoElement())
     453            return this.parentNode.appropriateSelectorFor() + "::" + this._pseudoType;
     454
    410455        var lowerCaseName = this.localName() || this.nodeName().toLowerCase();
    411456
     
    476521    _removeChild(node)
    477522    {
    478         this._children.splice(this._children.indexOf(node), 1);
     523        // FIXME: Handle removal if this is a shadow root.
     524        if (node.isPseudoElement())
     525            this._pseudoElements.delete(node.pseudoType());
     526        else
     527            this._children.splice(this._children.indexOf(node), 1);
     528
    479529        node.parentNode = null;
    480530        this._renumber();
     
    489539        this._children = this._shadowRoots.slice();
    490540        for (var i = 0; i < payloads.length; ++i) {
    491             var payload = payloads[i];
    492             var node = new WebInspector.DOMNode(this._domAgent, this.ownerDocument, this._isInShadowTree, payload);
     541            var node = new WebInspector.DOMNode(this._domAgent, this.ownerDocument, this._isInShadowTree, payloads[i]);
    493542            this._children.push(node);
    494543        }
     
    598647    AttributeRemoved: "dom-node-attribute-removed"
    599648};
     649
     650WebInspector.DOMNode.PseudoElementType = {
     651    Before: "before",
     652    After: "after",
     653};
  • trunk/Source/WebInspectorUI/UserInterface/Protocol/DOMObserver.js

    r182039 r187496  
    8282        WebInspector.domTreeManager._childNodeRemoved(parentNodeId, nodeId);
    8383    }
     84
     85    pseudoElementAdded(parentNodeId, pseudoElement)
     86    {
     87        WebInspector.domTreeManager._pseudoElementAdded(parentNodeId, pseudoElement);
     88    }
     89
     90    pseudoElementRemoved(parentNodeId, pseudoElementId)
     91    {
     92        WebInspector.domTreeManager._pseudoElementRemoved(parentNodeId, pseudoElementId);
     93    }
    8494};
  • trunk/Source/WebInspectorUI/UserInterface/Views/CSSStyleDetailsSidebarPanel.js

    r187402 r187496  
    158158    addEventListeners()
    159159    {
    160         this.domNode.addEventListener(WebInspector.DOMNode.Event.EnabledPseudoClassesChanged, this._updatePseudoClassCheckboxes, this);
     160        var effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode;
     161        if (!effectiveDOMNode)
     162            return;
     163
     164        effectiveDOMNode.addEventListener(WebInspector.DOMNode.Event.EnabledPseudoClassesChanged, this._updatePseudoClassCheckboxes, this);
    161165    }
    162166
    163167    removeEventListeners()
    164168    {
    165         this.domNode.removeEventListener(null, null, this);
     169        var effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode;
     170        if (!effectiveDOMNode)
     171            return;
     172
     173        effectiveDOMNode.removeEventListener(null, null, this);
    166174    }
    167175
     
    236244            return;
    237245
    238         this.domNode.setPseudoClassEnabled(pseudoClass, event.target.checked);
     246        var effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode;
     247
     248        effectiveDOMNode.setPseudoClassEnabled(pseudoClass, event.target.checked);
    239249    }
    240250
     
    244254            return;
    245255
    246         var enabledPseudoClasses = this.domNode.enabledPseudoClasses;
     256        var effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode;
     257
     258        var enabledPseudoClasses = effectiveDOMNode.enabledPseudoClasses;
    247259
    248260        for (var pseudoClass in this._forcedPseudoClassCheckboxes) {
  • trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js

    r187465 r187496  
    3636
    3737        this._elementCloseTag = elementCloseTag;
    38         this.hasChildren = !elementCloseTag && node.hasChildNodes() && !this._showInlineText(node);
     38        this.hasChildren = !elementCloseTag && this._hasVisibleChildren();
    3939
    4040        if (this.representedObject.nodeType() === Node.ELEMENT_NODE && !elementCloseTag)
     
    159159    get editable()
    160160    {
    161         if (this.representedObject.isInShadowTree())
     161        var node = this.representedObject;
     162        if (node.isInShadowTree())
     163            return false;
     164        if (node.isPseudoElement())
    162165            return false;
    163166
     
    190193    }
    191194
    192     showChild(index)
     195    showChildNode(node)
    193196    {
    194197        console.assert(!this._elementCloseTag);
    195198        if (this._elementCloseTag)
    196             return false;
     199            return null;
     200
     201        var index = this._visibleChildren().indexOf(node);
     202        if (index === -1)
     203            return null;
    197204
    198205        if (index >= this.expandedChildrenLimit) {
     
    201208        }
    202209
    203         // Whether index-th child is visible in the children tree
    204         return this.expandedChildCount > index;
     210        return this.children[index];
    205211    }
    206212
     
    285291    onpopulate()
    286292    {
    287         if (this.children.length || this._showInlineText(this.representedObject) || this._elementCloseTag)
     293        if (this.children.length || !this._hasVisibleChildren() || this._elementCloseTag)
    288294            return;
    289295
     
    300306        if (this._elementCloseTag)
    301307            return;
     308
    302309        this.representedObject.getChildNodes(this._updateChildren.bind(this, fullRefresh));
    303310    }
     
    326333
    327334        this._updateChildrenInProgress = true;
     335
     336        var node = this.representedObject;
    328337        var selectedNode = this.treeOutline.selectedDOMNode();
    329338        var originalScrollTop = 0;
    330         if (fullRefresh) {
     339
     340        var hasVisibleChildren = this._hasVisibleChildren();
     341
     342        if (fullRefresh || !hasVisibleChildren) {
    331343            var treeOutlineContainerElement = this.treeOutline.element.parentNode;
    332344            originalScrollTop = treeOutlineContainerElement.scrollTop;
     
    335347                this.select();
    336348            this.removeChildren();
    337         }
    338 
    339         var treeElement = this;
    340         var treeChildIndex = 0;
    341         var elementToSelect;
    342 
    343         function updateChildrenOfNode(node)
    344         {
    345             var treeOutline = treeElement.treeOutline;
    346             var child = node.firstChild;
    347             while (child) {
    348                 var currentTreeElement = treeElement.children[treeChildIndex];
    349                 if (!currentTreeElement || currentTreeElement.representedObject !== child) {
    350                     // Find any existing element that is later in the children list.
    351                     var existingTreeElement = null;
    352                     for (var i = (treeChildIndex + 1), size = treeElement.expandedChildCount; i < size; ++i) {
    353                         if (treeElement.children[i].representedObject === child) {
    354                             existingTreeElement = treeElement.children[i];
    355                             break;
    356                         }
    357                     }
    358 
    359                     if (existingTreeElement && existingTreeElement.parent === treeElement) {
    360                         // If an existing element was found and it has the same parent, just move it.
    361                         treeElement.moveChild(existingTreeElement, treeChildIndex);
    362                     } else {
    363                         // No existing element found, insert a new element.
    364                         if (treeChildIndex < treeElement.expandedChildrenLimit) {
    365                             var newElement = treeElement.insertChildElement(child, treeChildIndex);
    366                             if (child === selectedNode)
    367                                 elementToSelect = newElement;
    368                             if (treeElement.expandedChildCount > treeElement.expandedChildrenLimit)
    369                                 treeElement.expandedChildrenLimit++;
    370                         }
    371                     }
    372                 }
    373 
    374                 child = child.nextSibling;
    375                 ++treeChildIndex;
    376             }
     349
     350            // No longer have children.
     351            if (!hasVisibleChildren) {
     352                this.hasChildren = false;
     353                this.updateTitle();
     354                this._updateChildrenInProgress = false;
     355                return;
     356            }
     357        }
     358
     359        // We now have children.
     360        if (!this.hasChildren) {
     361            this.hasChildren = true;
     362            this.updateTitle();
    377363        }
    378364
    379365        // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent.
     366        // Keep a list of existing tree elements for nodes that we can use later.
     367        var existingChildTreeElements = new Map;
    380368        for (var i = (this.children.length - 1); i >= 0; --i) {
    381             var currentChild = this.children[i];
    382             var currentNode = currentChild.representedObject;
     369            var currentChildTreeElement = this.children[i];
     370            var currentNode = currentChildTreeElement.representedObject;
    383371            var currentParentNode = currentNode.parentNode;
    384 
    385             if (currentParentNode === this.representedObject)
     372            if (currentParentNode === node) {
     373                existingChildTreeElements.set(currentNode, currentChildTreeElement);
    386374                continue;
     375            }
    387376
    388377            var selectedTreeElement = this.treeOutline.selectedTreeElement;
    389             if (selectedTreeElement && (selectedTreeElement === currentChild || selectedTreeElement.hasAncestor(currentChild)))
     378            if (selectedTreeElement && (selectedTreeElement === currentChildTreeElement || selectedTreeElement.hasAncestor(currentChildTreeElement)))
    390379                this.select();
    391380
     
    393382        }
    394383
    395         updateChildrenOfNode(this.representedObject);
     384        // Move / create TreeElements for our visible children.
     385        var childIndex = 0;
     386        var elementToSelect = null;
     387        var visibleChildren = this._visibleChildren();
     388        for (var i = 0; i < visibleChildren.length && i < this.expandedChildrenLimit; ++i) {
     389            var childNode = visibleChildren[i];
     390
     391            // Already have a tree element for this child, just move it.
     392            var existingChildTreeElement = existingChildTreeElements.get(childNode);
     393            if (existingChildTreeElement) {
     394                this.moveChild(existingChildTreeElement, i);
     395                continue;
     396            }
     397
     398            // No existing tree element for this child. Insert a new element.
     399            var newChildTreeElement = this.insertChildElement(childNode, i);
     400
     401            // Update state.
     402            if (childNode === selectedNode)
     403                elementToSelect = newChildTreeElement;
     404            if (this.expandedChildCount > this.expandedChildrenLimit)
     405                this.expandedChildrenLimit++;
     406        }
     407
     408        // Update expand all children button.
    396409        this.adjustCollapsedRange();
    397410
     411        // Insert closing tag tree element.
    398412        var lastChild = this.children.lastValue;
    399         if (this.representedObject.nodeType() === Node.ELEMENT_NODE && (!lastChild || !lastChild._elementCloseTag))
     413        if (node.nodeType() === Node.ELEMENT_NODE && (!lastChild || !lastChild._elementCloseTag))
    400414            this.insertChildElement(this.representedObject, this.children.length, true);
    401415
     
    418432
    419433        var node = this.representedObject;
    420         if (!node.children)
    421             return;
    422         var childNodeCount = node.children.length;
     434        if (!this._hasVisibleChildren())
     435            return;
     436
     437        var visibleChildren = this._visibleChildren();
     438        var totalChildrenCount = visibleChildren.length;
    423439
    424440        // In case some nodes from the expanded range were removed, pull some nodes from the collapsed range into the expanded range at the bottom.
    425         for (var i = this.expandedChildCount, limit = Math.min(this.expandedChildrenLimit, childNodeCount); i < limit; ++i)
    426             this.insertChildElement(node.children[i], i);
     441        for (var i = this.expandedChildCount, limit = Math.min(this.expandedChildrenLimit, totalChildrenCount); i < limit; ++i)
     442            this.insertChildElement(totalChildrenCount[i], i);
    427443
    428444        var expandedChildCount = this.expandedChildCount;
    429         if (childNodeCount > this.expandedChildCount) {
     445        if (totalChildrenCount > this.expandedChildCount) {
    430446            var targetButtonIndex = expandedChildCount;
    431447            if (!this.expandAllButtonElement) {
     
    445461                this.insertChild(this.expandAllButtonElement.__treeElement, targetButtonIndex);
    446462
    447             this.expandAllButtonElement.textContent = WebInspector.UIString("Show All Nodes (%d More)").format(childNodeCount - expandedChildCount);
     463            this.expandAllButtonElement.textContent = WebInspector.UIString("Show All Nodes (%d More)").format(totalChildrenCount - expandedChildCount);
    448464        } else if (this.expandAllButtonElement)
    449465            this.expandAllButtonElement = null;
     
    452468    handleLoadAllChildren()
    453469    {
    454         this.expandedChildrenLimit = Math.max(this.representedObject.childNodeCount, this.expandedChildrenLimit + WebInspector.DOMTreeElement.InitialChildrenLimit);
     470        var visibleChildren = this._visibleChildren();
     471        var totalChildrenCount = visibleChildren.length;
     472        this.expandedChildrenLimit = Math.max(visibleChildren.length, this.expandedChildrenLimit + WebInspector.DOMTreeElement.InitialChildrenLimit);
    455473    }
    456474
     
    573591            return false;
    574592
    575         if (this.representedObject.isInShadowTree())
     593        if (this.representedObject.isInShadowTree() || this.representedObject.isPseudoElement())
    576594            return false;
    577595
     
    11121130        switch (node.nodeType()) {
    11131131            case Node.DOCUMENT_FRAGMENT_NODE:
    1114                 var fragmentElement = info.titleDOM.createChild("span", "webkit-html-fragment");
     1132                var fragmentElement = info.titleDOM.createChild("span", "html-fragment");
    11151133                if (node.isInShadowTree()) {
    11161134                    fragmentElement.textContent = WebInspector.UIString("Shadow Content");
     
    11261144
    11271145            case Node.ELEMENT_NODE:
     1146                if (node.isPseudoElement()) {
     1147                    var pseudoElement = info.titleDOM.createChild("span", "html-pseudo-element");
     1148                    pseudoElement.textContent = "::" + node.pseudoType();
     1149                    info.titleDOM.appendChild(document.createTextNode("\u200B"));
     1150                    info.hasChildren = false;
     1151                    break;
     1152                }
     1153
    11281154                var tagName = node.nodeNameInCorrectCase();
    11291155                if (this._elementCloseTag) {
     
    12351261        if (node.hasShadowRoots())
    12361262            return null;
     1263        if (node.hasPseudoElements())
     1264            return null;
    12371265
    12381266        var sibling = firstChild.nextSibling;
     
    12481276        }
    12491277        return false;
     1278    }
     1279
     1280    _hasVisibleChildren()
     1281    {
     1282        var node = this.representedObject;
     1283
     1284        if (this._showInlineText(node))
     1285            return false;
     1286
     1287        if (node.hasChildNodes())
     1288            return true;
     1289        if (node.hasPseudoElements())
     1290            return true;
     1291
     1292        return false;
     1293    }
     1294
     1295    _visibleChildren()
     1296    {
     1297        var node = this.representedObject;
     1298
     1299        var visibleChildren = [];
     1300
     1301        var beforePseudoElement = node.beforePseudoElement();
     1302        if (beforePseudoElement)
     1303            visibleChildren.push(beforePseudoElement);
     1304
     1305        if (node.childNodeCount)
     1306            visibleChildren = visibleChildren.concat(node.children);
     1307
     1308        var afterPseudoElement = node.afterPseudoElement();
     1309        if (afterPseudoElement)
     1310            visibleChildren.push(afterPseudoElement);
     1311
     1312        return visibleChildren;
    12501313    }
    12511314
  • trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElementPathComponent.js

    r182040 r187496  
    3535        switch (node.nodeType()) {
    3636        case Node.ELEMENT_NODE:
    37             className = WebInspector.DOMTreeElementPathComponent.DOMElementIconStyleClassName;
    38             title = WebInspector.displayNameForNode(node);
     37            if (node.isPseudoElement()) {
     38                className = WebInspector.DOMTreeElementPathComponent.DOMPseudoElementIconStyleClassName;
     39                title = "::" + node.pseudoType();
     40            } else {
     41                className = WebInspector.DOMTreeElementPathComponent.DOMElementIconStyleClassName;
     42                title = WebInspector.displayNameForNode(node);
     43            }
    3944            break;
    4045
     
    130135
    131136WebInspector.DOMTreeElementPathComponent.DOMElementIconStyleClassName = "dom-element-icon";
     137WebInspector.DOMTreeElementPathComponent.DOMPseudoElementIconStyleClassName = "dom-pseudo-element-icon";
    132138WebInspector.DOMTreeElementPathComponent.DOMTextNodeIconStyleClassName = "dom-text-node-icon";
    133139WebInspector.DOMTreeElementPathComponent.DOMCommentIconStyleClassName = "dom-comment-icon";
  • trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.css

    r187434 r187496  
    157157}
    158158
     159.dom-tree-outline .html-pseudo-element {
     160    color: hsl(0, 59%, 41%);
     161}
     162
     163.dom-tree-outline .html-fragment.shadow {
     164    opacity: 0.6;
     165}
     166
    159167.showing-find-banner .dom-tree-outline .search-highlight {
    160168    color: black;
     
    162170    border-bottom: 1px solid hsl(47, 82%, 60%);
    163171}
    164 
    165 .webkit-html-fragment.shadow {
    166     opacity: 0.6;
    167 }
  • trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js

    r186634 r187496  
    229229        if (treeElement)
    230230            return treeElement;
     231
    231232        if (!node.parentNode)
    232233            return null;
    233234
    234235        treeElement = this.createTreeElementFor(node.parentNode);
    235         if (treeElement && treeElement.showChild(node.index))
    236             return treeElement.children[node.index];
    237 
    238         return null;
     236        if (!treeElement)
     237            return null;
     238
     239        return treeElement.showChildNode(node);
    239240    }
    240241
     
    549550        event.preventDefault();
    550551
    551         var selectedNode = this.selectedTreeElement.representedObject;
    552         console.assert(selectedNode);
    553         if (!selectedNode)
    554             return;
    555 
    556         if (selectedNode.nodeType() !== Node.ELEMENT_NODE)
     552        var effectiveNode = this.selectedTreeElement.representedObject;
     553        console.assert(effectiveNode);
     554        if (!effectiveNode)
     555            return;
     556
     557        if (effectiveNode.isPseudoElement()) {
     558            effectiveNode = effectiveNode.parentNode;
     559            console.assert(effectiveNode);
     560            if (!effectiveNode)
     561                return;
     562        }           
     563
     564        if (effectiveNode.nodeType() !== Node.ELEMENT_NODE)
    557565            return;
    558566
     
    580588        }
    581589
    582         WebInspector.RemoteObject.resolveNode(selectedNode, "", resolvedNode);
     590        WebInspector.RemoteObject.resolveNode(effectiveNode, "", resolvedNode);
    583591    }
    584592};
  • trunk/Source/WebInspectorUI/UserInterface/Views/PathComponentIcons.css

    r183671 r187496  
    3636}
    3737
     38.dom-pseudo-element-icon .icon {
     39    content: url(../Images/PseudoElement.svg);
     40}
     41
    3842.dom-text-node-icon .icon {
    3943    content: url(../Images/DOMTextNode.svg);
  • trunk/Source/WebInspectorUI/UserInterface/Views/RulesStyleDetailsPanel.css

    r187105 r187496  
    5151
    5252    opacity: 0.5;
     53}
     54
     55.sidebar > .panel.details.css-style .rules > *:first-child:matches(.new-rule) {
     56    margin-top: 8px;
    5357}
    5458
Note: See TracChangeset for help on using the changeset viewer.