Bug 987797 - Font preview tooltip does not preview web fonts. r=bgrins

This commit is contained in:
Gabriel Luong 2014-05-27 18:55:00 +02:00
parent 846faa287d
commit 5acb30cc53
10 changed files with 140 additions and 70 deletions

View File

@ -37,7 +37,6 @@ const SPECTRUM_FRAME = "chrome://browser/content/devtools/spectrum-frame.xhtml";
const ESCAPE_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE;
const RETURN_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
const POPUP_EVENTS = ["shown", "hidden", "showing", "hiding"];
const FONT_FAMILY_PREVIEW_TEXT = "(ABCabc123&@%)";
/**
* Tooltip widget.
@ -636,7 +635,9 @@ Tooltip.prototype = {
* it was resized, if if was resized before this function was called.
* If not provided, will be measured on the loaded image.
* - maxDim : if the image should be resized before being shown, pass
* a number here
* a number here.
* - hideDimensionLabel : if the dimension label should be appended
* after the image.
*/
setImageContent: function(imageUrl, options={}) {
if (!imageUrl) {
@ -656,25 +657,28 @@ Tooltip.prototype = {
}
vbox.appendChild(image);
// Dimension label
let label = this.doc.createElement("label");
label.classList.add("devtools-tooltip-caption");
label.classList.add("theme-comment");
if (options.naturalWidth && options.naturalHeight) {
label.textContent = this._getImageDimensionLabel(options.naturalWidth,
options.naturalHeight);
} else {
// If no dimensions were provided, load the image to get them
label.textContent = l10n.strings.GetStringFromName("previewTooltip.image.brokenImage");
let imgObj = new this.doc.defaultView.Image();
imgObj.src = imageUrl;
imgObj.onload = () => {
imgObj.onload = null;
label.textContent = this._getImageDimensionLabel(imgObj.naturalWidth,
imgObj.naturalHeight);
if (!options.hideDimensionLabel) {
let label = this.doc.createElement("label");
label.classList.add("devtools-tooltip-caption");
label.classList.add("theme-comment");
if (options.naturalWidth && options.naturalHeight) {
label.textContent = this._getImageDimensionLabel(options.naturalWidth,
options.naturalHeight);
} else {
// If no dimensions were provided, load the image to get them
label.textContent = l10n.strings.GetStringFromName("previewTooltip.image.brokenImage");
let imgObj = new this.doc.defaultView.Image();
imgObj.src = imageUrl;
imgObj.onload = () => {
imgObj.onload = null;
label.textContent = this._getImageDimensionLabel(imgObj.naturalWidth,
imgObj.naturalHeight);
}
}
vbox.appendChild(label);
}
vbox.appendChild(label);
this.content = vbox;
},
@ -777,26 +781,30 @@ Tooltip.prototype = {
* This is based on Lea Verou's Dablet. See https://github.com/LeaVerou/dabblet
* for more info.
* @param {String} font The font family value.
* @param {object} nodeFront
* The NodeActor that will used to retrieve the dataURL for the font
* family tooltip contents.
* @return A promise that resolves when the font tooltip content is ready, or
* rejects if no font is provided
*/
setFontFamilyContent: function(font) {
if (!font) {
return;
setFontFamilyContent: Task.async(function*(font, nodeFront) {
if (!font || !nodeFront) {
throw "Missing font";
}
// Main container
let vbox = this.doc.createElement("vbox");
vbox.setAttribute("flex", "1");
if (typeof nodeFront.getFontFamilyDataURL === "function") {
font = font.replace(/"/g, "'");
font = font.replace("!important", "");
font = font.trim();
// Display the font family previewer
let previewer = this.doc.createElement("description");
previewer.setAttribute("flex", "1");
previewer.style.fontFamily = font;
previewer.classList.add("devtools-tooltip-font-previewer-text");
previewer.textContent = FONT_FAMILY_PREVIEW_TEXT;
vbox.appendChild(previewer);
let fillStyle = (Services.prefs.getCharPref("devtools.theme") === "light") ?
"black" : "white";
this.content = vbox;
}
let {data, size} = yield nodeFront.getFontFamilyDataURL(font, fillStyle);
let str = yield data.string();
this.setImageContent(str, { hideDimensionLabel: true, maxDim: size });
}
})
};
/**

View File

@ -545,8 +545,12 @@ CssHtmlTree.prototype = {
// Test for font family
if (propName.textContent === "font-family") {
this.tooltip.setFontFamilyContent(propValue.textContent);
return true;
let prop = propValue.textContent.toLowerCase();
if (prop !== "inherit" && prop !== "unset" && prop !== "initial") {
return this.tooltip.setFontFamilyContent(propValue.textContent,
inspector.selection.nodeFront);
}
}
}

View File

@ -1212,8 +1212,12 @@ CssRuleView.prototype = {
return this.previewTooltip.setRelativeImageContent(uri, this.inspector.inspector, dim);
}
if (tooltipType === "font") {
this.previewTooltip.setFontFamilyContent(target.textContent);
return true;
let prop = target.textContent.toLowerCase();
if (prop !== "inherit" && prop !== "unset" && prop !== "initial") {
return this.previewTooltip.setFontFamilyContent(target.textContent,
this.inspector.selection.nodeFront);
}
}
return false;

View File

@ -29,15 +29,15 @@ let test = asyncTest(function*() {
info("Selecting the test node");
yield selectNode("#testElement", inspector);
yield testRuleView(view);
yield testRuleView(view, inspector.selection.nodeFront);
info("Opening the computed view");
let {toolbox, inspector, view} = yield openComputedView();
yield testComputedView(view);
yield testComputedView(view, inspector.selection.nodeFront);
});
function* testRuleView(ruleView) {
function* testRuleView(ruleView, nodeFront) {
info("Testing font-family tooltips in the rule view");
let panel = ruleView.previewTooltip.panel;
@ -52,11 +52,15 @@ function* testRuleView(ruleView) {
// And verify that the tooltip gets shown on this property
yield assertHoverTooltipOn(ruleView.previewTooltip, valueSpan);
let description = panel.getElementsByTagName("description")[0];
is(description.style.fontFamily, "cursive", "Tooltips contains correct font-family style");
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* testComputedView(computedView) {
function* testComputedView(computedView, nodeFront) {
info("Testing font-family tooltips in the computed view");
let panel = computedView.tooltip.panel;
@ -64,6 +68,10 @@ function* testComputedView(computedView) {
yield assertHoverTooltipOn(computedView.tooltip, valueSpan);
let description = panel.getElementsByTagName("description")[0];
is(description.style.fontFamily, "cursive", "Tooltips contains correct font-family style");
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

@ -27,15 +27,15 @@ let test = asyncTest(function*() {
info("Selecting the test node");
yield selectNode("#testElement", inspector);
yield testRuleView(view);
yield testRuleView(view, inspector.selection.nodeFront);
info("Opening the computed view");
let {toolbox, inspector, view} = yield openComputedView();
yield testComputedView(view);
yield testComputedView(view, inspector.selection.nodeFront);
});
function* testRuleView(ruleView) {
function* testRuleView(ruleView, nodeFront) {
info("Testing font-family tooltips in the rule view");
let panel = ruleView.previewTooltip.panel;
@ -55,11 +55,15 @@ function* testRuleView(ruleView) {
// And verify that the tooltip gets shown on this property
yield assertHoverTooltipOn(ruleView.previewTooltip, valueSpan);
let description = panel.getElementsByTagName("description")[0];
is(description.style.fontFamily, "Arial", "Tooltips contains correct font-family style");
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* testComputedView(computedView) {
function* testComputedView(computedView, nodeFront) {
info("Testing font-family tooltips in the computed view");
let panel = computedView.tooltip.panel;
@ -67,6 +71,10 @@ function* testComputedView(computedView) {
yield assertHoverTooltipOn(computedView.tooltip, valueSpan);
let description = panel.getElementsByTagName("description")[0];
is(description.style.fontFamily, "Arial", "Tooltips contains correct font-family style");
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

@ -468,6 +468,22 @@ function hasSideBarTab(inspector, id) {
return !!inspector.sidebar.getWindowForTab(id);
}
/**
* Get the dataURL for the font family tooltip.
* @param {String} font The font family value.
* @param {object} nodeFront
* The NodeActor that will used to retrieve the dataURL for the
* font family tooltip contents.
*/
let getFontFamilyDataURL = Task.async(function*(font, nodeFront) {
let fillStyle = (Services.prefs.getCharPref("devtools.theme") === "light") ?
"black" : "white";
let {data} = yield nodeFront.getFontFamilyDataURL(font, fillStyle);
let dataURL = yield data.string();
return dataURL;
});
/* *********************************************
* RULE-VIEW
* *********************************************

View File

@ -184,14 +184,6 @@
margin-bottom: -4px;
}
/* Tooltip: Font Family Previewer Text */
.devtools-tooltip-font-previewer-text {
max-width: 400px;
line-height: 1.5;
font-size: 150%;
text-align: center;
}
/* Tooltip: Alert Icon */
.devtools-tooltip-alert-icon {

View File

@ -349,10 +349,6 @@ div.CodeMirror span.eval-text {
border-bottom: 1px solid #434850;
}
.theme-tooltip-panel .devtools-tooltip-font-previewer-text {
color: white;
}
.theme-tooltip-panel .devtools-tooltip-simple-text:last-child {
border-bottom: 0;
}

View File

@ -358,10 +358,6 @@ div.CodeMirror span.eval-text {
border-bottom: 1px solid #d9e1e8;
}
.theme-tooltip-panel .devtools-tooltip-font-previewer-text {
color: black;
}
.theme-tooltip-panel .devtools-tooltip-simple-text:last-child {
border-bottom: 0;
}

View File

@ -63,6 +63,8 @@ const {Class} = require("sdk/core/heritage");
const {PageStyleActor} = require("devtools/server/actors/styles");
const {HighlighterActor} = require("devtools/server/actors/highlighter");
const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog";
const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20;
const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
const XHTML_NS = "http://www.w3.org/1999/xhtml";
@ -351,6 +353,42 @@ var NodeActor = exports.NodeActor = protocol.ActorClass({
modifications: Arg(0, "array:json")
},
response: {}
}),
/**
* Given the font and fill style, get the image data of a canvas with the
* preview text and font.
* Returns an imageData object with the actual data being a LongStringActor
* and the width of the text as a string.
* The image data is transmitted as a base64 encoded png data-uri.
*/
getFontFamilyDataURL: method(function(font, fillStyle="black") {
let doc = this.rawNode.ownerDocument;
let canvas = doc.createElementNS(XHTML_NS, "canvas");
let ctx = canvas.getContext("2d");
let fontValue = FONT_FAMILY_PREVIEW_TEXT_SIZE + "px " + font + ", serif";
// Get the correct preview text measurements and set the canvas dimensions
ctx.font = fontValue;
let textWidth = ctx.measureText(FONT_FAMILY_PREVIEW_TEXT).width;
canvas.width = textWidth * 2;
canvas.height = FONT_FAMILY_PREVIEW_TEXT_SIZE * 3;
ctx.font = fontValue;
ctx.fillStyle = fillStyle;
// Align the text to be vertically center in the tooltip and
// oversample the canvas for better text quality
ctx.textBaseline = "top";
ctx.scale(2, 2);
ctx.fillText(FONT_FAMILY_PREVIEW_TEXT, 0, Math.round(FONT_FAMILY_PREVIEW_TEXT_SIZE / 3));
let dataURL = canvas.toDataURL("image/png");
return { data: LongStringActor(this.conn, dataURL), size: textWidth };
}, {
request: {font: Arg(0, "string"), fillStyle: Arg(1, "nullable:string")},
response: RetVal("imageData")
})
});