Bug 1059360 - Highlight nodes that match selectors in the computed-view on mouse-over. r=miker

This commit is contained in:
Patrick Brosset 2014-09-02 04:13:00 +02:00
parent 62e533ef60
commit eb93385b5d
9 changed files with 319 additions and 70 deletions

View File

@ -324,38 +324,83 @@ CssHtmlTree.prototype = {
* returns null of the node isn't anything we care about
*/
getNodeInfo: function(node) {
let type, value;
if (!node) {
return null;
}
let classes = node.classList;
if (classes.contains("property-name") ||
classes.contains("property-value") ||
(classes.contains("theme-link") && !classes.contains("link"))) {
// Go up to the common parent to find the property and value
let parent = node.parentNode;
while (!parent.classList.contains("property-view")) {
parent = parent.parentNode;
// Check if the node isn't a selector first since this doesn't require
// walking the DOM
if (classes.contains("matched") ||
classes.contains("bestmatch") ||
classes.contains("parentmatch")) {
let selectorText = "";
for (let child of node.childNodes) {
if (child.nodeType === node.TEXT_NODE) {
selectorText += child.textContent;
}
}
return {
type: overlays.VIEW_NODE_SELECTOR_TYPE,
value: selectorText.trim()
}
}
// Walk up the nodes to find out where node is
let propertyView;
let propertyContent;
let parent = node;
while (parent.parentNode) {
if (parent.classList.contains("property-view")) {
propertyView = parent;
break;
}
if (parent.classList.contains("property-content")) {
propertyContent = parent;
break;
}
parent = parent.parentNode;
}
if (!propertyView && !propertyContent) {
return null;
}
let value, type;
// Get the property and value for a node that's a property name or value
let isHref = classes.contains("theme-link") && !classes.contains("link");
if (propertyView && (classes.contains("property-name") ||
classes.contains("property-value") ||
isHref)) {
value = {
property: parent.querySelector(".property-name").textContent,
value: parent.querySelector(".property-value").textContent
};
}
if (propertyContent && (classes.contains("other-property-value") ||
isHref)) {
let view = propertyContent.previousSibling;
value = {
property: view.querySelector(".property-name").textContent,
value: node.textContent
};
}
// Get the type
if (classes.contains("property-name")) {
type = overlays.VIEW_NODE_PROPERTY_TYPE;
} else if (classes.contains("property-value")) {
} else if (classes.contains("property-value") ||
classes.contains("other-property-value")) {
type = overlays.VIEW_NODE_VALUE_TYPE;
} else if (classes.contains("theme-link")) {
} else if (isHref) {
type = overlays.VIEW_NODE_IMAGE_URL_TYPE;
value.url = node.href;
} else {
return null;
}
return {
type: type,
value: value
};
return {type, value};
},
_createPropertyViews: function()

View File

@ -1248,6 +1248,10 @@ CssRuleView.prototype = {
* returns null of the node isn't anything we care about
*/
getNodeInfo: function(node) {
if (!node) {
return null;
}
let type, value;
let classes = node.classList;
let prop = getParentTextProperty(node);

View File

@ -25,6 +25,7 @@ support-files =
head.js
[browser_computedview_browser-styles.js]
[browser_computedview_getNodeInfo.js]
[browser_computedview_keybindings_01.js]
[browser_computedview_keybindings_02.js]
[browser_computedview_matched-selectors-toggle.js]

View File

@ -0,0 +1,177 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test various output of the computed-view's getNodeInfo method.
// This method is used by the style-inspector-overlay on mouseover to decide
// which tooltip or highlighter to show when hovering over a value/name/selector
// if any.
// For instance, browser_ruleview_selector-highlighter_01.js and
// browser_ruleview_selector-highlighter_02.js test that the selector
// highlighter appear when hovering over a selector in the rule-view.
// Since the code to make this work for the computed-view is 90% the same, there
// is no need for testing it again here.
// This test however serves as a unit test for getNodeInfo.
const {
VIEW_NODE_SELECTOR_TYPE,
VIEW_NODE_PROPERTY_TYPE,
VIEW_NODE_VALUE_TYPE,
VIEW_NODE_IMAGE_URL_TYPE
} = devtools.require("devtools/styleinspector/style-inspector-overlays");
const PAGE_CONTENT = [
'<style type="text/css">',
' body {',
' background: red;',
' color: white;',
' }',
' div {',
' background: green;',
' }',
' div div {',
' background-color: yellow;',
' background-image: url(chrome://global/skin/icons/warning-64.png);',
' color: red;',
' }',
'</style>',
'<div><div id="testElement">Test element</div></div>'
].join("\n");
// Each item in this array must have the following properties:
// - desc {String} will be logged for information
// - getHoveredNode {Generator Function} received the computed-view instance as
// argument and must return the node to be tested
// - assertNodeInfo {Function} should check the validity of the nodeInfo
// argument it receives
const TEST_DATA = [
{
desc: "Testing a null node",
getHoveredNode: function*() {
return null;
},
assertNodeInfo: function(nodeInfo) {
is(nodeInfo, null);
}
},
{
desc: "Testing a useless node",
getHoveredNode: function*(view) {
return view.element;
},
assertNodeInfo: function(nodeInfo) {
is(nodeInfo, null);
}
},
{
desc: "Testing a property name",
getHoveredNode: function*(view) {
return getComputedViewProperty(view, "color").nameSpan;
},
assertNodeInfo: function(nodeInfo) {
is(nodeInfo.type, VIEW_NODE_PROPERTY_TYPE);
ok("property" in nodeInfo.value);
ok("value" in nodeInfo.value);
is(nodeInfo.value.property, "color");
is(nodeInfo.value.value, "#F00");
}
},
{
desc: "Testing a property value",
getHoveredNode: function*(view) {
return getComputedViewProperty(view, "color").valueSpan;
},
assertNodeInfo: function(nodeInfo) {
is(nodeInfo.type, VIEW_NODE_VALUE_TYPE);
ok("property" in nodeInfo.value);
ok("value" in nodeInfo.value);
is(nodeInfo.value.property, "color");
is(nodeInfo.value.value, "#F00");
}
},
{
desc: "Testing an image url",
getHoveredNode: function*(view) {
let {valueSpan} = getComputedViewProperty(view, "background-image");
return valueSpan.querySelector(".theme-link");
},
assertNodeInfo: function(nodeInfo) {
is(nodeInfo.type, VIEW_NODE_IMAGE_URL_TYPE);
ok("property" in nodeInfo.value);
ok("value" in nodeInfo.value);
is(nodeInfo.value.property, "background-image");
is(nodeInfo.value.value, "url(\"chrome://global/skin/icons/warning-64.png\")");
is(nodeInfo.value.url, "chrome://global/skin/icons/warning-64.png");
}
},
{
desc: "Testing a matched rule selector (bestmatch)",
getHoveredNode: function*(view) {
let content = yield getComputedViewMatchedRules(view, "background-color");
return content.querySelector(".bestmatch");
},
assertNodeInfo: function(nodeInfo) {
is(nodeInfo.type, VIEW_NODE_SELECTOR_TYPE);
is(nodeInfo.value, "div div");
}
},
{
desc: "Testing a matched rule selector (matched)",
getHoveredNode: function*(view) {
let content = yield getComputedViewMatchedRules(view, "background-color");
return content.querySelector(".matched");
},
assertNodeInfo: function(nodeInfo) {
is(nodeInfo.type, VIEW_NODE_SELECTOR_TYPE);
is(nodeInfo.value, "div");
}
},
{
desc: "Testing a matched rule selector (parentmatch)",
getHoveredNode: function*(view) {
let content = yield getComputedViewMatchedRules(view, "color");
return content.querySelector(".parentmatch");
},
assertNodeInfo: function(nodeInfo) {
is(nodeInfo.type, VIEW_NODE_SELECTOR_TYPE);
is(nodeInfo.value, "body");
}
},
{
desc: "Testing a matched rule value",
getHoveredNode: function*(view) {
let content = yield getComputedViewMatchedRules(view, "color");
return content.querySelector(".other-property-value");
},
assertNodeInfo: function(nodeInfo) {
is(nodeInfo.type, VIEW_NODE_VALUE_TYPE);
is(nodeInfo.value.property, "color");
is(nodeInfo.value.value, "#F00");
}
},
{
desc: "Testing a matched rule stylesheet link",
getHoveredNode: function*(view) {
let content = yield getComputedViewMatchedRules(view, "color");
return content.querySelector(".rule-link .theme-link");
},
assertNodeInfo: function(nodeInfo) {
is(nodeInfo, null);
}
}
];
let test = asyncTest(function*() {
yield addTab("data:text/html;charset=utf-8," + PAGE_CONTENT);
let {inspector, view} = yield openComputedView();
yield selectNode("#testElement", inspector);
for (let {desc, getHoveredNode, assertNodeInfo} of TEST_DATA) {
info(desc);
let nodeInfo = view.getNodeInfo(yield getHoveredNode(view));
assertNodeInfo(nodeInfo);
}
});

View File

@ -23,7 +23,7 @@ let test = asyncTest(function*() {
yield selectNode("div", inspector);
info("Expanding the first property");
yield expandComputedViewPropertyByIndex(view, inspector, 0);
yield expandComputedViewPropertyByIndex(view, 0);
info("Verifying the link text");
yield verifyLinkText(view, SCSS_LOC);

View File

@ -61,7 +61,7 @@ let test = asyncTest(function*() {
function* testInlineStyle(view, inspector) {
info("Testing inline style");
yield expandComputedViewPropertyByIndex(view, inspector, 0);
yield expandComputedViewPropertyByIndex(view, 0);
let onWindow = waitForWindow();
info("Clicking on the first rule-link in the computed-view");

View File

@ -35,6 +35,8 @@ let test = asyncTest(function*() {
let {toolbox, inspector, view} = yield openComputedView();
yield testComputedView(view, inspector.selection.nodeFront);
yield testExpandedComputedViewProperty(view, inspector.selection.nodeFront);
});
function* testRuleView(ruleView, nodeFront) {
@ -77,3 +79,39 @@ function* testComputedView(computedView, nodeFront) {
let dataURL = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront);
is(images[0].getAttribute("src"), dataURL, "Tooltip contains the correct data-uri image");
}
function* testExpandedComputedViewProperty(computedView, nodeFront) {
info("Testing font-family tooltips in expanded properties of the computed view");
info("Expanding the font-family property to reveal matched selectors");
let propertyView = getPropertyView(computedView, "font-family");
propertyView.matchedExpanded = true;
yield propertyView.refreshMatchedSelectors();
let valueSpan = propertyView.matchedSelectorsContainer
.querySelector(".bestmatch .other-property-value");
let tooltip = computedView.tooltips.previewTooltip;
let panel = tooltip.panel;
yield assertHoverTooltipOn(tooltip, valueSpan);
let images = panel.getElementsByTagName("image");
is(images.length, 1, "Tooltip contains an image");
ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri image as expected");
let dataURL = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront);
is(images[0].getAttribute("src"), dataURL, "Tooltip contains the correct data-uri image");
}
function getPropertyView(computedView, name) {
let propertyView = null;
computedView.propertyViews.some(function(view) {
if (view.name == name) {
propertyView = view;
return true;
}
return false;
});
return propertyView;
}

View File

@ -28,11 +28,6 @@ let test = asyncTest(function*() {
yield selectNode("#testElement", inspector);
yield testRuleView(view, inspector.selection.nodeFront);
info("Opening the computed view");
let {toolbox, inspector, view} = yield openComputedView();
yield testComputedView(view, inspector.selection.nodeFront);
});
function* testRuleView(ruleView, nodeFront) {
@ -63,21 +58,3 @@ function* testRuleView(ruleView, nodeFront) {
let dataURL = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront);
is(images[0].getAttribute("src"), dataURL, "Tooltip contains the correct data-uri image");
}
function* testComputedView(computedView, nodeFront) {
info("Testing font-family tooltips in the computed view");
let tooltip = computedView.tooltips.previewTooltip;
let panel = tooltip.panel;
let {valueSpan} = getComputedViewProperty(computedView, "font-family");
yield assertHoverTooltipOn(tooltip, valueSpan);
let images = panel.getElementsByTagName("image");
is(images.length, 1, "Tooltip contains an image");
ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri image as expected");
let dataURL = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront);
is(images[0].getAttribute("src"), dataURL, "Tooltip contains the correct data-uri image");
}

View File

@ -752,32 +752,6 @@ let createNewRuleViewProperty = Task.async(function*(ruleEditor, inputValue) {
yield onFocus;
});
// TO BE UNCOMMENTED WHEN THE EYEDROPPER FINALLY LANDS
// /**
// * Given a color swatch in the ruleview, click on it to open the color picker
// * and then click on the eyedropper button to start the eyedropper tool
// * @param {CssRuleView} view The instance of the rule-view panel
// * @param {DOMNode} swatch The color swatch to be clicked on
// * @return A promise that resolves when the dropper is opened
// */
// let openRuleViewEyeDropper = Task.async(function*(view, swatch) {
// info("Opening the colorpicker tooltip on a colorswatch");
// let tooltip = view.colorPicker.tooltip;
// let onTooltipShown = tooltip.once("shown");
// swatch.click();
// yield onTooltipShown;
// info("Finding the eyedropper icon in the colorpicker document");
// let tooltipDoc = tooltip.content.contentDocument;
// let dropperButton = tooltipDoc.querySelector("#eyedropper-button");
// ok(dropperButton, "Found the eyedropper icon");
// info("Opening the eyedropper");
// let onOpen = tooltip.once("eyedropper-opened");
// dropperButton.click();
// return yield onOpen;
// });
/* *********************************************
* COMPUTED-VIEW
* *********************************************
@ -806,6 +780,40 @@ function getComputedViewProperty(view, name) {
return prop;
}
/**
* Get a reference to the property-content element for a given property name in
* the computed-view.
* A property-content element always follows (nextSibling) the property itself
* and is only shown when the twisty icon is expanded on the property.
* A property-content element contains matched rules, with selectors, properties,
* values and stylesheet links
* @param {CssHtmlTree} view The instance of the computed view panel
* @param {String} name The name of the property to retrieve
* @return {Promise} A promise that resolves to the property matched rules
* container
*/
let getComputedViewMatchedRules = Task.async(function*(view, name) {
let expander;
let propertyContent;
for (let property of view.styleDocument.querySelectorAll(".property-view")) {
let nameSpan = property.querySelector(".property-name");
if (nameSpan.textContent === name) {
expander = property.querySelector(".expandable");
propertyContent = property.nextSibling;
break;
}
}
if (!expander.hasAttribute("open")) {
// Need to expand the property
let onExpand = view.inspector.once("computed-view-property-expanded");
expander.click();
yield onExpand;
}
return propertyContent;
});
/**
* Get the text value of the property corresponding to a given name in the
* computed-view
@ -813,8 +821,8 @@ function getComputedViewProperty(view, name) {
* @param {String} name The name of the property to retrieve
* @return {String} The property value
*/
function getComputedViewPropertyValue(view, selectorText, propertyName) {
return getComputedViewProperty(view, selectorText, propertyName)
function getComputedViewPropertyValue(view, name, propertyName) {
return getComputedViewProperty(view, name, propertyName)
.valueSpan.textContent;
}
@ -822,19 +830,18 @@ function getComputedViewPropertyValue(view, selectorText, propertyName) {
* Expand a given property, given its index in the current property list of
* the computed view
* @param {CssHtmlTree} view The instance of the computed view panel
* @param {InspectorPanel} inspector The instance of the inspector panel
* @param {Number} index The index of the property to be expanded
* @return a promise that resolves when the property has been expanded, or
* rejects if the property was not found
*/
function expandComputedViewPropertyByIndex(view, inspector, index) {
function expandComputedViewPropertyByIndex(view, index) {
info("Expanding property " + index + " in the computed view");
let expandos = view.styleDocument.querySelectorAll(".expandable");
if (!expandos.length || !expandos[index]) {
return promise.reject();
}
let onExpand = inspector.once("computed-view-property-expanded");
let onExpand = view.inspector.once("computed-view-property-expanded");
expandos[index].click();
return onExpand;
}