https://bugs.webkit.org/show_bug.cgi?id=86630
Reviewed by Pavel Feldman.
Source/WebCore:
- A map of pseudo-states for all bound DOM nodes is maintained in the backend and queried whenever StyleResolver
calculates the effective element style.
- In the frontend, markers are introduced to distinguish elements that have forced pseudo styles set for them.
Additionally, dimmed markers are added for collapsed nodes, whose descendants have forced pseudo styles.
More ElementDecorator subtypes will be added for other types of markers.
Test: inspector/styles/force-pseudo-state.html
* English.lproj/localizedStrings.js:
* inspector/InspectorCSSAgent.cpp:
(WebCore::InspectorCSSAgent::InspectorCSSAgent):
(WebCore::InspectorCSSAgent::clearFrontend):
(WebCore::InspectorCSSAgent::reset):
(WebCore::InspectorCSSAgent::forcePseudoState):
(WebCore::InspectorCSSAgent::recalcStyleForPseudoStateIfNeeded):
(WebCore::InspectorCSSAgent::elementForId):
(WebCore::InspectorCSSAgent::didRemoveDocument):
(WebCore::InspectorCSSAgent::didRemoveDOMNode):
(WebCore::InspectorCSSAgent::resetPseudoStates):
* inspector/InspectorCSSAgent.h:
(InspectorCSSAgent):
* inspector/InspectorDOMAgent.cpp:
(WebCore::InspectorDOMAgent::unbind):
(WebCore::InspectorDOMAgent::didRemoveDOMNode):
* inspector/front-end/ElementsPanel.js:
(WebInspector.ElementsPanel.get this):
(WebInspector.ElementsPanel):
(WebInspector.ElementsPanel.prototype._setPseudoClassForNodeId):
* inspector/front-end/ElementsTreeOutline.js:
(WebInspector.ElementsTreeOutline):
(WebInspector.ElementsTreeOutline.prototype._createNodeDecorators):
(WebInspector.ElementsTreeOutline.prototype.updateOpenCloseTags):
(WebInspector.ElementsTreeOutline.ElementDecorator):
(WebInspector.ElementsTreeOutline.ElementDecorator.prototype.decorate):
(WebInspector.ElementsTreeOutline.ElementDecorator.prototype.decorateAncestor):
(WebInspector.ElementsTreeOutline.PseudoStateDecorator):
(WebInspector.ElementsTreeOutline.PseudoStateDecorator.prototype.decorate):
(WebInspector.ElementsTreeOutline.PseudoStateDecorator.prototype.decorateAncestor):
(WebInspector.ElementsTreeElement.prototype._populateTagContextMenu):
(WebInspector.ElementsTreeElement.prototype._populateForcedPseudoStateItems):
(WebInspector.ElementsTreeElement.prototype.updateTitle):
(WebInspector.ElementsTreeElement.prototype._createDecoratorElement):
(WebInspector.ElementsTreeElement.prototype._updateDecorations):
* inspector/front-end/StylesSidebarPane.js:
(WebInspector.StylesSidebarPane):
(WebInspector.StylesSidebarPane.prototype.get forcedPseudoClasses):
(WebInspector.StylesSidebarPane.prototype._updateForcedPseudoStateInputs):
(WebInspector.StylesSidebarPane.prototype.update):
(WebInspector.StylesSidebarPane.prototype._refreshUpdate):
(WebInspector.StylesSidebarPane.prototype._rebuildUpdate):
(WebInspector.StylesSidebarPane.prototype._toggleElementStatePane):
(WebInspector.StylesSidebarPane.prototype._createElementStatePane.clickListener):
* inspector/front-end/elementsPanel.css:
(#elements-content .elements-gutter-decoration):
(#elements-content .elements-gutter-decoration.elements-has-decorated-children):
LayoutTests:
* inspector/styles/force-pseudo-state-expected.txt: Added.
* inspector/styles/force-pseudo-state.html: Added.
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@121860
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2012-07-04 Alexander Pavlov <apavlov@chromium.org>
+
+ Web Inspector: Emulate pseudo styles (hover etc.) of non-selected elements
+ https://bugs.webkit.org/show_bug.cgi?id=86630
+
+ Reviewed by Pavel Feldman.
+
+ * inspector/styles/force-pseudo-state-expected.txt: Added.
+ * inspector/styles/force-pseudo-state.html: Added.
+
2012-07-04 Vsevolod Vlasov <vsevik@chromium.org>
REGRESSION(r121792): inspector/extensions/extensions-resources.html fails
--- /dev/null
+Tests that forced element state is reflected in the DOM tree and Styles pane.
+
+Test text
+
+DIV with :hover and :active
+[expanded]
+element.style { ()
+
+======== Matched CSS Rules ========
+[expanded]
+div:active, a:active { (force-pseudo-state.html:69)
+font-weight: bold;
+
+[expanded]
+div:hover, a:hover { (force-pseudo-state.html:61)
+color: red;
+
+[expanded]
+div { (user agent stylesheet)
+display: block;
+
+======== Inherited from body#mainBody.main1.main2.mainpage ========
+[expanded]
+Style Attribute { ()
+/-- overloaded --/ font-weight: normal;
+
+
+- <html> [descendantUserAttributeCounters:[pseudoState=1]]
+ + <head>…</head>
+ - <body id="mainBody" class="main1 main2 mainpage" onload="runTest()" style="font-weight: normal; width: 85%; background-image: url(bar.png)"> [descendantUserAttributeCounters:[pseudoState=1]]
+ <p>Tests that forced element state is reflected in the DOM tree and Styles pane.</p>
+ <div id="div">Test text</div> [userProperties:[pseudoState=hover,active]]
+ </body>
+ </html>
+
+DIV with :active and :focus
+[expanded]
+element.style { ()
+
+======== Matched CSS Rules ========
+[expanded]
+div:active, a:active { (force-pseudo-state.html:69)
+font-weight: bold;
+
+[expanded]
+div:focus, a:focus { (force-pseudo-state.html:65)
+border: 1px solid green;
+ border-top-width: 1px;
+ border-right-width: 1px;
+ border-bottom-width: 1px;
+ border-left-width: 1px;
+ border-top-style: solid;
+ border-right-style: solid;
+ border-bottom-style: solid;
+ border-left-style: solid;
+ border-top-color: green;
+ border-right-color: green;
+ border-bottom-color: green;
+ border-left-color: green;
+
+[expanded]
+:focus { (user agent stylesheet)
+outline: -webkit-focus-ring-color auto 5px;
+ outline-style: auto;
+ outline-width: 5px;
+ outline-color: -webkit-focus-ring-color;
+
+[expanded]
+div { (user agent stylesheet)
+display: block;
+
+======== Inherited from body#mainBody.main1.main2.mainpage ========
+[expanded]
+Style Attribute { ()
+/-- overloaded --/ font-weight: normal;
+
+
+- <html> [descendantUserAttributeCounters:[pseudoState=1]]
+ + <head>…</head>
+ - <body id="mainBody" class="main1 main2 mainpage" onload="runTest()" style="font-weight: normal; width: 85%; background-image: url(bar.png)"> [descendantUserAttributeCounters:[pseudoState=1]]
+ <p>Tests that forced element state is reflected in the DOM tree and Styles pane.</p>
+ <div id="div">Test text</div> [userProperties:[pseudoState=active,focus]]
+ + <div>…</div>
+ </body>
+ </html>
+
+DIV with no forced state
+[expanded]
+element.style { ()
+
+======== Matched CSS Rules ========
+[expanded]
+div { (user agent stylesheet)
+display: block;
+
+======== Inherited from body#mainBody.main1.main2.mainpage ========
+[expanded]
+Style Attribute { ()
+font-weight: normal;
+
+
+- <html>
+ + <head>…</head>
+ - <body id="mainBody" class="main1 main2 mainpage" onload="runTest()" style="font-weight: normal; width: 85%; background-image: url(bar.png)">
+ <p>Tests that forced element state is reflected in the DOM tree and Styles pane.</p>
+ <div id="div">Test text</div>
+ + <div>…</div>
+ </body>
+ </html>
+
--- /dev/null
+<html>
+<head>
+<script src="../../http/tests/inspector/inspector-test.js"></script>
+<script src="../../http/tests/inspector/elements-test.js"></script>
+<script>
+
+function test()
+{
+ WebInspector.inspectorView.setCurrentPanel(WebInspector.panels.elements);
+
+ InspectorTest.nodeWithId("div", foundDiv);
+
+ var divNodeId;
+
+ function dumpData()
+ {
+ InspectorTest.dumpSelectedElementStyles(true);
+ InspectorTest.dumpElementsTree();
+ }
+
+ function foundDiv(divNode)
+ {
+ divNodeId = divNode.id;
+ WebInspector.panels.elements._setPseudoClassForNodeId(divNodeId, "hover", true);
+ WebInspector.panels.elements._setPseudoClassForNodeId(divNodeId, "active", true);
+ InspectorTest.selectNodeAndWaitForStyles("div", divSelected1);
+ }
+
+ function divSelected1()
+ {
+ InspectorTest.addResult("");
+ InspectorTest.addResult("DIV with :hover and :active");
+ dumpData();
+ WebInspector.panels.elements._setPseudoClassForNodeId(divNodeId, "hover", false);
+ WebInspector.panels.elements._setPseudoClassForNodeId(divNodeId, "focus", true);
+ InspectorTest.waitForStyles("div", divSelected2, true);
+ }
+
+ function divSelected2()
+ {
+ InspectorTest.addResult("");
+ InspectorTest.addResult("DIV with :active and :focus");
+ dumpData();
+ WebInspector.panels.elements._setPseudoClassForNodeId(divNodeId, "focus", false);
+ WebInspector.panels.elements._setPseudoClassForNodeId(divNodeId, "active", false);
+ InspectorTest.waitForStyles("div", divSelected3, true);
+ }
+
+ function divSelected3(node)
+ {
+ InspectorTest.addResult("");
+ InspectorTest.addResult("DIV with no forced state");
+ dumpData();
+ InspectorTest.completeTest();
+ return;
+ }
+}
+</script>
+
+<style>
+div:hover, a:hover {
+ color: red;
+}
+
+div:focus, a:focus {
+ border: 1px solid green;
+}
+
+div:active, a:active {
+ font-weight: bold;
+}
+
+</style>
+</head>
+
+<body id="mainBody" class="main1 main2 mainpage" onload="runTest()" style="font-weight: normal; width: 85%; background-image: url(bar.png)">
+<p>
+Tests that forced element state is reflected in the DOM tree and Styles pane.
+</p>
+<div id="div">Test text</div>
+</body>
+</html>
+2012-07-03 Alexander Pavlov <apavlov@chromium.org>
+
+ Web Inspector: Emulate pseudo styles (hover etc.) of non-selected elements
+ https://bugs.webkit.org/show_bug.cgi?id=86630
+
+ Reviewed by Pavel Feldman.
+
+ - A map of pseudo-states for all bound DOM nodes is maintained in the backend and queried whenever StyleResolver
+ calculates the effective element style.
+ - In the frontend, markers are introduced to distinguish elements that have forced pseudo styles set for them.
+ Additionally, dimmed markers are added for collapsed nodes, whose descendants have forced pseudo styles.
+ More ElementDecorator subtypes will be added for other types of markers.
+
+ Test: inspector/styles/force-pseudo-state.html
+
+ * English.lproj/localizedStrings.js:
+ * inspector/InspectorCSSAgent.cpp:
+ (WebCore::InspectorCSSAgent::InspectorCSSAgent):
+ (WebCore::InspectorCSSAgent::clearFrontend):
+ (WebCore::InspectorCSSAgent::reset):
+ (WebCore::InspectorCSSAgent::forcePseudoState):
+ (WebCore::InspectorCSSAgent::recalcStyleForPseudoStateIfNeeded):
+ (WebCore::InspectorCSSAgent::elementForId):
+ (WebCore::InspectorCSSAgent::didRemoveDocument):
+ (WebCore::InspectorCSSAgent::didRemoveDOMNode):
+ (WebCore::InspectorCSSAgent::resetPseudoStates):
+ * inspector/InspectorCSSAgent.h:
+ (InspectorCSSAgent):
+ * inspector/InspectorDOMAgent.cpp:
+ (WebCore::InspectorDOMAgent::unbind):
+ (WebCore::InspectorDOMAgent::didRemoveDOMNode):
+ * inspector/front-end/ElementsPanel.js:
+ (WebInspector.ElementsPanel.get this):
+ (WebInspector.ElementsPanel):
+ (WebInspector.ElementsPanel.prototype._setPseudoClassForNodeId):
+ * inspector/front-end/ElementsTreeOutline.js:
+ (WebInspector.ElementsTreeOutline):
+ (WebInspector.ElementsTreeOutline.prototype._createNodeDecorators):
+ (WebInspector.ElementsTreeOutline.prototype.updateOpenCloseTags):
+ (WebInspector.ElementsTreeOutline.ElementDecorator):
+ (WebInspector.ElementsTreeOutline.ElementDecorator.prototype.decorate):
+ (WebInspector.ElementsTreeOutline.ElementDecorator.prototype.decorateAncestor):
+ (WebInspector.ElementsTreeOutline.PseudoStateDecorator):
+ (WebInspector.ElementsTreeOutline.PseudoStateDecorator.prototype.decorate):
+ (WebInspector.ElementsTreeOutline.PseudoStateDecorator.prototype.decorateAncestor):
+ (WebInspector.ElementsTreeElement.prototype._populateTagContextMenu):
+ (WebInspector.ElementsTreeElement.prototype._populateForcedPseudoStateItems):
+ (WebInspector.ElementsTreeElement.prototype.updateTitle):
+ (WebInspector.ElementsTreeElement.prototype._createDecoratorElement):
+ (WebInspector.ElementsTreeElement.prototype._updateDecorations):
+ * inspector/front-end/StylesSidebarPane.js:
+ (WebInspector.StylesSidebarPane):
+ (WebInspector.StylesSidebarPane.prototype.get forcedPseudoClasses):
+ (WebInspector.StylesSidebarPane.prototype._updateForcedPseudoStateInputs):
+ (WebInspector.StylesSidebarPane.prototype.update):
+ (WebInspector.StylesSidebarPane.prototype._refreshUpdate):
+ (WebInspector.StylesSidebarPane.prototype._rebuildUpdate):
+ (WebInspector.StylesSidebarPane.prototype._toggleElementStatePane):
+ (WebInspector.StylesSidebarPane.prototype._createElementStatePane.clickListener):
+ * inspector/front-end/elementsPanel.css:
+ (#elements-content .elements-gutter-decoration):
+ (#elements-content .elements-gutter-decoration.elements-has-decorated-children):
+
2012-07-04 Pavel Feldman <pfeldman@chromium.org>
Web Inspector: fix search on the network panel.
localizedStrings["%.3fms"] = "%.3fms";
localizedStrings["%d console messages are not shown."] = "%d console messages are not shown.";
localizedStrings["%d cookies (%s)"] = "%d cookies (%s)";
+localizedStrings["%d descendant with forced state"] = "%d descendant with forced state";
+localizedStrings["%d descendants with forced state"] = "%d descendants with forced state";
localizedStrings["%d error"] = "%d error";
localizedStrings["%d error, %d warning"] = "%d error, %d warning";
localizedStrings["%d error, %d warnings"] = "%d error, %d warnings";
localizedStrings["Edit text"] = "Edit text";
localizedStrings["Edit as HTML"] = "Edit as HTML";
localizedStrings["Edit"] = "Edit";
+localizedStrings["Element state: %s"] = "Element state: %s";
localizedStrings["Elements Panel"] = "Elements Panel";
localizedStrings["Elements"] = "Elements";
localizedStrings["Emulate touch events"] = "Emulate touch events";
localizedStrings["Expires"] = "Expires";
localizedStrings["File size"] = "File size";
localizedStrings["Fit in window"] = "Fit in window";
+localizedStrings["Force Element State"] = "Force Element State";
+localizedStrings["Force element state"] = "Force element state";
localizedStrings["Go to the panel to the left/right"] = "Go to the panel to the left/right";
localizedStrings["Go back/forward in panel history"] = "Go back/forward in panel history";
localizedStrings["Finish Loading"] = "Finish Loading";
: InspectorBaseAgent<InspectorCSSAgent>("CSS", instrumentingAgents, state)
, m_frontend(0)
, m_domAgent(domAgent)
- , m_lastPseudoState(0)
, m_lastStyleSheetId(1)
{
m_domAgent->setDOMListener(this);
{
ASSERT(m_frontend);
m_frontend = 0;
- clearPseudoState(true);
+ resetPseudoStates();
String errorString;
stopSelectorProfilerImpl(&errorString, false);
}
m_cssStyleSheetToInspectorStyleSheet.clear();
m_nodeToInspectorStyleSheet.clear();
m_documentToInspectorStyleSheet.clear();
+ resetPseudoStates();
}
void InspectorCSSAgent::enable(ErrorString*)
bool InspectorCSSAgent::forcePseudoState(Element* element, CSSSelector::PseudoType pseudoType)
{
- if (m_lastElementWithPseudoState != element)
+ if (m_nodeIdToForcedPseudoState.isEmpty())
return false;
+ int nodeId = m_domAgent->boundNodeId(element);
+ if (!nodeId)
+ return false;
+
+ NodeIdToForcedPseudoState::iterator it = m_nodeIdToForcedPseudoState.find(nodeId);
+ if (it == m_nodeIdToForcedPseudoState.end())
+ return false;
+
+ unsigned forcedPseudoState = it->second;
switch (pseudoType) {
case CSSSelector::PseudoActive:
- return m_lastPseudoState & PseudoActive;
+ return forcedPseudoState & PseudoActive;
case CSSSelector::PseudoFocus:
- return m_lastPseudoState & PseudoFocus;
+ return forcedPseudoState & PseudoFocus;
case CSSSelector::PseudoHover:
- return m_lastPseudoState & PseudoHover;
+ return forcedPseudoState & PseudoHover;
case CSSSelector::PseudoVisited:
- return m_lastPseudoState & PseudoVisited;
+ return forcedPseudoState & PseudoVisited;
default:
return false;
}
void InspectorCSSAgent::recalcStyleForPseudoStateIfNeeded(Element* element, InspectorArray* forcedPseudoClasses)
{
- unsigned forcePseudoState = computePseudoClassMask(forcedPseudoClasses);
- bool needStyleRecalc = element != m_lastElementWithPseudoState || forcePseudoState != m_lastPseudoState;
- m_lastPseudoState = forcePseudoState;
- m_lastElementWithPseudoState = element;
- if (needStyleRecalc)
- element->ownerDocument()->styleResolverChanged(RecalcStyleImmediately);
+ int nodeId = m_domAgent->boundNodeId(element);
+ if (!nodeId)
+ return;
+
+ unsigned forcedPseudoState = computePseudoClassMask(forcedPseudoClasses);
+ NodeIdToForcedPseudoState::iterator it = m_nodeIdToForcedPseudoState.find(nodeId);
+ unsigned currentForcedPseudoState = it == m_nodeIdToForcedPseudoState.end() ? 0 : it->second;
+ bool needStyleRecalc = forcedPseudoState != currentForcedPseudoState;
+ if (!needStyleRecalc)
+ return;
+
+ if (forcedPseudoState)
+ m_nodeIdToForcedPseudoState.set(nodeId, forcedPseudoState);
+ else
+ m_nodeIdToForcedPseudoState.remove(nodeId);
+ element->ownerDocument()->styleResolverChanged(RecalcStyleImmediately);
}
void InspectorCSSAgent::getMatchedStylesForNode(ErrorString* errorString, int nodeId, const RefPtr<InspectorArray>* forcedPseudoClasses, const bool* includePseudo, const bool* includeInherited, RefPtr<TypeBuilder::Array<TypeBuilder::CSS::CSSRule> >& matchedCSSRules, RefPtr<TypeBuilder::Array<TypeBuilder::CSS::PseudoIdRules> >& pseudoIdRules, RefPtr<TypeBuilder::Array<TypeBuilder::CSS::InheritedStyleEntry> >& inheritedEntries)
*errorString = "Not an element node";
return 0;
}
- return static_cast<Element*>(node);
+ return toElement(node);
}
void InspectorCSSAgent::collectStyleSheets(CSSStyleSheet* styleSheet, TypeBuilder::Array<TypeBuilder::CSS::CSSStyleSheetHeader>* result)
{
if (document)
m_documentToInspectorStyleSheet.remove(document);
- clearPseudoState(false);
}
void InspectorCSSAgent::didRemoveDOMNode(Node* node)
if (!node)
return;
- if (m_lastElementWithPseudoState.get() == node)
- clearPseudoState(false);
+ int nodeId = m_domAgent->boundNodeId(node);
+ if (nodeId)
+ m_nodeIdToForcedPseudoState.remove(nodeId);
NodeToInspectorStyleSheet::iterator it = m_nodeToInspectorStyleSheet.find(node);
if (it == m_nodeToInspectorStyleSheet.end())
m_frontend->styleSheetChanged(styleSheet->id());
}
-void InspectorCSSAgent::clearPseudoState(bool recalcStyles)
+void InspectorCSSAgent::resetPseudoStates()
{
- RefPtr<Element> element = m_lastElementWithPseudoState;
- m_lastElementWithPseudoState = 0;
- m_lastPseudoState = 0;
- if (recalcStyles && element) {
- Document* document = element->ownerDocument();
- if (document)
- document->styleResolverChanged(RecalcStyleImmediately);
+ HashSet<Document*> documentsToChange;
+ for (NodeIdToForcedPseudoState::iterator it = m_nodeIdToForcedPseudoState.begin(), end = m_nodeIdToForcedPseudoState.end(); it != end; ++it) {
+ Element* element = toElement(m_domAgent->nodeForId(it->first));
+ if (element && element->ownerDocument())
+ documentsToChange.add(element->ownerDocument());
}
+
+ m_nodeIdToForcedPseudoState.clear();
+ for (HashSet<Document*>::iterator it = documentsToChange.begin(), end = documentsToChange.end(); it != end; ++it)
+ (*it)->styleResolverChanged(RecalcStyleImmediately);
}
} // namespace WebCore
typedef HashMap<CSSStyleSheet*, RefPtr<InspectorStyleSheet> > CSSStyleSheetToInspectorStyleSheet;
typedef HashMap<Node*, RefPtr<InspectorStyleSheetForInlineStyle> > NodeToInspectorStyleSheet; // bogus "stylesheets" with elements' inline styles
typedef HashMap<RefPtr<Document>, RefPtr<InspectorStyleSheet> > DocumentToViaInspectorStyleSheet; // "via inspector" stylesheets
+ typedef HashMap<int, unsigned> NodeIdToForcedPseudoState;
void recalcStyleForPseudoStateIfNeeded(Element*, InspectorArray* forcedPseudoClasses);
InspectorStyleSheetForInlineStyle* asInspectorStyleSheet(Element* element);
// InspectorCSSAgent::Listener implementation
virtual void styleSheetChanged(InspectorStyleSheet*);
- void clearPseudoState(bool recalcStyles);
+ void resetPseudoStates();
InspectorFrontend::CSS* m_frontend;
InspectorDOMAgent* m_domAgent;
CSSStyleSheetToInspectorStyleSheet m_cssStyleSheetToInspectorStyleSheet;
NodeToInspectorStyleSheet m_nodeToInspectorStyleSheet;
DocumentToViaInspectorStyleSheet m_documentToInspectorStyleSheet;
-
- RefPtr<Element> m_lastElementWithPseudoState;
- unsigned m_lastPseudoState;
+ NodeIdToForcedPseudoState m_nodeIdToForcedPseudoState;
int m_lastStyleSheetId;
}
nodesMap->remove(node);
+ if (m_domListener)
+ m_domListener->didRemoveDOMNode(node);
+
bool childrenRequested = m_childrenRequested.contains(id);
if (childrenRequested) {
// Unbind subtree known to client recursively.
int parentId = m_documentNodeToIdMap.get(parent);
- if (m_domListener)
- m_domListener->didRemoveDOMNode(node);
-
if (!m_childrenRequested.contains(parentId)) {
// No children are mapped yet -> only notify on changes of hasChildren.
if (innerChildNodeCount(parent) == 1)
this.contentElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
- this.treeOutline = new WebInspector.ElementsTreeOutline(true, true, false, this._populateContextMenu.bind(this));
+ this.treeOutline = new WebInspector.ElementsTreeOutline(true, true, false, this._populateContextMenu.bind(this), this._setPseudoClassForNodeId.bind(this));
this.treeOutline.wireToDomAgent();
this.treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedNodeChanged, this);
this.sidebarPanes = {};
this.sidebarPanes.computedStyle = new WebInspector.ComputedStyleSidebarPane();
- this.sidebarPanes.styles = new WebInspector.StylesSidebarPane(this.sidebarPanes.computedStyle);
+ this.sidebarPanes.styles = new WebInspector.StylesSidebarPane(this.sidebarPanes.computedStyle, this._setPseudoClassForNodeId.bind(this));
this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane();
this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane();
this.sidebarPanes.domBreakpoints = WebInspector.domBreakpointsSidebarPane;
this.updateBreadcrumbSizes();
},
+ /**
+ * @param {DOMAgent.NodeId} nodeId
+ * @param {string} pseudoClass
+ * @param {boolean} enable
+ */
+ _setPseudoClassForNodeId: function(nodeId, pseudoClass, enable)
+ {
+ var node = WebInspector.domAgent.nodeForId(nodeId);
+ if (!node)
+ return;
+
+ var pseudoClasses = node.getUserProperty("pseudoState");
+ if (enable) {
+ pseudoClasses = pseudoClasses || [];
+ if (pseudoClasses.indexOf(pseudoClass) >= 0)
+ return;
+ pseudoClasses.push(pseudoClass);
+ node.setUserProperty("pseudoState", pseudoClasses);
+ } else {
+ if (!pseudoClasses || pseudoClasses.indexOf(pseudoClass) < 0)
+ return;
+ pseudoClasses.remove(pseudoClass);
+ if (!pseudoClasses.length)
+ node.removeUserProperty("pseudoState");
+ }
+
+ this.treeOutline.updateOpenCloseTags(node);
+ this._metricsPaneEdited();
+ this._stylesPaneEdited();
+ },
+
_selectedNodeChanged: function()
{
var selectedNode = this.selectedDOMNode();
* @param {boolean=} selectEnabled
* @param {boolean=} showInElementsPanelEnabled
* @param {function(WebInspector.ContextMenu, WebInspector.DOMNode)=} contextMenuCallback
+ * @param {function(DOMAgent.NodeId, string, boolean)=} setPseudoClassCallback
*/
-WebInspector.ElementsTreeOutline = function(omitRootDOMNode, selectEnabled, showInElementsPanelEnabled, contextMenuCallback)
+WebInspector.ElementsTreeOutline = function(omitRootDOMNode, selectEnabled, showInElementsPanelEnabled, contextMenuCallback, setPseudoClassCallback)
{
this.element = document.createElement("ol");
this.element.addEventListener("mousedown", this._onmousedown.bind(this), false);
this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
this._contextMenuCallback = contextMenuCallback;
+ this._setPseudoClassCallback = setPseudoClassCallback;
+ this._createNodeDecorators();
}
WebInspector.ElementsTreeOutline.Events = {
}
WebInspector.ElementsTreeOutline.prototype = {
+ _createNodeDecorators: function()
+ {
+ this._nodeDecorators = [];
+ this._nodeDecorators.push(new WebInspector.ElementsTreeOutline.PseudoStateDecorator());
+ },
+
wireToDomAgent: function()
{
this._elementsTreeUpdater = new WebInspector.ElementsTreeUpdater(this);
element.updateSelection();
},
+ /**
+ * @param {WebInspector.DOMNode} node
+ */
+ updateOpenCloseTags: function(node)
+ {
+ var treeElement = this.findTreeElement(node);
+ if (treeElement)
+ treeElement.updateTitle();
+ var children = treeElement.children;
+ var closingTagElement = children[children.length - 1];
+ if (closingTagElement && closingTagElement._elementCloseTag)
+ closingTagElement.updateTitle();
+ },
+
_selectedNodeChanged: function()
{
this._eventSupport.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedDOMNode);
},
+ /**
+ * @param {WebInspector.DOMNode} node
+ */
findTreeElement: function(node)
{
function isAncestorNode(ancestor, node)
return treeElement;
},
+ /**
+ * @param {WebInspector.DOMNode} node
+ */
createTreeElementFor: function(node)
{
var treeElement = this.findTreeElement(node);
WebInspector.ElementsTreeOutline.prototype.__proto__ = TreeOutline.prototype;
/**
+ * @interface
+ */
+WebInspector.ElementsTreeOutline.ElementDecorator = function()
+{
+}
+
+WebInspector.ElementsTreeOutline.ElementDecorator.prototype = {
+ /**
+ * @param {WebInspector.DOMNode} node
+ */
+ decorate: function(node)
+ {
+ },
+
+ /**
+ * @param {WebInspector.DOMNode} node
+ */
+ decorateAncestor: function(node)
+ {
+ }
+}
+
+/**
+ * @constructor
+ * @implements {WebInspector.ElementsTreeOutline.ElementDecorator}
+ */
+WebInspector.ElementsTreeOutline.PseudoStateDecorator = function()
+{
+ WebInspector.ElementsTreeOutline.ElementDecorator.call(this);
+}
+
+WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName = "pseudoState";
+
+WebInspector.ElementsTreeOutline.PseudoStateDecorator.prototype = {
+ decorate: function(node)
+ {
+ if (node.nodeType() !== Node.ELEMENT_NODE)
+ return null;
+ var propertyValue = node.getUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName);
+ if (!propertyValue)
+ return null;
+ return WebInspector.UIString("Element state: %s", ":" + propertyValue.join(", :"));
+ },
+
+ decorateAncestor: function(node)
+ {
+ if (node.nodeType() !== Node.ELEMENT_NODE)
+ return null;
+
+ var descendantCount = node.descendantUserPropertyCount(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName);
+ if (!descendantCount)
+ return null;
+ if (descendantCount === 1)
+ return WebInspector.UIString("%d descendant with forced state", descendantCount);
+ return WebInspector.UIString("%d descendants with forced state", descendantCount);
+ }
+}
+
+WebInspector.ElementsTreeOutline.PseudoStateDecorator.prototype.__proto__ = WebInspector.ElementsTreeOutline.ElementDecorator.prototype;
+
+/**
* @constructor
* @extends {TreeElement}
* @param {boolean=} elementCloseTag
if (attribute && !newAttribute)
contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit attribute" : "Edit Attribute"), this._startEditingAttribute.bind(this, attribute, event.target));
contextMenu.appendSeparator();
+ if (this.treeOutline._setPseudoClassCallback) {
+ var pseudoSubMenu = contextMenu.appendSubMenuItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Force element state" : "Force Element State"));
+ this._populateForcedPseudoStateItems(pseudoSubMenu);
+ contextMenu.appendSeparator();
+ }
this._populateNodeContextMenu(contextMenu);
this.treeOutline._populateContextMenu(contextMenu, this.representedObject);
},
+ _populateForcedPseudoStateItems: function(subMenu)
+ {
+ const pseudoClasses = ["active", "hover", "focus", "visited"];
+ var node = this.representedObject;
+ var forcedPseudoState = (node ? node.getUserProperty("pseudoState") : null) || [];
+ var elementsPanel = WebInspector.panels.elements;
+ for (var i = 0; i < pseudoClasses.length; ++i) {
+ var pseudoClassForced = forcedPseudoState.indexOf(pseudoClasses[i]) >= 0;
+ subMenu.appendCheckboxItem(":" + pseudoClasses[i], this.treeOutline._setPseudoClassCallback.bind(null, node.id, pseudoClasses[i], !pseudoClassForced), pseudoClassForced, false);
+ }
+ },
+
_populateTextContextMenu: function(contextMenu, textNode)
{
contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit text" : "Edit Text"), this._startEditingTextNode.bind(this, textNode));
highlightElement.className = "highlight";
highlightElement.appendChild(this._nodeTitleInfo(WebInspector.linkifyURLAsNode).titleDOM);
this.title = highlightElement;
+ this._updateDecorations();
delete this._highlightResult;
}
this._highlightSearchResults();
},
+ _createDecoratorElement: function()
+ {
+ var node = this.representedObject;
+ var decoratorMessages = [];
+ var parentDecoratorMessages = [];
+ for (var i = 0; i < this.treeOutline._nodeDecorators.length; ++i) {
+ var decorator = this.treeOutline._nodeDecorators[i];
+ var message = decorator.decorate(node);
+ if (message) {
+ decoratorMessages.push(message);
+ continue;
+ }
+
+ if (this.expanded || this._elementCloseTag)
+ continue;
+
+ message = decorator.decorateAncestor(node);
+ if (message)
+ parentDecoratorMessages.push(message)
+ }
+ if (!decoratorMessages.length && !parentDecoratorMessages.length)
+ return null;
+
+ var decoratorElement = document.createElement("div");
+ decoratorElement.addStyleClass("elements-gutter-decoration");
+ if (!decoratorMessages.length)
+ decoratorElement.addStyleClass("elements-has-decorated-children");
+ decoratorElement.title = decoratorMessages.concat(parentDecoratorMessages).join("\n");
+ return decoratorElement;
+ },
+
+ _updateDecorations: function()
+ {
+ if (this._decoratorElement && this._decoratorElement.parentElement)
+ this._decoratorElement.parentElement.removeChild(this._decoratorElement);
+ this._decoratorElement = this._createDecoratorElement();
+ if (this._decoratorElement && this.listItemElement)
+ this.listItemElement.insertBefore(this._decoratorElement, this.listItemElement.firstChild);
+ },
+
/**
* @param {WebInspector.DOMNode=} node
* @param {function(string, string, string, boolean=, string=)=} linkify
/**
* @constructor
* @extends {WebInspector.SidebarPane}
+ * @param {WebInspector.ComputedStyleSidebarPane} computedStylePane
+ * @param {function(DOMAgent.NodeId, string, boolean)} setPseudoClassCallback
*/
-WebInspector.StylesSidebarPane = function(computedStylePane)
+WebInspector.StylesSidebarPane = function(computedStylePane, setPseudoClassCallback)
{
WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles"));
this._computedStylePane = computedStylePane;
computedStylePane._stylesSidebarPane = this;
+ this._setPseudoClassCallback = setPseudoClassCallback;
this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
WebInspector.settings.colorFormat.addChangeListener(this._colorFormatSettingChanged.bind(this));
get forcedPseudoClasses()
{
- return this._forcedPseudoClasses;
+ return this.node ? (this.node.getUserProperty("pseudoState") || undefined) : undefined;
+ },
+
+ _updateForcedPseudoStateInputs: function()
+ {
+ if (!this.node)
+ return;
+
+ var nodePseudoState = this.forcedPseudoClasses;
+ if (!nodePseudoState)
+ nodePseudoState = [];
+
+ var inputs = this._elementStatePane.inputs;
+ for (var i = 0; i < inputs.length; ++i)
+ inputs[i].checked = nodePseudoState.indexOf(inputs[i].state) >= 0;
},
update: function(node, forceUpdate)
else
node = this.node;
+ this._updateForcedPseudoStateInputs();
+
if (refresh)
this._refreshUpdate();
else
if (this._computedStylePane.expanded || forceFetchComputedStyle) {
this._refreshUpdateInProgress = true;
- WebInspector.cssModel.getComputedStyleAsync(node.id, this._forcedPseudoClasses, computedStyleCallback.bind(this));
+ WebInspector.cssModel.getComputedStyleAsync(node.id, this.forcedPseudoClasses, computedStyleCallback.bind(this));
} else {
this._innerRefreshUpdate(node, null, editedSection);
if (userCallback)
}
if (this._computedStylePane.expanded)
- WebInspector.cssModel.getComputedStyleAsync(node.id, this._forcedPseudoClasses, computedCallback.bind(this));
+ WebInspector.cssModel.getComputedStyleAsync(node.id, this.forcedPseudoClasses, computedCallback.bind(this));
WebInspector.cssModel.getInlineStylesAsync(node.id, inlineCallback.bind(this));
- WebInspector.cssModel.getMatchedStylesAsync(node.id, this._forcedPseudoClasses, true, true, stylesCallback.bind(this));
+ WebInspector.cssModel.getMatchedStylesAsync(node.id, this.forcedPseudoClasses, true, true, stylesCallback.bind(this));
},
+ /**
+ * @param {function()=} userCallback
+ */
_validateNode: function(userCallback)
{
if (!this.node) {
} else {
this._elementStateButton.removeStyleClass("toggled");
this._elementStatePane.removeStyleClass("expanded");
- // Clear flags on hide.
- if (this._forcedPseudoClasses) {
- for (var i = 0; i < this._elementStatePane.inputs.length; ++i)
- this._elementStatePane.inputs[i].checked = false;
- delete this._forcedPseudoClasses;
- this._rebuildUpdate();
- }
}
},
function clickListener(event)
{
- var pseudoClasses = [];
- for (var i = 0; i < inputs.length; ++i) {
- if (inputs[i].checked)
- pseudoClasses.push(inputs[i].state);
- }
- this._forcedPseudoClasses = pseudoClasses.length ? pseudoClasses : undefined;
- this._rebuildUpdate();
+ var node = this._validateNode();
+ if (!node)
+ return;
+ this._setPseudoClassCallback(node.id, event.target.state, event.target.checked);
}
function createCheckbox(state)
margin-left: 8px;
}
+#elements-content .elements-gutter-decoration {
+ position: absolute;
+ left: 1px;
+ margin-top: 2px;
+ height: 8px;
+ width: 8px;
+ border-radius: 4px;
+ border: 1px solid orange;
+ background-color: orange;
+}
+
+#elements-content .elements-gutter-decoration.elements-has-decorated-children {
+ opacity: 0.5;
+}
+
.elements-tree-editor {
-webkit-user-select: text;
-webkit-user-modify: read-write-plaintext-only;