Bug 1266450 - part2: remove iframe container for HTML tooltip;r=bgrins

In order to have tooltips with a variable height, the tooltip container
should be allowed to resize itself on the fly, which cannot be achieved
with an iframe.

This changeset makes the HTMLTooltip rely on a HTML container inserted
in the XUL document directly. This allows to go back to a synchronous
API which also simplifies the implementation.

MozReview-Commit-ID: EDcsnVSKmeU

--HG--
extra : rebase_source : 80a22bc558468b69ff099602ab2364a55bcdd2f7
extra : source : 1794dbc179a093b26d06eadc18086c8f138dc008
This commit is contained in:
Julian Descottes 2016-05-30 23:02:58 +02:00
parent 973205986c
commit b46591f37c
8 changed files with 51 additions and 123 deletions

View File

@ -2704,10 +2704,10 @@ MarkupElementContainer.prototype = Heritage.extend(MarkupContainer.prototype, {
maxDim: Services.prefs.getIntPref(PREVIEW_MAX_DIM_PREF)
};
yield setImageTooltip(tooltip, this.markup.doc, data, options);
setImageTooltip(tooltip, this.markup.doc, data, options);
} catch (e) {
// Indicate the failure but show the tooltip anyway.
yield setBrokenImageTooltip(tooltip, this.markup.doc);
setBrokenImageTooltip(tooltip, this.markup.doc);
}
return true;
}),

View File

@ -128,7 +128,6 @@ devtools.jar:
content/shared/widgets/graphs-frame.xhtml (shared/widgets/graphs-frame.xhtml)
content/shared/widgets/spectrum-frame.xhtml (shared/widgets/spectrum-frame.xhtml)
content/shared/widgets/cubic-bezier-frame.xhtml (shared/widgets/cubic-bezier-frame.xhtml)
content/shared/widgets/tooltip-frame.xhtml (shared/widgets/tooltip-frame.xhtml)
content/shared/widgets/cubic-bezier.css (shared/widgets/cubic-bezier.css)
content/shared/widgets/mdn-docs-frame.xhtml (shared/widgets/mdn-docs-frame.xhtml)
content/shared/widgets/mdn-docs.css (shared/widgets/mdn-docs.css)

View File

@ -15,7 +15,9 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Tooltip test">
<vbox flex="1">
<hbox id="box1" flex="1">test1</hbox>
<hbox id="box1" flex="1">
<textbox></textbox>
</hbox>
<hbox id="box2" flex="1">test2</hbox>
<hbox id="box3" flex="1">
<textbox id="box3-input"></textbox>
@ -31,7 +33,7 @@ loadHelperScript("helper_html_tooltip.js");
add_task(function* () {
yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
let [, , doc] = yield createHost("bottom", TEST_URI);
yield testNoAutoFocus(doc);
yield testAutoFocus(doc);

View File

@ -72,9 +72,9 @@ add_task(function* () {
let arrow = tooltip.arrow;
ok(arrow, "Tooltip has an arrow");
// Get the geometry of the anchor, the tooltip frame & arrow.
// Get the geometry of the anchor, the tooltip panel & arrow.
let arrowBounds = arrow.getBoxQuads({relativeTo: doc})[0].bounds;
let frameBounds = tooltip.frame.getBoxQuads({relativeTo: doc})[0].bounds;
let panelBounds = tooltip.panel.getBoxQuads({relativeTo: doc})[0].bounds;
let anchorBounds = el.getBoxQuads({relativeTo: doc})[0].bounds;
let intersects = arrowBounds.left <= anchorBounds.right &&
@ -84,10 +84,10 @@ add_task(function* () {
ok(intersects || isBlockedByViewport,
"Tooltip arrow is aligned with the anchor, or stuck on viewport's edge.");
let isInFrame = arrowBounds.left >= frameBounds.left &&
arrowBounds.right <= frameBounds.right;
ok(isInFrame,
"The tooltip arrow remains inside the tooltip frame horizontally");
let isInPanel = arrowBounds.left >= panelBounds.left &&
arrowBounds.right <= panelBounds.right;
ok(isInPanel,
"The tooltip arrow remains inside the tooltip panel horizontally");
yield hideTooltip(tooltip);
}

View File

@ -66,9 +66,9 @@ add_task(function* () {
let arrow = tooltip.arrow;
ok(arrow, "Tooltip has an arrow");
// Get the geometry of the anchor, the tooltip frame & arrow.
// Get the geometry of the anchor, the tooltip panel & arrow.
let arrowBounds = arrow.getBoxQuads({relativeTo: doc})[0].bounds;
let frameBounds = tooltip.frame.getBoxQuads({relativeTo: doc})[0].bounds;
let panelBounds = tooltip.panel.getBoxQuads({relativeTo: doc})[0].bounds;
let anchorBounds = el.getBoxQuads({relativeTo: doc})[0].bounds;
let intersects = arrowBounds.left <= anchorBounds.right &&
@ -78,10 +78,10 @@ add_task(function* () {
ok(intersects || isBlockedByViewport,
"Tooltip arrow is aligned with the anchor, or stuck on viewport's edge.");
let isInFrame = arrowBounds.left >= frameBounds.left &&
arrowBounds.right <= frameBounds.right;
ok(isInFrame,
"The tooltip arrow remains inside the tooltip frame horizontally");
let isInPanel = arrowBounds.left >= panelBounds.left &&
arrowBounds.right <= panelBounds.right;
ok(isInPanel,
"The tooltip arrow remains inside the tooltip panel horizontally");
yield hideTooltip(tooltip);
}
});

View File

@ -12,9 +12,6 @@ const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipT
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const XHTML_NS = "http://www.w3.org/1999/xhtml";
const IFRAME_URL = "chrome://devtools/content/shared/widgets/tooltip-frame.xhtml";
const IFRAME_CONTAINER_ID = "tooltip-iframe-container";
const POSITION = {
TOP: "top",
BOTTOM: "bottom",
@ -79,51 +76,23 @@ function HTMLTooltip(toolbox,
this.container = this._createContainer();
// Promise that will resolve when the container can be filled with content.
this.containerReady = new Promise(resolve => {
if (this._isXUL()) {
// In XUL context, load a placeholder document in the iframe container.
let onLoad = () => {
this.frame.removeEventListener("load", onLoad, true);
resolve();
};
this.frame.addEventListener("load", onLoad, true);
this.frame.setAttribute("src", IFRAME_URL);
this.doc.querySelector("window").appendChild(this.container);
} else {
// In non-XUL context the container is ready to use as is.
this.doc.body.appendChild(this.container);
resolve();
}
});
if (this._isXUL()) {
this.doc.querySelector("window").appendChild(this.container);
} else {
// In non-XUL context the container is ready to use as is.
this.doc.body.appendChild(this.container);
}
}
module.exports.HTMLTooltip = HTMLTooltip;
HTMLTooltip.prototype = {
/**
* The tooltip frame is the child of the tooltip container that will only
* contain the tooltip content (and not the arrow or any other tooltip styling
* element).
* In XUL contexts, this is an iframe. In non XUL contexts this is a div,
* which also happens to be the tooltip.panel property.
*/
get frame() {
return this.container.querySelector(".tooltip-panel");
},
/**
* The tooltip panel is the parentNode of the tooltip content provided in
* setContent().
*/
get panel() {
if (!this._isXUL()) {
return this.frame;
}
// In XUL context, the content is wrapped in an iframe.
let win = this.frame.contentWindow.wrappedJSObject;
return win.document.getElementById(IFRAME_CONTAINER_ID);
return this.container.querySelector(".tooltip-panel");
},
/**
@ -153,10 +122,8 @@ HTMLTooltip.prototype = {
this.preferredWidth = width + themeWidth;
this.preferredHeight = height + themeHeight;
return this.containerReady.then(() => {
this.panel.innerHTML = "";
this.panel.appendChild(content);
});
this.panel.innerHTML = "";
this.panel.appendChild(content);
},
/**
@ -172,33 +139,31 @@ HTMLTooltip.prototype = {
* more space is available.
*/
show: function (anchor, {position} = {}) {
this.containerReady.then(() => {
let computedPosition = this._findBestPosition(anchor, position);
let computedPosition = this._findBestPosition(anchor, position);
let isTop = computedPosition.position === POSITION.TOP;
this.container.classList.toggle("tooltip-top", isTop);
this.container.classList.toggle("tooltip-bottom", !isTop);
let isTop = computedPosition.position === POSITION.TOP;
this.container.classList.toggle("tooltip-top", isTop);
this.container.classList.toggle("tooltip-bottom", !isTop);
this.container.style.width = computedPosition.width + "px";
this.container.style.height = computedPosition.height + "px";
this.container.style.top = computedPosition.top + "px";
this.container.style.left = computedPosition.left + "px";
this.container.style.width = computedPosition.width + "px";
this.container.style.height = computedPosition.height + "px";
this.container.style.top = computedPosition.top + "px";
this.container.style.left = computedPosition.left + "px";
if (this.type === TYPE.ARROW) {
this.arrow.style.left = computedPosition.arrowLeft + "px";
if (this.type === TYPE.ARROW) {
this.arrow.style.left = computedPosition.arrowLeft + "px";
}
this.container.classList.add("tooltip-visible");
this.attachEventsTimer = this.doc.defaultView.setTimeout(() => {
this._focusedElement = this.doc.activeElement;
if (this.autofocus) {
this.panel.focus();
}
this.container.classList.add("tooltip-visible");
this.attachEventsTimer = this.doc.defaultView.setTimeout(() => {
this._focusedElement = this.doc.activeElement;
if (this.autofocus) {
this.frame.focus();
}
this.topWindow.addEventListener("click", this._onClick, true);
this.emit("shown");
}, 0);
});
this.topWindow.addEventListener("click", this._onClick, true);
this.emit("shown");
}, 0);
},
/**
@ -242,12 +207,7 @@ HTMLTooltip.prototype = {
container.setAttribute("type", this.type);
container.classList.add("tooltip-container");
let html;
if (this._isXUL()) {
html = '<iframe class="devtools-tooltip-iframe tooltip-panel"></iframe>';
} else {
html = '<div class="tooltip-panel"></div>';
}
let html = '<div class="tooltip-panel"></div>';
if (this.type === TYPE.ARROW) {
html += '<div class="tooltip-arrow"></div>';

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
<style>
html, body, #tooltip-iframe-container {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
color: var(--theme-body-color);
}
:root[platform="linux"] body {
font-size: 80%;
}
</style>
</head>
<body role="application">
<div id="tooltip-iframe-container"></div>
</body>
</html>

View File

@ -62,8 +62,6 @@ function getImageDimensions(doc, imageUrl) {
* - {Number} naturalHeight mandatory, height of the image to display
* - {Number} maxDim optional, max width/height of the preview
* - {Boolean} hideDimensionLabel optional, pass true to hide the label
* @return {Promise} promise that will resolve when the tooltip content has been
* set
*/
function setImageTooltip(tooltip, doc, imageUrl, options) {
let {naturalWidth, naturalHeight, hideDimensionLabel, maxDim} = options;
@ -113,7 +111,7 @@ function setImageTooltip(tooltip, doc, imageUrl, options) {
}
let width = Math.max(CONTAINER_MIN_WIDTH, imgWidth + 2 * IMAGE_PADDING);
return tooltip.setContent(div, width, height);
tooltip.setContent(div, width, height);
}
/*
@ -124,8 +122,6 @@ function setImageTooltip(tooltip, doc, imageUrl, options) {
* The tooltip instance on which the image preview content should be set
* @param {Document} doc
* A document element to create the HTML elements needed for the tooltip
* @return {Promise} promise that will resolve when the tooltip content has been
* set
*/
function setBrokenImageTooltip(tooltip, doc) {
let div = doc.createElementNS(XHTML_NS, "div");
@ -137,7 +133,7 @@ function setBrokenImageTooltip(tooltip, doc) {
let message = GetStringFromName("previewTooltip.image.brokenImage");
div.textContent = message;
return tooltip.setContent(div, 150, 30);
tooltip.setContent(div, 150, 30);
}
module.exports.getImageDimensions = getImageDimensions;