зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
e34c3dfb54
Коммит
ae34b55b6a
|
@ -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 };
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
Загрузка…
Ссылка в новой задаче