blob: 0739889fa11d4f1242db70e642d52898bb2b1f1b [file] [log] [blame]
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @constructor
* @param {!Document} doc
*/
WebInspector.Tooltip = function(doc)
{
this.element = doc.body.createChild("div");
this._shadowRoot = WebInspector.createShadowRootWithCoreStyles(this.element, "ui/tooltip.css");
this._tooltipElement = this._shadowRoot.createChild("div", "tooltip");
doc.addEventListener("mousemove", this._mouseMove.bind(this), true);
doc.addEventListener("mousedown", this._hide.bind(this, true), true);
doc.addEventListener("mouseleave", this._hide.bind(this, false), true);
doc.addEventListener("keydown", this._hide.bind(this, true), true);
WebInspector.zoomManager.addEventListener(WebInspector.ZoomManager.Events.ZoomChanged, this._reset, this);
doc.defaultView.addEventListener("resize", this._reset.bind(this), false);
}
WebInspector.Tooltip.Timing = {
// Max time between tooltips showing that no opening delay is required.
"InstantThreshold": 300,
// Wait time before opening a tooltip.
"OpeningDelay": 600
}
WebInspector.Tooltip.prototype = {
/**
* @param {!Event} event
*/
_mouseMove: function(event)
{
var path = event.deepPath ? event.deepPath : event.path;
if (!path || event.buttons !== 0)
return;
if (this._anchorElement && path.indexOf(this._anchorElement) === -1)
this._hide(false);
for (var element of path) {
if (element === this._anchorElement) {
return;
} else if (element[WebInspector.Tooltip._symbol]) {
this._show(element, event);
return;
}
}
},
/**
* @param {!Element} anchorElement
* @param {!Event} event
*/
_show: function(anchorElement, event)
{
var tooltip = anchorElement[WebInspector.Tooltip._symbol];
this._anchorElement = anchorElement;
this._tooltipElement.removeChildren();
// Check if native tooltips should be used.
for (var element of WebInspector.Tooltip._nativeOverrideContainer) {
if (this._anchorElement.isSelfOrDescendant(element)) {
Object.defineProperty(this._anchorElement, "title", WebInspector.Tooltip._nativeTitle);
this._anchorElement.title = tooltip.content;
return;
}
}
if (typeof tooltip.content === "string")
this._tooltipElement.textContent = tooltip.content;
else
this._tooltipElement.appendChild(tooltip.content);
if (tooltip.actionId) {
var shortcuts = WebInspector.shortcutRegistry.shortcutDescriptorsForAction(tooltip.actionId);
for (var shortcut of shortcuts) {
var shortcutElement = this._tooltipElement.createChild("div", "tooltip-shortcut");
shortcutElement.textContent = shortcut.name;
}
}
this._tooltipElement.classList.add("shown");
// Reposition to ensure text doesn't overflow unnecessarily.
this._tooltipElement.positionAt(0, 0);
// Show tooltip instantly if a tooltip was shown recently.
var now = Date.now();
var instant = (this._tooltipLastClosed && now - this._tooltipLastClosed < WebInspector.Tooltip.Timing.InstantThreshold);
this._tooltipElement.classList.toggle("instant", instant);
this._tooltipLastOpened = instant ? now : now + WebInspector.Tooltip.Timing.OpeningDelay;
// Get container element.
var container = WebInspector.Dialog.modalHostView().element;
if (!anchorElement.isDescendant(container))
container = this.element.parentElement;
// Posititon tooltip based on the anchor element.
var containerOffset = container.offsetRelativeToWindow(this.element.window());
var containerOffsetWidth = container.offsetWidth;
var containerOffsetHeight = container.offsetHeight;
var anchorBox = this._anchorElement.boxInWindow(this.element.window());
const anchorOffset = 2;
const pageMargin = 2;
var cursorOffset = 10;
this._tooltipElement.style.maxWidth = (containerOffsetWidth - pageMargin * 2) + "px";
this._tooltipElement.style.maxHeight = "";
var tooltipWidth = this._tooltipElement.offsetWidth;
var tooltipHeight = this._tooltipElement.offsetHeight;
var anchorTooltipAtElement = this._anchorElement.nodeName === "BUTTON" || this._anchorElement.nodeName === "LABEL";
var tooltipX = anchorTooltipAtElement ? anchorBox.x : event.x + cursorOffset;
tooltipX = Number.constrain(tooltipX,
containerOffset.x + pageMargin,
containerOffset.x + containerOffsetWidth - tooltipWidth - pageMargin);
var tooltipY;
if (!anchorTooltipAtElement) {
tooltipY = event.y + cursorOffset + tooltipHeight < containerOffset.y + containerOffsetHeight ? event.y + cursorOffset : event.y - tooltipHeight;
} else {
var onBottom = anchorBox.y + anchorOffset + anchorBox.height + tooltipHeight < containerOffset.y + containerOffsetHeight;
tooltipY = onBottom ? anchorBox.y + anchorBox.height + anchorOffset : anchorBox.y - tooltipHeight - anchorOffset;
}
this._tooltipElement.positionAt(tooltipX, tooltipY);
},
/**
* @param {boolean} removeInstant
*/
_hide: function(removeInstant)
{
delete this._anchorElement;
this._tooltipElement.classList.remove("shown");
if (Date.now() > this._tooltipLastOpened)
this._tooltipLastClosed = Date.now();
if (removeInstant)
delete this._tooltipLastClosed;
},
_reset: function()
{
this._hide(true);
this._tooltipElement.positionAt(0, 0);
this._tooltipElement.style.maxWidth = "0";
this._tooltipElement.style.maxHeight = "0";
}
}
WebInspector.Tooltip._symbol = Symbol("Tooltip");
/**
* @param {!Document} doc
*/
WebInspector.Tooltip.installHandler = function(doc)
{
new WebInspector.Tooltip(doc);
}
/**
* @param {!Element} element
* @param {!Element|string} tooltipContent
* @param {string=} actionId
* @param {!Object=} options
*/
WebInspector.Tooltip.install = function(element, tooltipContent, actionId, options)
{
if (typeof tooltipContent === "string" && tooltipContent === "") {
delete element[WebInspector.Tooltip._symbol];
return;
}
element[WebInspector.Tooltip._symbol] = { content: tooltipContent, actionId: actionId, options: options || {} };
}
/**
* @param {!Element} element
*/
WebInspector.Tooltip.addNativeOverrideContainer = function(element)
{
WebInspector.Tooltip._nativeOverrideContainer.push(element);
}
/** @type {!Array.<!Element>} */
WebInspector.Tooltip._nativeOverrideContainer = [];
WebInspector.Tooltip._nativeTitle = /** @type {!ObjectPropertyDescriptor} */(Object.getOwnPropertyDescriptor(HTMLElement.prototype, "title"));
Object.defineProperty(HTMLElement.prototype, "title", {
/**
* @return {!Element|string}
* @this {!Element}
*/
get: function()
{
var tooltip = this[WebInspector.Tooltip._symbol];
return tooltip ? tooltip.content : "";
},
/**
* @param {!Element|string} x
* @this {!Element}
*/
set: function(x)
{
WebInspector.Tooltip.install(this, x);
}
});