Bug 1461522 - Allow { height: "auto" } in HTMLTooltip setContent and make it the default; r=jdescottes

The current default value for height of Infinity has the unfortunate side
effect that, when combined with using a XUL wrapper, there will be a large
filler element stretching vertically on one side of the tooltip that effectively
neuters all content beneath it. While this is probably fine for tooltips that
are shown on hover, it is problematic if we want to use this for DevTools menus
because it means the user is unable to click anything above/below the menu so
long as it is open (which can be particularly problematic once we make
HTMLTooltip support the "Disable popup autohide" feature"). Even if we were to
decide that clicks outside the tooltip should be consumed anyway we would still
have the problem that hover styles don't apply in this "dead" region.

As a result, this patch makes the { height: Infinity } behaviour opt-in for
those tooltips that really need it. For most uses, however, a height calculated
when the tooltip is shown should be sufficient (and later in this patch series
we will add a mechanism to HTMLTooltip to explicitly request it recalculate its
size and position in response to content changes).

This patch introduces the { height: "auto" } mechanism and also reverses the
order in which size/position properties are calculated to match the regular
manner in which layout is performed: widths first, then heights.

MozReview-Commit-ID: 7BeVkxGVhYn

--HG--
extra : rebase_source : 4f0d88b377e00efc9d443db1a5ef0a7d29295929
This commit is contained in:
Brian Birtles 2018-06-28 15:03:57 +09:00
Родитель e34c3dfb54
Коммит ae34b55b6a
4 изменённых файлов: 145 добавлений и 30 удалений

Просмотреть файл

@ -138,6 +138,7 @@ skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_html_tooltip_arrow-01.js]
[browser_html_tooltip_arrow-02.js]
[browser_html_tooltip_consecutive-show.js]
[browser_html_tooltip_height-auto.js]
[browser_html_tooltip_hover.js]
[browser_html_tooltip_offset.js]
[browser_html_tooltip_rtl.js]

Просмотреть файл

@ -0,0 +1,67 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip content can automatically calculate its height based on
* content.
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = CHROME_URL_ROOT + "doc_html_tooltip.xul";
const {HTMLTooltip} =
require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
let useXulWrapper;
add_task(async function() {
await addTab("about:blank");
const [,, doc] = await createHost("bottom", TEST_URI);
info("Run tests for a Tooltip without using a XUL panel");
useXulWrapper = false;
await runTests(doc);
info("Run tests for a Tooltip with a XUL panel");
useXulWrapper = true;
await runTests(doc);
});
async function runTests(doc) {
const tooltip = new HTMLTooltip(doc, {useXulWrapper});
info("Create tooltip content height to 150px");
const tooltipContent = doc.createElementNS(HTML_NS, "div");
tooltipContent.style.cssText =
"width: 300px; height: 150px; background: red;";
info("Set tooltip content using width:auto and height:auto");
tooltip.setContent(tooltipContent);
info("Show the tooltip and check the tooltip panel dimensions.");
await showTooltip(tooltip, doc.getElementById("box1"));
let panelRect = tooltip.panel.getBoundingClientRect();
is(panelRect.width, 300, "Tooltip panel has the expected width.");
is(panelRect.height, 150, "Tooltip panel has the expected width.");
await hideTooltip(tooltip);
info("Set tooltip content using fixed width and height:auto");
tooltipContent.style.cssText =
"width: auto; height: 200px; background: red;";
tooltip.setContent(tooltipContent, { width: 400 });
info("Show the tooltip and check the tooltip panel height.");
await showTooltip(tooltip, doc.getElementById("box1"));
panelRect = tooltip.panel.getBoundingClientRect();
is(panelRect.height, 200, "Tooltip panel has the expected width.");
await hideTooltip(tooltip);
tooltip.destroy();
}

Просмотреть файл

@ -197,7 +197,10 @@ EventTooltip.prototype = {
this._addContentListeners(header);
}
this._tooltip.setContent(this.container, {width: CONTAINER_WIDTH});
this._tooltip.setContent(
this.container,
{width: CONTAINER_WIDTH, height: Infinity}
);
this._tooltip.on("hidden", this.destroy);
},

Просмотреть файл

@ -255,6 +255,8 @@ function HTMLTooltip(toolboxDoc, {
this.autofocus = autofocus;
this.consumeOutsideClicks = consumeOutsideClicks;
this.useXulWrapper = this._isXUL() && useXulWrapper;
this.preferredWidth = "auto";
this.preferredHeight = "auto";
// The top window is used to attach click event listeners to close the tooltip if the
// user clicks on the content page.
@ -326,11 +328,21 @@ HTMLTooltip.prototype = {
* @param {Object}
* - {Number} width: preferred width for the tooltip container. If not specified
* the tooltip container will be measured before being displayed, and the
* measured width will be used as preferred width.
* - {Number} height: optional, preferred height for the tooltip container. If
* not specified, the tooltip will be able to use all the height available.
* measured width will be used as the preferred width.
* - {Number} height: preferred height for the tooltip container. If
* not specified the tooltip container will be measured before being
* displayed, and the measured height will be used as the preferred
* height.
*
* For tooltips whose content height may change while being
* displayed, the special value Infinity may be used to produce
* a flexible container that accommodates resizing content. Note,
* however, that when used in combination with the XUL wrapper the
* unfilled part of this container will consume all mouse events
* making content behind this area inaccessible until the tooltip is
* dismissed.
*/
setContent: function(content, {width = "auto", height = Infinity} = {}) {
setContent: function(content, {width = "auto", height = "auto"} = {}) {
this.preferredWidth = width;
this.preferredHeight = height;
@ -366,28 +378,21 @@ HTMLTooltip.prototype = {
// Get viewport size
const viewportRect = this._getViewportRect();
const themeHeight = EXTRA_HEIGHT[this.type] + 2 * EXTRA_BORDER[this.type];
const preferredHeight = this.preferredHeight + themeHeight;
const {top, height, computedPosition} =
calculateVerticalPosition(anchorRect, viewportRect, preferredHeight, position, y);
this._position = computedPosition;
// Apply height before measuring the content width (if width="auto").
const isTop = computedPosition === POSITION.TOP;
this.container.classList.toggle("tooltip-top", isTop);
this.container.classList.toggle("tooltip-bottom", !isTop);
// If the preferred height is set to Infinity, the tooltip container should grow based
// on its content's height and use as much height as possible.
this.container.classList.toggle("tooltip-flexible-height",
this.preferredHeight === Infinity);
this.container.style.height = height + "px";
// Calculate the horizonal position and width
let preferredWidth;
// Record the height too since it might save us from having to look it up
// later.
let measuredHeight;
if (this.preferredWidth === "auto") {
preferredWidth = this._measureContainerWidth();
// Reset any styles that constrain the dimensions we want to calculate.
this.container.style.width = "auto";
if (this.preferredHeight === "auto") {
this.container.style.height = "auto";
}
({
width: preferredWidth,
height: measuredHeight,
} = this._measureContainerSize());
} else {
const themeWidth = 2 * EXTRA_BORDER[this.type];
preferredWidth = this.preferredWidth + themeWidth;
@ -398,12 +403,47 @@ HTMLTooltip.prototype = {
const {left, width, arrowLeft} = calculateHorizontalPosition(
anchorRect, viewportRect, preferredWidth, this.type, x, isRtl);
this.container.style.width = width + "px";
// If we constrained the width, then any measured height we have is no
// longer valid.
if (measuredHeight && width !== preferredWidth) {
measuredHeight = undefined;
}
// Apply width and arrow positioning
this.container.style.width = width + "px";
if (this.type === TYPE.ARROW) {
this.arrow.style.left = arrowLeft + "px";
}
// Calculate the vertical position and height
let preferredHeight;
if (this.preferredHeight === "auto") {
if (measuredHeight) {
this.container.style.height = "auto";
preferredHeight = measuredHeight;
} else {
({ height: preferredHeight } = this._measureContainerSize());
}
} else {
const themeHeight = EXTRA_HEIGHT[this.type] + 2 * EXTRA_BORDER[this.type];
preferredHeight = this.preferredHeight + themeHeight;
}
const {top, height, computedPosition} =
calculateVerticalPosition(anchorRect, viewportRect, preferredHeight, position, y);
this._position = computedPosition;
const isTop = computedPosition === POSITION.TOP;
this.container.classList.toggle("tooltip-top", isTop);
this.container.classList.toggle("tooltip-bottom", !isTop);
// If the preferred height is set to Infinity, the tooltip container should grow based
// on its content's height and use as much height as possible.
this.container.classList.toggle("tooltip-flexible-height",
this.preferredHeight === Infinity);
this.container.style.height = height + "px";
if (this.useXulWrapper) {
await this._showXulWrapperAt(left, top);
} else {
@ -455,7 +495,7 @@ HTMLTooltip.prototype = {
return this.doc.documentElement.getBoundingClientRect();
},
_measureContainerWidth: function() {
_measureContainerSize: function() {
const xulParent = this.container.parentNode;
if (this.useXulWrapper && !this.isVisible()) {
// Move the container out of the XUL Panel to measure it.
@ -463,15 +503,19 @@ HTMLTooltip.prototype = {
}
this.container.classList.add("tooltip-hidden");
this.container.style.width = "auto";
const width = this.container.getBoundingClientRect().width;
// Set either of the tooltip-top or tooltip-bottom styles so that we get an
// accurate height. We're assuming that the two styles will be symmetrical
// and that we will clear this as necessary later.
this.container.classList.add("tooltip-top");
this.container.classList.remove("tooltip-bottom");
const { width, height } = this.container.getBoundingClientRect();
this.container.classList.remove("tooltip-hidden");
if (this.useXulWrapper && !this.isVisible()) {
xulParent.appendChild(this.container);
}
return width;
return { width, height };
},
/**