--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -119,16 +119,17 @@ skip-if = e10s # Bug 1221911, bug 122228
[browser_html_tooltip-04.js]
[browser_html_tooltip-05.js]
[browser_html_tooltip_arrow-01.js]
[browser_html_tooltip_arrow-02.js]
[browser_html_tooltip_consecutive-show.js]
[browser_html_tooltip_offset.js]
[browser_html_tooltip_variable-height.js]
[browser_html_tooltip_width-auto.js]
+[browser_html_tooltip_xul-wrapper.js]
[browser_inplace-editor-01.js]
[browser_inplace-editor-02.js]
[browser_inplace-editor_autocomplete_01.js]
[browser_inplace-editor_autocomplete_02.js]
[browser_inplace-editor_autocomplete_offset.js]
[browser_inplace-editor_maxwidth.js]
[browser_key_shortcuts.js]
[browser_layoutHelpers.js]
--- a/devtools/client/shared/test/browser_html_tooltip-01.js
+++ b/devtools/client/shared/test/browser_html_tooltip-01.js
@@ -4,63 +4,57 @@
"use strict";
/**
* Test the HTMLTooltip show & hide methods.
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
-const TEST_WINDOW_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
+const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
<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="box2" flex="1">test2</hbox>
<hbox id="box3" flex="1">test3</hbox>
<hbox id="box4" flex="1">test4</hbox>
</vbox>
</window>`;
-const TEST_PAGE_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
- <?xml-stylesheet href="chrome://global/skin/global.css"?>
- <?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
- <page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
- title="Tooltip test with document using a Page element">
- <vbox flex="1">
- <hbox id="box1" flex="1">test1</hbox>
- </vbox>
- </page>`;
-
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
+let useXulWrapper;
+
function getTooltipContent(doc) {
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "50px";
div.style.boxSizing = "border-box";
div.textContent = "tooltip";
return div;
}
add_task(function* () {
- info("Test showing a basic tooltip in XUL document using <window>");
- yield testTooltipForUri(TEST_WINDOW_URI);
+ let [,, doc] = yield createHost("bottom", TEST_URI);
- info("Test showing a basic tooltip in XUL document using <page>");
- yield testTooltipForUri(TEST_PAGE_URI);
+ info("Run tests for a Tooltip without using a XUL panel");
+ useXulWrapper = false;
+ yield runTests(doc);
+
+ info("Run tests for a Tooltip with a XUL panel");
+ useXulWrapper = true;
+ yield runTests(doc);
});
-function* testTooltipForUri(uri) {
- let tab = yield addTab("about:blank");
- let [,, doc] = yield createHost("bottom", uri);
-
- let tooltip = new HTMLTooltip({doc}, {});
+function* runTests(doc) {
+ yield addTab("about:blank");
+ let tooltip = new HTMLTooltip({doc}, {useXulWrapper});
info("Set tooltip content");
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
is(tooltip.isVisible(), false, "Tooltip is not visible");
info("Show the tooltip and check the expected events are fired.");
@@ -85,10 +79,10 @@ function* testTooltipForUri(uri) {
tooltip.hide();
yield onPopupHidden;
is(hidden, 1, "Event hidden was fired once");
yield waitForReflow(tooltip);
is(tooltip.isVisible(), false, "Tooltip is not visible");
- yield removeTab(tab);
+ tooltip.destroy();
}
--- a/devtools/client/shared/test/browser_html_tooltip-02.js
+++ b/devtools/client/shared/test/browser_html_tooltip-02.js
@@ -22,47 +22,59 @@ const TEST_URI = `data:text/xml;charset=
<hbox id="box4" flex="1">test4</hbox>
<iframe id="frame" width="200"></iframe>
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
+let useXulWrapper;
+
add_task(function* () {
yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
+ info("Run tests for a Tooltip without using a XUL panel");
+ useXulWrapper = false;
+ yield runTests(doc);
+
+ info("Run tests for a Tooltip with a XUL panel");
+ useXulWrapper = true;
+ yield runTests(doc);
+});
+
+function* runTests(doc) {
yield testClickInTooltipContent(doc);
yield testConsumeOutsideClicksFalse(doc);
yield testConsumeOutsideClicksTrue(doc);
yield testClickInOuterIframe(doc);
yield testClickInInnerIframe(doc);
-});
+}
function* testClickInTooltipContent(doc) {
info("Test a tooltip is not closed when clicking inside itself");
- let tooltip = new HTMLTooltip({doc}, {});
+ let tooltip = new HTMLTooltip({doc}, {useXulWrapper});
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
yield showTooltip(tooltip, doc.getElementById("box1"));
let onTooltipContainerClick = once(tooltip.container, "click");
EventUtils.synthesizeMouseAtCenter(tooltip.container, {}, doc.defaultView);
yield onTooltipContainerClick;
is(tooltip.isVisible(), true, "Tooltip is still visible");
tooltip.destroy();
}
function* testConsumeOutsideClicksFalse(doc) {
info("Test closing a tooltip via click with consumeOutsideClicks: false");
let box4 = doc.getElementById("box4");
- let tooltip = new HTMLTooltip({doc}, {consumeOutsideClicks: false});
+ let tooltip = new HTMLTooltip({doc}, {consumeOutsideClicks: false, useXulWrapper});
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
yield showTooltip(tooltip, doc.getElementById("box1"));
let onBox4Clicked = once(box4, "click");
let onHidden = once(tooltip, "hidden");
EventUtils.synthesizeMouseAtCenter(box4, {}, doc.defaultView);
yield onHidden;
yield onBox4Clicked;
@@ -75,17 +87,17 @@ function* testConsumeOutsideClicksFalse(
function* testConsumeOutsideClicksTrue(doc) {
info("Test closing a tooltip via click with consumeOutsideClicks: true");
let box4 = doc.getElementById("box4");
// Count clicks on box4
let box4clicks = 0;
box4.addEventListener("click", () => box4clicks++);
- let tooltip = new HTMLTooltip({doc}, {consumeOutsideClicks: true});
+ let tooltip = new HTMLTooltip({doc}, {consumeOutsideClicks: true, useXulWrapper});
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
yield showTooltip(tooltip, doc.getElementById("box1"));
let onHidden = once(tooltip, "hidden");
EventUtils.synthesizeMouseAtCenter(box4, {}, doc.defaultView);
yield onHidden;
is(box4clicks, 0, "box4 catched no click event");
@@ -93,32 +105,32 @@ function* testConsumeOutsideClicksTrue(d
tooltip.destroy();
}
function* testClickInOuterIframe(doc) {
info("Test clicking an iframe outside of the tooltip closes the tooltip");
let frame = doc.getElementById("frame");
- let tooltip = new HTMLTooltip({doc});
+ let tooltip = new HTMLTooltip({doc}, {useXulWrapper});
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
yield showTooltip(tooltip, doc.getElementById("box1"));
let onHidden = once(tooltip, "hidden");
EventUtils.synthesizeMouseAtCenter(frame, {}, doc.defaultView);
yield onHidden;
is(tooltip.isVisible(), false, "Tooltip is hidden");
tooltip.destroy();
}
function* testClickInInnerIframe(doc) {
info("Test clicking an iframe inside the tooltip content does not close the tooltip");
- let tooltip = new HTMLTooltip({doc}, {consumeOutsideClicks: false});
+ let tooltip = new HTMLTooltip({doc}, {consumeOutsideClicks: false, useXulWrapper});
let iframe = doc.createElementNS(HTML_NS, "iframe");
iframe.style.width = "100px";
iframe.style.height = "50px";
tooltip.setContent(iframe, {width: 100, height: 50});
yield showTooltip(tooltip, doc.getElementById("box1"));
let onTooltipContainerClick = once(tooltip.container, "click");
--- a/devtools/client/shared/test/browser_html_tooltip-03.js
+++ b/devtools/client/shared/test/browser_html_tooltip-03.js
@@ -26,24 +26,36 @@ const TEST_URI = `data:text/xml;charset=
<textbox id="box4-input"></textbox>
</hbox>
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
+let useXulWrapper;
+
add_task(function* () {
yield addTab("about:blank");
let [, , doc] = yield createHost("bottom", TEST_URI);
+ info("Run tests for a Tooltip without using a XUL panel");
+ useXulWrapper = false;
+ yield runTests(doc);
+
+ info("Run tests for a Tooltip with a XUL panel");
+ useXulWrapper = true;
+ yield runTests(doc);
+});
+
+function* runTests(doc) {
yield testNoAutoFocus(doc);
yield testAutoFocus(doc);
yield testAutoFocusPreservesFocusChange(doc);
-});
+}
function* testNoAutoFocus(doc) {
yield focusNode(doc, "#box4-input");
ok(doc.activeElement.closest("#box4-input"), "Focus is in the #box4-input");
info("Test a tooltip without autofocus will not take focus");
let tooltip = yield createTooltip(doc, false);
@@ -121,17 +133,17 @@ function blurNode(doc, selector) {
*
* @param {Document} doc
* Document in which the tooltip should be created
* @param {Boolean} autofocus
* @return {Promise} promise that will resolve the HTMLTooltip instance created when the
* tooltip content will be ready.
*/
function* createTooltip(doc, autofocus) {
- let tooltip = new HTMLTooltip({doc}, {autofocus});
+ let tooltip = new HTMLTooltip({doc}, {autofocus, useXulWrapper});
let div = doc.createElementNS(HTML_NS, "div");
div.classList.add("tooltip-content");
div.style.height = "50px";
div.innerHTML = '<input type="text"></input>';
tooltip.setContent(div, {width: 150, height: 50});
return tooltip;
}
--- a/devtools/client/shared/test/browser_html_tooltip-04.js
+++ b/devtools/client/shared/test/browser_html_tooltip-04.js
@@ -35,17 +35,17 @@ const TOOLTIP_WIDTH = 100;
add_task(function* () {
// Force the toolbox to be 400px high;
yield pushPref("devtools.toolbox.footer.height", 400);
yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
info("Create HTML tooltip");
- let tooltip = new HTMLTooltip({doc}, {});
+ let tooltip = new HTMLTooltip({doc}, {useXulWrapper: false});
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "100%";
tooltip.setContent(div, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
let box1 = doc.getElementById("box1");
let box2 = doc.getElementById("box2");
let box3 = doc.getElementById("box3");
let box4 = doc.getElementById("box4");
--- a/devtools/client/shared/test/browser_html_tooltip-05.js
+++ b/devtools/client/shared/test/browser_html_tooltip-05.js
@@ -27,22 +27,21 @@ const {HTMLTooltip} = require("devtools/
loadHelperScript("helper_html_tooltip.js");
const TOOLTIP_HEIGHT = 200;
const TOOLTIP_WIDTH = 200;
add_task(function* () {
// Force the toolbox to be 200px high;
yield pushPref("devtools.toolbox.footer.height", 200);
-
yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
info("Create HTML tooltip");
- let tooltip = new HTMLTooltip({doc}, {});
+ let tooltip = new HTMLTooltip({doc}, {useXulWrapper: false});
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "100%";
tooltip.setContent(div, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
let box1 = doc.getElementById("box1");
let box2 = doc.getElementById("box2");
let box3 = doc.getElementById("box3");
let box4 = doc.getElementById("box4");
@@ -52,17 +51,17 @@ add_task(function* () {
// height of 150px.
info("Display the tooltip on box1.");
yield showTooltip(tooltip, box1);
let expectedTooltipGeometry = {position: "bottom", height: 150, width};
checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
yield hideTooltip(tooltip);
info("Try to display the tooltip on top of box1.");
- yield showTooltip(tooltip, box1, "top");
+ yield showTooltip(tooltip, box1, {position: "top"});
expectedTooltipGeometry = {position: "bottom", height: 150, width};
checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
yield hideTooltip(tooltip);
// box2: Can not fit above or below box2, default to bottom with a reduced
// height of 100px.
info("Try to display the tooltip on box2.");
yield showTooltip(tooltip, box2);
--- a/devtools/client/shared/test/browser_html_tooltip_arrow-01.js
+++ b/devtools/client/shared/test/browser_html_tooltip_arrow-01.js
@@ -44,25 +44,37 @@ const TEST_URI = `data:text/xml;charset=
${getAnchor("top: 0; right: 50px;")}
${getAnchor("top: 0; right: 75px;")}
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
+let useXulWrapper;
+
add_task(function* () {
// Force the toolbox to be 200px high;
yield pushPref("devtools.toolbox.footer.height", 200);
yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
+ info("Run tests for a Tooltip without using a XUL panel");
+ useXulWrapper = false;
+ yield runTests(doc);
+
+ info("Run tests for a Tooltip with a XUL panel");
+ useXulWrapper = true;
+ yield runTests(doc);
+});
+
+function* runTests(doc) {
info("Create HTML tooltip");
- let tooltip = new HTMLTooltip({doc}, {type: "arrow"});
+ let tooltip = new HTMLTooltip({doc}, {type: "arrow", useXulWrapper});
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "35px";
tooltip.setContent(div, {width: 200, height: 35});
let {right: docRight} = doc.documentElement.getBoundingClientRect();
let elements = [...doc.querySelectorAll(".anchor")];
for (let el of elements) {
@@ -86,9 +98,11 @@ add_task(function* () {
let isInPanel = arrowBounds.left >= panelBounds.left &&
arrowBounds.right <= panelBounds.right;
ok(isInPanel,
"The tooltip arrow remains inside the tooltip panel horizontally");
yield hideTooltip(tooltip);
}
-});
+
+ tooltip.destroy();
+}
--- a/devtools/client/shared/test/browser_html_tooltip_arrow-02.js
+++ b/devtools/client/shared/test/browser_html_tooltip_arrow-02.js
@@ -38,25 +38,36 @@ const TEST_URI = `data:text/xml;charset=
${getAnchor("top: 130px; width: 200px; right: 0;")}
${getAnchor("top: 140px; width: 250px; right: 0;")}
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
+let useXulWrapper;
+
add_task(function* () {
// Force the toolbox to be 200px high;
yield pushPref("devtools.toolbox.footer.height", 200);
- yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
+ info("Run tests for a Tooltip without using a XUL panel");
+ useXulWrapper = false;
+ yield runTests(doc);
+
+ info("Run tests for a Tooltip with a XUL panel");
+ useXulWrapper = true;
+ yield runTests(doc);
+});
+
+function* runTests(doc) {
info("Create HTML tooltip");
- let tooltip = new HTMLTooltip({doc}, {type: "arrow"});
+ let tooltip = new HTMLTooltip({doc}, {type: "arrow", useXulWrapper});
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "35px";
tooltip.setContent(div, {width: 200, height: 35});
let {right: docRight} = doc.documentElement.getBoundingClientRect();
let elements = [...doc.querySelectorAll(".anchor")];
for (let el of elements) {
@@ -79,9 +90,9 @@ add_task(function* () {
"Tooltip arrow is aligned with the anchor, or stuck on viewport's edge.");
let isInPanel = arrowBounds.left >= panelBounds.left &&
arrowBounds.right <= panelBounds.right;
ok(isInPanel,
"The tooltip arrow remains inside the tooltip panel horizontally");
yield hideTooltip(tooltip);
}
-});
+}
--- a/devtools/client/shared/test/browser_html_tooltip_consecutive-show.js
+++ b/devtools/client/shared/test/browser_html_tooltip_consecutive-show.js
@@ -29,27 +29,26 @@ loadHelperScript("helper_html_tooltip.js
function getTooltipContent(doc) {
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "50px";
div.textContent = "tooltip";
return div;
}
add_task(function* () {
- yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
let box1 = doc.getElementById("box1");
let box2 = doc.getElementById("box2");
let box3 = doc.getElementById("box3");
let box4 = doc.getElementById("box4");
let width = 100, height = 50;
- let tooltip = new HTMLTooltip({doc}, {});
+ let tooltip = new HTMLTooltip({doc}, {useXulWrapper: false});
tooltip.setContent(getTooltipContent(doc), {width, height});
info("Show the tooltip on each of the 4 hbox, without calling hide in between");
info("Show tooltip on box1");
tooltip.show(box1);
checkTooltipGeometry(tooltip, box1, {position: "bottom", width, height});
--- a/devtools/client/shared/test/browser_html_tooltip_offset.js
+++ b/devtools/client/shared/test/browser_html_tooltip_offset.js
@@ -25,27 +25,26 @@ const TEST_URI = `data:text/xml;charset=
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
add_task(function* () {
// Force the toolbox to be 200px high;
yield pushPref("devtools.toolbox.footer.height", 200);
- yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
info("Test a tooltip is not closed when clicking inside itself");
let box1 = doc.getElementById("box1");
let box2 = doc.getElementById("box2");
let box3 = doc.getElementById("box3");
let box4 = doc.getElementById("box4");
- let tooltip = new HTMLTooltip({doc}, {});
+ let tooltip = new HTMLTooltip({doc}, {useXulWrapper: false});
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "100px";
div.style.boxSizing = "border-box";
div.textContent = "tooltip";
tooltip.setContent(div, {width: 50, height: 100});
info("Display the tooltip on box1.");
--- a/devtools/client/shared/test/browser_html_tooltip_variable-height.js
+++ b/devtools/client/shared/test/browser_html_tooltip_variable-height.js
@@ -24,24 +24,36 @@ const TEST_URI = `data:text/xml;charset=
const CONTAINER_HEIGHT = 200;
const CONTAINER_WIDTH = 200;
const TOOLTIP_HEIGHT = 50;
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
+let useXulWrapper;
+
add_task(function* () {
// Force the toolbox to be 400px tall => 50px for each box.
yield pushPref("devtools.toolbox.footer.height", 400);
yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
- let tooltip = new HTMLTooltip({doc}, {});
+ info("Run tests for a Tooltip without using a XUL panel");
+ useXulWrapper = false;
+ yield runTests(doc);
+
+ info("Run tests for a Tooltip with a XUL panel");
+ useXulWrapper = true;
+ yield runTests(doc);
+});
+
+function* runTests(doc) {
+ let tooltip = new HTMLTooltip({doc}, {useXulWrapper});
info("Set tooltip content 50px tall, but request a container 200px tall");
let tooltipContent = doc.createElementNS(HTML_NS, "div");
tooltipContent.style.cssText = "height: " + TOOLTIP_HEIGHT + "px; background: red;";
tooltip.setContent(tooltipContent, {width: CONTAINER_WIDTH, height: CONTAINER_HEIGHT});
info("Show the tooltip and check the container and panel height.");
yield showTooltip(tooltip, doc.getElementById("box1"));
@@ -66,9 +78,9 @@ add_task(function* () {
yield onPanelClick;
is(tooltip.isVisible(), true, "Tooltip is still visible");
info("Click below the tooltip container, the tooltip should be closed.");
onHidden = once(tooltip, "hidden");
EventUtils.synthesizeMouse(tooltip.container, 100, CONTAINER_HEIGHT + 10,
{}, doc.defaultView);
yield onHidden;
-});
+}
--- a/devtools/client/shared/test/browser_html_tooltip_width-auto.js
+++ b/devtools/client/shared/test/browser_html_tooltip_width-auto.js
@@ -20,28 +20,40 @@ const TEST_URI = `data:text/xml;charset=
<hbox id="box3" flex="1">test3</hbox>
<hbox id="box4" flex="1">test4</hbox>
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
+let useXulWrapper;
+
add_task(function* () {
yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
- let tooltip = new HTMLTooltip({doc}, {});
+ info("Run tests for a Tooltip without using a XUL panel");
+ useXulWrapper = false;
+ yield runTests(doc);
+
+ info("Run tests for a Tooltip with a XUL panel");
+ useXulWrapper = true;
+ yield runTests(doc);
+});
+
+function* runTests(doc) {
+ let tooltip = new HTMLTooltip({doc}, {useXulWrapper});
info("Create tooltip content width to 150px");
let tooltipContent = doc.createElementNS(HTML_NS, "div");
tooltipContent.style.cssText = "height: 100%; width: 150px; background: red;";
info("Set tooltip content using width:auto");
tooltip.setContent(tooltipContent, {width: "auto", height: 50});
info("Show the tooltip and check the tooltip panel width.");
yield showTooltip(tooltip, doc.getElementById("box1"));
let panelRect = tooltip.panel.getBoundingClientRect();
is(panelRect.width, 150, "Tooltip panel has the expected width.");
yield hideTooltip(tooltip);
-});
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_html_tooltip_xul-wrapper.js
@@ -0,0 +1,78 @@
+/* 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 can overflow out of the toolbox when using a XUL panel wrapper.
+ */
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
+ <?xml-stylesheet href="chrome://global/skin/global.css"?>
+ <?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
+ <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Tooltip test">
+ <vbox flex="1">
+ <hbox id="box1" style="height: 50px">test1</hbox>
+ <hbox id="box2" style="height: 50px">test2</hbox>
+ <hbox id="box3" style="height: 50px">test3</hbox>
+ <hbox id="box4" style="height: 50px">test4</hbox>
+ </vbox>
+ </window>`;
+
+const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
+loadHelperScript("helper_html_tooltip.js");
+
+// The test toolbox will be 200px tall, the anchors are 50px tall, therefore, the maximum
+// tooltip height that could fit in the toolbox is 150px. Setting 160px, the tooltip will
+// either have to overflow or to be resized.
+const TOOLTIP_HEIGHT = 160;
+const TOOLTIP_WIDTH = 200;
+
+add_task(function* () {
+ // Force the toolbox to be 200px high;
+ yield pushPref("devtools.toolbox.footer.height", 200);
+
+ let [, win, doc] = yield createHost("bottom", TEST_URI);
+
+ info("Resizing window to have some space below the window.");
+ let originalWidth = win.top.outerWidth;
+ let originalHeight = win.top.outerHeight;
+ win.top.resizeBy(0, -100);
+
+ info("Create HTML tooltip");
+ let tooltip = new HTMLTooltip({doc}, {useXulWrapper: true});
+ let div = doc.createElementNS(HTML_NS, "div");
+ div.style.height = "200px";
+ div.style.background = "red";
+ tooltip.setContent(div, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
+
+ let box1 = doc.getElementById("box1");
+
+ // Above box1: check that the tooltip can overflow onto the content page.
+ info("Display the tooltip above box1.");
+ yield showTooltip(tooltip, box1, {position: "top"});
+ checkTooltip(tooltip, "top", TOOLTIP_HEIGHT);
+ yield hideTooltip(tooltip);
+
+ // Below box1: check that the tooltip can overflow out of the browser window.
+ info("Display the tooltip below box1.");
+ yield showTooltip(tooltip, box1, {position: "bottom"});
+ checkTooltip(tooltip, "bottom", TOOLTIP_HEIGHT);
+ yield hideTooltip(tooltip);
+
+ is(tooltip.isVisible(), false, "Tooltip is not visible");
+
+ info("Restore original window dimensions.");
+ win.top.resizeTo(originalWidth, originalHeight);
+});
+
+function checkTooltip(tooltip, position, height) {
+ is(tooltip.position, position, "Actual tooltip position is " + position);
+ let rect = tooltip.container.getBoundingClientRect();
+ is(rect.height, height, "Actual tooltip height is " + height);
+ // Testing the actual left/top offsets is not relevant here as it is handled by the XUL
+ // panel.
+}
--- a/devtools/client/shared/widgets/HTMLTooltip.js
+++ b/devtools/client/shared/widgets/HTMLTooltip.js
@@ -3,16 +3,18 @@
/* 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/. */
"use strict";
const EventEmitter = require("devtools/shared/event-emitter");
const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipToggle");
+const {listenOnce} = require("devtools/shared/async-utils");
+const {Task} = require("devtools/shared/task");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const XHTML_NS = "http://www.w3.org/1999/xhtml";
const POSITION = {
TOP: "top",
BOTTOM: "bottom",
};
@@ -44,37 +46,41 @@ const EXTRA_BORDER = {
/**
* Calculate the vertical position & offsets to use for the tooltip. Will attempt to
* respect the provided height and position preferences, unless the available height
* prevents this.
*
* @param {DOMRect} anchorRect
* Bounding rectangle for the anchor, relative to the tooltip document.
- * @param {DOMRect} docRect
- * Bounding rectange for the tooltip document owner.
+ * @param {DOMRect} viewportRect
+ * Bounding rectangle for the viewport. top/left can be different from 0 if some
+ * space should not be used by tooltips (for instance OS toolbars, taskbars etc.).
* @param {Number} height
* Preferred height for the tooltip.
* @param {String} pos
* Preferred position for the tooltip. Possible values: "top" or "bottom".
* @return {Object}
* - {Number} top: the top offset for the tooltip.
* - {Number} height: the height to use for the tooltip container.
* - {String} computedPosition: Can differ from the preferred position depending
* on the available height). "top" or "bottom"
*/
-const calculateVerticalPosition = function (anchorRect, docRect, height, pos, offset) {
+const calculateVerticalPosition =
+function (anchorRect, viewportRect, height, pos, offset) {
let {TOP, BOTTOM} = POSITION;
let {top: anchorTop, height: anchorHeight} = anchorRect;
- let {bottom: docBottom} = docRect;
+
+ // Translate to the available viewport space before calculating dimensions and position.
+ anchorTop -= viewportRect.top;
// Calculate available space for the tooltip.
let availableTop = anchorTop;
- let availableBottom = docBottom - (anchorTop + anchorHeight);
+ let availableBottom = viewportRect.height - (anchorTop + anchorHeight);
// Find POSITION
let keepPosition = false;
if (pos === TOP) {
keepPosition = availableTop >= height + offset;
} else if (pos === BOTTOM) {
keepPosition = availableBottom >= height + offset;
}
@@ -85,47 +91,53 @@ const calculateVerticalPosition = functi
// Calculate HEIGHT.
let availableHeight = pos === TOP ? availableTop : availableBottom;
height = Math.min(height, availableHeight - offset);
height = Math.floor(height);
// Calculate TOP.
let top = pos === TOP ? anchorTop - height - offset : anchorTop + anchorHeight + offset;
+ // Translate back to absolute coordinates by re-including viewport top margin.
+ top += viewportRect.top;
+
return {top, height, computedPosition: pos};
};
/**
* Calculate the vertical position & offsets to use for the tooltip. Will attempt to
* respect the provided height and position preferences, unless the available height
* prevents this.
*
* @param {DOMRect} anchorRect
* Bounding rectangle for the anchor, relative to the tooltip document.
- * @param {DOMRect} docRect
- * Bounding rectange for the tooltip document owner.
+ * @param {DOMRect} viewportRect
+ * Bounding rectangle for the viewport. top/left can be different from 0 if some
+ * space should not be used by tooltips (for instance OS toolbars, taskbars etc.).
* @param {Number} width
* Preferred width for the tooltip.
* @return {Object}
* - {Number} left: the left offset for the tooltip.
* - {Number} width: the width to use for the tooltip container.
* - {Number} arrowLeft: the left offset to use for the arrow element.
*/
-const calculateHorizontalPosition = function (anchorRect, docRect, width, type, offset) {
+const calculateHorizontalPosition =
+function (anchorRect, viewportRect, width, type, offset) {
let {left: anchorLeft, width: anchorWidth} = anchorRect;
- let {right: docRight} = docRect;
+
+ // Translate to the available viewport space before calculating dimensions and position.
+ anchorLeft -= viewportRect.left;
// Calculate WIDTH.
- let availableWidth = docRight;
- width = Math.min(width, availableWidth);
+ width = Math.min(width, viewportRect.width);
// Calculate LEFT.
// By default the tooltip is aligned with the anchor left edge. Unless this
// makes it overflow the viewport, in which case is shifts to the left.
- let left = Math.min(anchorLeft + offset, docRight - width);
+ let left = Math.min(anchorLeft + offset, viewportRect.width - width);
// Calculate ARROW LEFT (tooltip's LEFT might be updated)
let arrowLeft;
// Arrow style tooltips may need to be shifted to the left
if (type === TYPE.ARROW) {
let arrowCenter = left + ARROW_OFFSET + ARROW_WIDTH / 2;
let anchorCenter = anchorLeft + anchorWidth / 2;
// If the anchor is too narrow, align the arrow and the anchor center.
@@ -136,16 +148,19 @@ const calculateHorizontalPosition = func
arrowLeft = Math.min(ARROW_OFFSET, (anchorWidth - ARROW_WIDTH) / 2) | 0;
// Translate the coordinate to tooltip container
arrowLeft += anchorLeft - left;
// Make sure the arrow remains in the tooltip container.
arrowLeft = Math.min(arrowLeft, width - ARROW_WIDTH);
arrowLeft = Math.max(arrowLeft, 0);
}
+ // Translate back to absolute coordinates by re-including viewport left margin.
+ left += viewportRect.left;
+
return {left, width, arrowLeft};
};
/**
* Get the bounding client rectangle for a given node, relative to a custom
* reference element (instead of the default for getBoundingClientRect which
* is always the element's ownerDocument).
*/
@@ -172,38 +187,61 @@ const getRelativeRect = function (node,
* @param {Object}
* - {String} type
* Display type of the tooltip. Possible values: "normal", "arrow"
* - {Boolean} autofocus
* Defaults to false. Should the tooltip be focused when opening it.
* - {Boolean} consumeOutsideClicks
* Defaults to true. The tooltip is closed when clicking outside.
* Should this event be stopped and consumed or not.
+ * - {Boolean} useXulWrapper
+ * Defaults to true. If the tooltip is hosted in a XUL document, use a XUL panel
+ * in order to use all the screen viewport available.
*/
-function HTMLTooltip(toolbox,
- {type = "normal", autofocus = false, consumeOutsideClicks = true} = {}) {
+function HTMLTooltip(toolbox, {
+ type = "normal",
+ autofocus = false,
+ consumeOutsideClicks = true,
+ useXulWrapper = true,
+ } = {}) {
EventEmitter.decorate(this);
this.doc = toolbox.doc;
this.type = type;
this.autofocus = autofocus;
this.consumeOutsideClicks = consumeOutsideClicks;
+ this.useXulWrapper = useXulWrapper;
+
+ this._position = null;
// Use the topmost window to listen for click events to close the tooltip
this.topWindow = this.doc.defaultView.top;
this._onClick = this._onClick.bind(this);
this._toggle = new TooltipToggle(this);
this.startTogglingOnHover = this._toggle.start.bind(this._toggle);
this.stopTogglingOnHover = this._toggle.stop.bind(this._toggle);
this.container = this._createContainer();
- if (this._isXUL()) {
+ if (this._isXUL() && this.useXulWrapper) {
+ // When using a XUL panel as the wrapper, the actual markup for the tooltip is as
+ // follows :
+ // <panel> <!-- XUL panel used to position the tooltip anywhere on screen -->
+ // <div> <!-- div wrapper used to isolate the tooltip container -->
+ // <div> <! the actual tooltip.container element -->
+ this.xulPanelWrapper = this._createXulPanelWrapper();
+ let inner = this.doc.createElementNS(XHTML_NS, "div");
+ inner.classList.add("tooltip-xul-wrapper-inner");
+
+ this.doc.documentElement.appendChild(this.xulPanelWrapper);
+ this.xulPanelWrapper.appendChild(inner);
+ inner.appendChild(this.container);
+ } else if (this._isXUL()) {
this.doc.documentElement.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;
@@ -220,16 +258,23 @@ HTMLTooltip.prototype = {
/**
* The arrow element. Might be null depending on the tooltip type.
*/
get arrow() {
return this.container.querySelector(".tooltip-arrow");
},
/**
+ * Retrieve the displayed position used for the tooltip. Null if the tooltip is hidden.
+ */
+ get position() {
+ return this.isVisible() ? this._position : null;
+ },
+
+ /**
* Set the tooltip content element. The preferred width/height should also be
* specified here.
*
* @param {Element} content
* The tooltip content, should be a HTML element.
* @param {Object}
* - {Number} width: preferred width for the tooltip container. If not specified
* the tooltip container will be measured before being displayed, and the
@@ -255,107 +300,167 @@ HTMLTooltip.prototype = {
* @param {Object}
* - {String} position: optional, possible values: top|bottom
* If layout permits, the tooltip will be displayed on top/bottom
* of the anchor. If ommitted, the tooltip will be displayed where
* more space is available.
* - {Number} x: optional, horizontal offset between the anchor and the tooltip
* - {Number} y: optional, vertical offset between the anchor and the tooltip
*/
- show: function (anchor, {position, x = 0, y = 0} = {}) {
+ show: Task.async(function* (anchor, {position, x = 0, y = 0} = {}) {
// Get anchor geometry
let anchorRect = getRelativeRect(anchor, this.doc);
- // Get document geometry
- let docRect = this.doc.documentElement.getBoundingClientRect();
+ if (this.useXulWrapper) {
+ anchorRect = this._convertToScreenRect(anchorRect);
+ }
+
+ // Get viewport size
+ let viewportRect = this._getViewportRect();
let themeHeight = EXTRA_HEIGHT[this.type] + 2 * EXTRA_BORDER[this.type];
let preferredHeight = this.preferredHeight + themeHeight;
let {top, height, computedPosition} =
- calculateVerticalPosition(anchorRect, docRect, preferredHeight, position, y);
+ calculateVerticalPosition(anchorRect, viewportRect, preferredHeight, position, y);
- // Apply height and top information before measuring the content width (if "auto").
+ this._position = computedPosition;
+ // Apply height before measuring the content width (if width="auto").
let isTop = computedPosition === POSITION.TOP;
this.container.classList.toggle("tooltip-top", isTop);
this.container.classList.toggle("tooltip-bottom", !isTop);
this.container.style.height = height + "px";
- this.container.style.top = top + "px";
- let themeWidth = 2 * EXTRA_BORDER[this.type];
- let preferredWidth = this.preferredWidth === "auto" ?
- this._measureContainerWidth() : this.preferredWidth + themeWidth;
+ let preferredWidth;
+ if (this.preferredWidth === "auto") {
+ preferredWidth = this._measureContainerWidth();
+ } else {
+ let themeWidth = 2 * EXTRA_BORDER[this.type];
+ preferredWidth = this.preferredWidth + themeWidth;
+ }
let {left, width, arrowLeft} =
- calculateHorizontalPosition(anchorRect, docRect, preferredWidth, this.type, x);
+ calculateHorizontalPosition(anchorRect, viewportRect, preferredWidth, this.type, x);
this.container.style.width = width + "px";
- this.container.style.left = left + "px";
if (this.type === TYPE.ARROW) {
this.arrow.style.left = arrowLeft + "px";
}
+ if (this.useXulWrapper) {
+ this._showXulWrapperAt(left, top);
+ } else {
+ this.container.style.left = left + "px";
+ this.container.style.top = top + "px";
+ }
+
this.container.classList.add("tooltip-visible");
// Keep a pointer on the focused element to refocus it when hiding the tooltip.
this._focusedElement = this.doc.activeElement;
this.doc.defaultView.clearTimeout(this.attachEventsTimer);
this.attachEventsTimer = this.doc.defaultView.setTimeout(() => {
this._maybeFocusTooltip();
this.topWindow.addEventListener("click", this._onClick, true);
this.emit("shown");
}, 0);
+ }),
+
+ /**
+ * Calculate the rect of the viewport that limits the tooltip dimensions. When using a
+ * XUL panel wrapper, the viewport will be able to use the whole screen (excluding space
+ * reserved by the OS for toolbars etc.). Otherwise, the viewport is limited to the
+ * tooltip's document.
+ *
+ * @return {Object} DOMRect-like object with the Number properties: top, right, bottom,
+ * left, width, height
+ */
+ _getViewportRect: function () {
+ if (this.useXulWrapper) {
+ // availLeft/Top are the coordinates first pixel available on the screen for
+ // applications (excluding space dedicated for OS toolbars, menus etc...)
+ // availWidth/Height are the dimensions available to applications excluding all
+ // the OS reserved space
+ let {availLeft, availTop, availHeight, availWidth} = this.doc.defaultView.screen;
+ return {
+ top: availTop,
+ right: availLeft + availWidth,
+ bottom: availTop + availHeight,
+ left: availLeft,
+ width: availWidth,
+ height: availHeight,
+ };
+ }
+
+ return this.doc.documentElement.getBoundingClientRect();
},
_measureContainerWidth: function () {
+ let xulParent = this.container.parentNode;
+ if (this.useXulWrapper && !this.isVisible()) {
+ // Move the container out of the XUL Panel to measure it.
+ this.doc.documentElement.appendChild(this.container);
+ }
+
this.container.classList.add("tooltip-hidden");
- this.container.style.left = "0px";
this.container.style.width = "auto";
let width = this.container.getBoundingClientRect().width;
this.container.classList.remove("tooltip-hidden");
+
+ if (this.useXulWrapper && !this.isVisible()) {
+ xulParent.appendChild(this.container);
+ }
+
return width;
},
/**
* Hide the current tooltip. The event "hidden" will be fired when the tooltip
* is hidden.
*/
- hide: function () {
+ hide: Task.async(function* () {
this.doc.defaultView.clearTimeout(this.attachEventsTimer);
if (!this.isVisible()) {
return;
}
this.topWindow.removeEventListener("click", this._onClick, true);
this.container.classList.remove("tooltip-visible");
+ if (this.useXulWrapper) {
+ yield this._hideXulWrapper();
+ }
+
this.emit("hidden");
let tooltipHasFocus = this.container.contains(this.doc.activeElement);
if (tooltipHasFocus && this._focusedElement) {
this._focusedElement.focus();
this._focusedElement = null;
}
- },
+ }),
/**
* Check if the tooltip is currently displayed.
* @return {Boolean} true if the tooltip is visible
*/
isVisible: function () {
return this.container.classList.contains("tooltip-visible");
},
/**
* Destroy the tooltip instance. Hide the tooltip if displayed, remove the
* tooltip container from the document.
*/
destroy: function () {
this.hide();
this.container.remove();
+ if (this.xulPanelWrapper) {
+ this.xulPanelWrapper.remove();
+ }
},
_createContainer: function () {
let container = this.doc.createElementNS(XHTML_NS, "div");
container.setAttribute("type", this.type);
container.classList.add("tooltip-container");
let html = '<div class="tooltip-filler"></div>';
@@ -403,28 +508,69 @@ HTMLTooltip.prototype = {
}
win = win.parent;
}
return false;
},
/**
- * Check if the tooltip's owner document is a XUL document.
- */
- _isXUL: function () {
- return this.doc.documentElement.namespaceURI === XUL_NS;
- },
-
- /**
* If the tootlip is configured to autofocus and a focusable element can be found,
* focus it.
*/
_maybeFocusTooltip: function () {
// Simplied selector targetting elements that can receive the focus, full version at
// http://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus .
let focusableSelector = "a, button, iframe, input, select, textarea";
let focusableElement = this.panel.querySelector(focusableSelector);
if (this.autofocus && focusableElement) {
focusableElement.focus();
}
},
+
+ /**
+ * Check if the tooltip's owner document is a XUL document.
+ */
+ _isXUL: function () {
+ return this.doc.documentElement.namespaceURI === XUL_NS;
+ },
+
+ _createXulPanelWrapper: function () {
+ let panel = this.doc.createElementNS(XUL_NS, "panel");
+
+ // XUL panel is only a way to display DOM elements outside of the document viewport,
+ // so disable all features that impact the behavior.
+ panel.setAttribute("animate", false);
+ panel.setAttribute("consumeoutsideclicks", false);
+ panel.setAttribute("noautofocus", true);
+ panel.setAttribute("ignorekeys", true);
+
+ panel.setAttribute("level", "float");
+ panel.setAttribute("class", "tooltip-xul-wrapper");
+
+ return panel;
+ },
+
+ _showXulWrapperAt: function (left, top) {
+ let onPanelShown = listenOnce(this.xulPanelWrapper, "popupshown");
+ this.xulPanelWrapper.openPopupAtScreen(left, top, false);
+ return onPanelShown;
+ },
+
+ _hideXulWrapper: function () {
+ let onPanelHidden = listenOnce(this.xulPanelWrapper, "popuphidden");
+ this.xulPanelWrapper.hidePopup();
+ return onPanelHidden;
+ },
+
+ /**
+ * Convert from coordinates relative to the tooltip's document, to coordinates relative
+ * to the "available" screen. By "available" we mean the screen, excluding the OS bars
+ * display on screen edges.
+ */
+ _convertToScreenRect: function ({left, top, width, height}) {
+ // mozInnerScreenX/Y are the coordinates of the top left corner of the window's
+ // viewport, excluding chrome UI.
+ left += this.doc.defaultView.mozInnerScreenX;
+ top += this.doc.defaultView.mozInnerScreenY;
+ return {top, right: left + width, bottom: top + height, left, width, height};
+ },
};
--- a/devtools/client/themes/tooltips.css
+++ b/devtools/client/themes/tooltips.css
@@ -104,16 +104,27 @@
position: fixed;
z-index: 9999;
display: none;
background: transparent;
pointer-events: none;
overflow: hidden;
}
+.tooltip-xul-wrapper {
+ -moz-appearance: none;
+ background: transparent;
+ overflow: visible;
+ border-style: none;
+}
+
+.tooltip-xul-wrapper .tooltip-container {
+ position: absolute;
+}
+
.tooltip-top {
flex-direction: column;
}
.tooltip-bottom {
flex-direction: column-reverse;
}
@@ -132,16 +143,21 @@
}
/* Tooltip : arrow style */
.tooltip-container[type="arrow"] {
filter: drop-shadow(0 3px 4px var(--theme-tooltip-shadow));
}
+.tooltip-xul-wrapper .tooltip-container[type="arrow"] {
+ /* When displayed in a XUL panel the drop shadow would be abruptly cut by the panel */
+ filter: none;
+}
+
.tooltip-container[type="arrow"] > .tooltip-panel {
position: relative;
flex-grow: 0;
min-height: 10px;
box-sizing: border-box;
width: 100%;
border: 3px solid var(--theme-tooltip-border);
@@ -260,17 +276,17 @@
}
.event-tooltip-debugger-icon:hover {
opacity: 1;
}
.event-tooltip-content-box {
display: none;
- height: 54px;
+ height: 100px;
overflow: hidden;
margin-inline-end: 0;
border: 1px solid var(--theme-splitter-color);
border-width: 1px 0 0 0;
}
.event-toolbox-content-box iframe {
height: 100%;