Bug 1267401 - part3: Create arrow style for HTML tooltips;r=bgrins
MozReview-Commit-ID: Bp2RylafolP
--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -112,16 +112,18 @@ skip-if = e10s # Bug 1221911, bug 122228
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-16.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_html_tooltip-01.js]
[browser_html_tooltip-02.js]
[browser_html_tooltip-03.js]
[browser_html_tooltip-04.js]
[browser_html_tooltip-05.js]
+[browser_html_tooltip_arrow-01.js]
+[browser_html_tooltip_arrow-02.js]
[browser_inplace-editor-01.js]
[browser_inplace-editor-02.js]
[browser_inplace-editor_maxwidth.js]
[browser_key_shortcuts.js]
[browser_layoutHelpers.js]
skip-if = e10s # Layouthelpers test should not run in a content page.
[browser_layoutHelpers-getBoxQuads.js]
skip-if = e10s # Layouthelpers test should not run in a content page.
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_html_tooltip_arrow-01.js
@@ -0,0 +1,95 @@
+/* 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 "arrow" type on small anchors. The arrow should remain
+ * aligned with the anchors as much as possible
+ */
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const getAnchor = function (position) {
+ return `<html:div class="anchor" style="width:10px;
+ height: 10px;
+ position: absolute;
+ background: red;
+ ${position}"></html:div>`;
+};
+
+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/common.css"?>
+ <?xml-stylesheet href="chrome://devtools/skin/light-theme.css"?>
+
+ <window class="theme-light"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Tooltip test">
+ <vbox flex="1" style="position: relative">
+ ${getAnchor("top: 0; left: 0;")}
+ ${getAnchor("top: 0; left: 25px;")}
+ ${getAnchor("top: 0; left: 50px;")}
+ ${getAnchor("top: 0; left: 75px;")}
+ ${getAnchor("bottom: 0; left: 0;")}
+ ${getAnchor("bottom: 0; left: 25px;")}
+ ${getAnchor("bottom: 0; left: 50px;")}
+ ${getAnchor("bottom: 0; left: 75px;")}
+ ${getAnchor("bottom: 0; right: 0;")}
+ ${getAnchor("bottom: 0; right: 25px;")}
+ ${getAnchor("bottom: 0; right: 50px;")}
+ ${getAnchor("bottom: 0; right: 75px;")}
+ ${getAnchor("top: 0; right: 0;")}
+ ${getAnchor("top: 0; right: 25px;")}
+ ${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");
+
+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}, {type: "arrow"});
+ let div = doc.createElementNS(HTML_NS, "div");
+ div.style.height = "100%";
+ yield tooltip.setContent(div, 200, 35);
+
+ let {right: docRight} = doc.documentElement.getBoundingClientRect();
+
+ let elements = [...doc.querySelectorAll(".anchor")];
+ for (let el of elements) {
+ info("Display the tooltip on an anchor.");
+ yield showTooltip(tooltip, el);
+
+ let arrow = tooltip.arrow;
+ ok(arrow, "Tooltip has an arrow");
+
+ // Get the geometry of the anchor, the tooltip frame & arrow.
+ let arrowBounds = arrow.getBoxQuads({relativeTo: doc})[0].bounds;
+ let frameBounds = tooltip.frame.getBoxQuads({relativeTo: doc})[0].bounds;
+ let anchorBounds = el.getBoxQuads({relativeTo: doc})[0].bounds;
+
+ let intersects = arrowBounds.left <= anchorBounds.right &&
+ arrowBounds.right >= anchorBounds.left;
+ let isBlockedByViewport = arrowBounds.left == 0 ||
+ arrowBounds.right == docRight;
+ 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");
+
+ yield hideTooltip(tooltip);
+ }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_html_tooltip_arrow-02.js
@@ -0,0 +1,88 @@
+/* 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 "arrow" type on wide anchors. The arrow should remain
+ * aligned with the anchors as much as possible
+ */
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const getAnchor = function (position) {
+ return `<html:div class="anchor" style="height: 5px;
+ position: absolute;
+ background: red;
+ ${position}"></html:div>`;
+};
+
+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/common.css"?>
+ <?xml-stylesheet href="chrome://devtools/skin/light-theme.css"?>
+
+ <window class="theme-light"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Tooltip test">
+ <vbox flex="1" style="position: relative">
+ ${getAnchor("top: 0; left: 0; width: 50px;")}
+ ${getAnchor("top: 10px; left: 0; width: 100px;")}
+ ${getAnchor("top: 20px; left: 0; width: 150px;")}
+ ${getAnchor("top: 30px; left: 0; width: 200px;")}
+ ${getAnchor("top: 40px; left: 0; width: 250px;")}
+ ${getAnchor("top: 50px; left: 100px; width: 250px;")}
+ ${getAnchor("top: 100px; width: 50px; right: 0;")}
+ ${getAnchor("top: 110px; width: 100px; right: 0;")}
+ ${getAnchor("top: 120px; width: 150px; right: 0;")}
+ ${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");
+
+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}, {type: "arrow"});
+ let div = doc.createElementNS(HTML_NS, "div");
+ div.style.height = "100%";
+ yield tooltip.setContent(div, 200, 35);
+
+ let {right: docRight} = doc.documentElement.getBoundingClientRect();
+
+ let elements = [...doc.querySelectorAll(".anchor")];
+ for (let el of elements) {
+ info("Display the tooltip on an anchor.");
+ yield showTooltip(tooltip, el);
+
+ let arrow = tooltip.arrow;
+ ok(arrow, "Tooltip has an arrow");
+
+ // Get the geometry of the anchor, the tooltip frame & arrow.
+ let arrowBounds = arrow.getBoxQuads({relativeTo: doc})[0].bounds;
+ let frameBounds = tooltip.frame.getBoxQuads({relativeTo: doc})[0].bounds;
+ let anchorBounds = el.getBoxQuads({relativeTo: doc})[0].bounds;
+
+ let intersects = arrowBounds.left <= anchorBounds.right &&
+ arrowBounds.right >= anchorBounds.left;
+ let isBlockedByViewport = arrowBounds.left == 0 ||
+ arrowBounds.right == docRight;
+ 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");
+ yield hideTooltip(tooltip);
+ }
+});
--- a/devtools/client/shared/widgets/HTMLTooltip.js
+++ b/devtools/client/shared/widgets/HTMLTooltip.js
@@ -8,24 +8,54 @@
const EventEmitter = require("devtools/shared/event-emitter");
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",
+};
+
+module.exports.POSITION = POSITION;
+
+const TYPE = {
+ NORMAL: "normal",
+ ARROW: "arrow",
+};
+
+module.exports.TYPE = TYPE;
+
+const ARROW_WIDTH = 32;
+
+// Default offset between the tooltip's left edge and the tooltip arrow.
+const ARROW_OFFSET = 20;
+
+const EXTRA_HEIGHT = {
+ "normal": 0,
+ // The arrow is 16px tall, but merges on 3px with the panel border
+ "arrow": 13,
+};
+
+const EXTRA_BORDER = {
+ "normal": 0,
+ "arrow": 3,
+};
+
/**
* The HTMLTooltip can display HTML content in a tooltip popup.
*
* @param {Toolbox} toolbox
* The devtools toolbox, needed to get the devtools main window.
* @param {Object}
* - {String} type
- * Display type of the tooltip. Possible values: "normal"
+ * Display type of the tooltip. Possible values: "normal", "arrow"
* - {Boolean} autofocus
* Defaults to true. 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.
*/
function HTMLTooltip(toolbox,
{type = "normal", autofocus = true, consumeOutsideClicks = true} = {}) {
@@ -43,53 +73,84 @@ 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.container.removeEventListener("load", onLoad, true);
+ this.frame.removeEventListener("load", onLoad, true);
resolve();
};
- this.container.addEventListener("load", onLoad, true);
- this.container.setAttribute("src", IFRAME_URL);
+ 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();
}
});
}
module.exports.HTMLTooltip = HTMLTooltip;
HTMLTooltip.prototype = {
- position: {
- TOP: "top",
- BOTTOM: "bottom",
+ /**
+ * 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);
+ },
+
+ /**
+ * The arrow element. Might be null depending on the tooltip type.
+ */
+ get arrow() {
+ return this.container.querySelector(".tooltip-arrow");
},
/**
* 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 {Number} width
* Preferred width for the tooltip container
* @param {Number} height
* Preferred height for the tooltip container
* @return {Promise} a promise that will resolve when the content has been
* added in the tooltip container.
*/
setContent: function (content, width, height) {
- this.preferredWidth = width;
- this.preferredHeight = height;
+ let themeHeight = EXTRA_HEIGHT[this.type] + 2 * EXTRA_BORDER[this.type];
+ let themeWidth = 2 * EXTRA_BORDER[this.type];
+
+ this.preferredWidth = width + themeWidth;
+ this.preferredHeight = height + themeHeight;
return this.containerReady.then(() => {
this.panel.innerHTML = "";
this.panel.appendChild(content);
});
},
/**
@@ -101,96 +162,90 @@ 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.
*/
show: function (anchor, {position} = {}) {
this.containerReady.then(() => {
- let {top, left, width, height} = 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);
- if (this._isXUL()) {
- this.container.setAttribute("width", width);
- this.container.setAttribute("height", height);
- } else {
- this.container.style.width = width + "px";
- this.container.style.height = height + "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";
}
- this.container.style.top = top + "px";
- this.container.style.left = left + "px";
- this.container.style.display = "block";
-
- if (this.autofocus) {
- this.container.focus();
- }
+ this.container.classList.add("tooltip-visible");
this.attachEventsTimer = this.doc.defaultView.setTimeout(() => {
+ if (this.autofocus) {
+ this.frame.focus();
+ }
this.topWindow.addEventListener("click", this._onClick, true);
this.emit("shown");
}, 0);
});
},
/**
* Hide the current tooltip. The event "hidden" will be fired when the tooltip
* is hidden.
*/
hide: function () {
this.doc.defaultView.clearTimeout(this.attachEventsTimer);
if (this.isVisible()) {
this.topWindow.removeEventListener("click", this._onClick, true);
- this.container.style.display = "none";
+ this.container.classList.remove("tooltip-visible");
this.emit("hidden");
}
},
- get panel() {
- if (this._isXUL()) {
- // In XUL context, we are wrapping the HTML content in an iframe.
- let win = this.container.contentWindow.wrappedJSObject;
- return win.document.getElementById(IFRAME_CONTAINER_ID);
- }
- return this.container;
- },
-
/**
* Check if the tooltip is currently displayed.
* @return {Boolean} true if the tooltip is visible
*/
isVisible: function () {
- let win = this.doc.defaultView;
- return win.getComputedStyle(this.container).display != "none";
+ 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();
},
_createContainer: function () {
- let container;
+ let container = this.doc.createElementNS(XHTML_NS, "div");
+ container.setAttribute("type", this.type);
+ container.classList.add("tooltip-container");
+
+ let html;
if (this._isXUL()) {
- container = this.doc.createElementNS(XHTML_NS, "iframe");
- container.classList.add("devtools-tooltip-iframe");
- this.doc.querySelector("window").appendChild(container);
+ html = '<iframe class="devtools-tooltip-iframe tooltip-panel"></iframe>';
} else {
- container = this.doc.createElementNS(XHTML_NS, "div");
- this.doc.body.appendChild(container);
+ html = '<div class="tooltip-panel theme-body"></div>';
}
- container.classList.add("theme-body");
- container.classList.add("devtools-htmltooltip-container");
-
+ if (this.type === TYPE.ARROW) {
+ html += '<div class="tooltip-arrow"></div>';
+ }
+ container.innerHTML = html;
return container;
},
_onClick: function (e) {
if (this._isInTooltipContainer(e.target)) {
return;
}
@@ -216,76 +271,104 @@ HTMLTooltip.prototype = {
win = win.parent;
if (win === contentWindow) {
return true;
}
}
return false;
},
+ /**
+ * Calculates the best possible position to display the tooltip near the
+ * provided anchor. An optional position can be provided, but will be
+ * respected only if it doesn't force the tooltip to be resized.
+ *
+ * If the tooltip has to be resized, the position will be wherever the most
+ * space is available.
+ *
+ */
_findBestPosition: function (anchor, position) {
- let top, left;
- let {TOP, BOTTOM} = this.position;
+ let {TOP, BOTTOM} = POSITION;
- let {left: anchorLeft, top: anchorTop, height: anchorHeight}
- = this._getRelativeRect(anchor, this.doc);
+ // Get anchor geometry
+ let {
+ left: anchorLeft, top: anchorTop,
+ height: anchorHeight, width: anchorWidth
+ } = this._getRelativeRect(anchor, this.doc);
+ // Get document geometry
let {bottom: docBottom, right: docRight} =
this.doc.documentElement.getBoundingClientRect();
- let height = this.preferredHeight;
- // Check if the popup can fit above the anchor.
+ // Calculate available space for the tooltip.
let availableTop = anchorTop;
- let fitsAbove = availableTop >= height;
- // Check if the popup can fit below the anchor.
- let availableBelow = docBottom - (anchorTop + anchorHeight);
- let fitsBelow = availableBelow >= height;
+ let availableBottom = docBottom - (anchorTop + anchorHeight);
- let isPositionSuitable = (fitsAbove && position === TOP)
- || (fitsBelow && position === BOTTOM);
- if (!isPositionSuitable) {
- // If the preferred position does not fit the preferred height,
- // pick the position offering the most height.
- position = availableTop > availableBelow ? TOP : BOTTOM;
+ // Find POSITION
+ let keepPosition = false;
+ if (position === TOP) {
+ keepPosition = availableTop >= this.preferredHeight;
+ } else if (position === BOTTOM) {
+ keepPosition = availableBottom >= this.preferredHeight;
+ }
+ if (!keepPosition) {
+ position = availableTop > availableBottom ? TOP : BOTTOM;
}
- // Calculate height, capped by the maximum height available.
- height = Math.min(height, Math.max(availableTop, availableBelow));
- top = position === TOP ? anchorTop - height : anchorTop + anchorHeight;
+ // Calculate HEIGHT.
+ let availableHeight = position === TOP ? availableTop : availableBottom;
+ let height = Math.min(this.preferredHeight, availableHeight);
+ height = Math.floor(height);
+ // Calculate TOP.
+ let top = position === TOP ? anchorTop - height : anchorTop + anchorHeight;
+
+ // Calculate WIDTH.
let availableWidth = docRight;
let width = Math.min(this.preferredWidth, availableWidth);
- // By default, align the tooltip's left edge with the anchor left edge.
- if (anchorLeft + width <= docRight) {
- left = anchorLeft;
- } else {
- // If the tooltip cannot fit, shift to the left just enough to fit.
- left = docRight - 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, docRight - width);
+
+ // Calculate ARROW LEFT (tooltip's LEFT might be updated)
+ let arrowLeft;
+ // Arrow style tooltips may need to be shifted to the left
+ if (this.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.
+ if (arrowCenter > anchorCenter) {
+ left = Math.max(0, left - (arrowCenter - anchorCenter));
+ }
+ // Arrow's feft offset relative to the anchor.
+ 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);
}
- return {top, left, width, height};
+ return {top, left, width, height, position, 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).
*/
_getRelativeRect: function (node, relativeTo) {
// Width and Height can be taken from the rect.
let {width, height} = node.getBoundingClientRect();
- // Find the smallest top/left coordinates from all quads.
- let top = Infinity, left = Infinity;
- let quads = node.getBoxQuads({relativeTo: relativeTo});
- for (let quad of quads) {
- top = Math.min(top, quad.bounds.top);
- left = Math.min(left, quad.bounds.left);
- }
+ let quads = node.getBoxQuads({relativeTo});
+ let top = quads[0].bounds.top;
+ let left = quads[0].bounds.left;
// Compute right and bottom coordinates using the rest of the data.
let right = left + width;
let bottom = top + height;
return {top, right, bottom, left, width, height};
},
--- a/devtools/client/shared/widgets/tooltip-frame.xhtml
+++ b/devtools/client/shared/widgets/tooltip-frame.xhtml
@@ -10,15 +10,16 @@
<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);
}
</style>
</head>
<body role="application" class="theme-body">
<div id="tooltip-iframe-container"></div>
</body>
</html>
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -241,20 +241,98 @@
background-position: 0 0, 10px 10px;
}
.devtools-tooltip-iframe {
border: none;
background: transparent;
}
-.devtools-htmltooltip-container {
+.tooltip-container {
display: none;
position: fixed;
z-index: 9999;
+ display: none;
+ background: transparent;
+}
+
+.tooltip-panel{
+ background-color: var(--theme-tooltip-background);
+}
+
+.tooltip-visible {
+ display: block;
+}
+
+.tooltip-container[type="normal"] > .tooltip-panel {
+ height: 100%;
+ width: 100%;
+}
+
+/* Tooltip : arrow style */
+
+.tooltip-container[type="arrow"] {
+ filter: drop-shadow(0 3px 4px var(--theme-tooltip-shadow));
+}
+
+.tooltip-container[type="arrow"] > .tooltip-panel {
+ position: absolute;
+ box-sizing: border-box;
+ height: calc(100% - 13px);
+ width: 100%;
+
+ border: 3px solid var(--theme-tooltip-border);
+ border-radius: 5px;
+}
+
+.tooltip-top[type="arrow"] .tooltip-panel {
+ top: 0;
+}
+
+.tooltip-bottom[type="arrow"] .tooltip-panel {
+ bottom: 0;
+}
+
+.tooltip-arrow {
+ position: absolute;
+ height: 16px;
+ width: 32px;
+ overflow: hidden;
+}
+
+.tooltip-top .tooltip-arrow {
+ bottom: 0;
+}
+
+.tooltip-bottom .tooltip-arrow {
+ top: 0;
+}
+
+.tooltip-arrow:before {
+ content: "";
+ position: absolute;
+ width: 21px;
+ height: 21px;
+ margin-left: 4px;
+ background: linear-gradient(-45deg,
+ var(--theme-tooltip-background) 50%, transparent 50%);
+ border-color: var(--theme-tooltip-border);
+ border-style: solid;
+ border-width: 0px 3px 3px 0px;
+ border-radius: 3px;
+}
+
+.tooltip-bottom .tooltip-arrow:before {
+ margin-top: 4px;
+ transform: rotate(225deg);
+}
+
+.tooltip-top .tooltip-arrow:before {
+ margin-top: -12px;
+ transform: rotate(45deg);
}
/* links to source code, like displaying `myfile.js:45` */
.devtools-source-link {
font-family: var(--monospace-font-family);
color: var(--theme-highlight-blue);
cursor: pointer;
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -58,16 +58,21 @@
--theme-graphs-red: #e57180;
--theme-graphs-grey: #cccccc;
--theme-graphs-full-red: #f00;
--theme-graphs-full-blue: #00f;
/* Images */
--theme-pane-collapse-image: url(chrome://devtools/skin/images/pane-collapse.svg);
--theme-pane-expand-image: url(chrome://devtools/skin/images/pane-expand.svg);
+
+ /* Tooltips */
+ --theme-tooltip-border: #d9e1e8;
+ --theme-tooltip-background: rgba(255, 255, 255, .9);
+ --theme-tooltip-shadow: rgba(155, 155, 155, 0.26);
}
:root.theme-dark {
--theme-body-background: #393f4c;
--theme-sidebar-background: #393f4c;
--theme-contrast-background: #ffb35b;
--theme-tab-toolbar-background: #272b35;
@@ -109,16 +114,21 @@
--theme-graphs-red: #eb5368;
--theme-graphs-grey: #757873;
--theme-graphs-full-red: #f00;
--theme-graphs-full-blue: #00f;
/* Images */
--theme-pane-collapse-image: url(chrome://devtools/skin/images/pane-collapse.svg);
--theme-pane-expand-image: url(chrome://devtools/skin/images/pane-expand.svg);
+
+ /* Tooltips */
+ --theme-tooltip-border: #434850;
+ --theme-tooltip-background: rgba(19, 28, 38, .9);
+ --theme-tooltip-shadow: rgba(25, 25, 25, 0.76);
}
:root.theme-firebug {
--theme-body-background: #fcfcfc;
--theme-sidebar-background: #fcfcfc;
--theme-contrast-background: #e6b064;
--theme-tab-toolbar-background: #ebeced;