summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/shared/tooltips-overlay.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/inspector/shared/tooltips-overlay.js')
-rw-r--r--devtools/client/inspector/shared/tooltips-overlay.js319
1 files changed, 319 insertions, 0 deletions
diff --git a/devtools/client/inspector/shared/tooltips-overlay.js b/devtools/client/inspector/shared/tooltips-overlay.js
new file mode 100644
index 000000000..8a02d7e3d
--- /dev/null
+++ b/devtools/client/inspector/shared/tooltips-overlay.js
@@ -0,0 +1,319 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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";
+
+/**
+ * The tooltip overlays are tooltips that appear when hovering over property values and
+ * editor tooltips that appear when clicking swatch based editors.
+ */
+
+const { Task } = require("devtools/shared/task");
+const Services = require("Services");
+const {
+ VIEW_NODE_VALUE_TYPE,
+ VIEW_NODE_IMAGE_URL_TYPE,
+} = require("devtools/client/inspector/shared/node-types");
+const { getColor } = require("devtools/client/shared/theme");
+const { getCssProperties } = require("devtools/shared/fronts/css-properties");
+const CssDocsTooltip = require("devtools/client/shared/widgets/tooltip/CssDocsTooltip");
+const { HTMLTooltip } = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
+const {
+ getImageDimensions,
+ setImageTooltip,
+ setBrokenImageTooltip,
+} = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
+const SwatchColorPickerTooltip = require("devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip");
+const SwatchCubicBezierTooltip = require("devtools/client/shared/widgets/tooltip/SwatchCubicBezierTooltip");
+const SwatchFilterTooltip = require("devtools/client/shared/widgets/tooltip/SwatchFilterTooltip");
+
+const PREF_IMAGE_TOOLTIP_SIZE = "devtools.inspector.imagePreviewTooltipSize";
+
+// Types of existing tooltips
+const TOOLTIP_IMAGE_TYPE = "image";
+const TOOLTIP_FONTFAMILY_TYPE = "font-family";
+
+/**
+ * Manages all tooltips in the style-inspector.
+ *
+ * @param {CssRuleView|CssComputedView} view
+ * Either the rule-view or computed-view panel
+ */
+function TooltipsOverlay(view) {
+ this.view = view;
+
+ let {CssRuleView} = require("devtools/client/inspector/rules/rules");
+ this.isRuleView = view instanceof CssRuleView;
+ this._cssProperties = getCssProperties(this.view.inspector.toolbox);
+
+ this._onNewSelection = this._onNewSelection.bind(this);
+ this.view.inspector.selection.on("new-node-front", this._onNewSelection);
+}
+
+TooltipsOverlay.prototype = {
+ get isEditing() {
+ return this.colorPicker.tooltip.isVisible() ||
+ this.colorPicker.eyedropperOpen ||
+ this.cubicBezier.tooltip.isVisible() ||
+ this.filterEditor.tooltip.isVisible();
+ },
+
+ /**
+ * Add the tooltips overlay to the view. This will start tracking mouse
+ * movements and display tooltips when needed
+ */
+ addToView: function () {
+ if (this._isStarted || this._isDestroyed) {
+ return;
+ }
+
+ let { toolbox } = this.view.inspector;
+
+ // Initializing the different tooltips that are used in the inspector.
+ // These tooltips are attached to the toolbox document if they require a popup panel.
+ // Otherwise, it is attached to the inspector panel document if it is an inline
+ // editor.
+ this.previewTooltip = new HTMLTooltip(toolbox.doc, {
+ type: "arrow",
+ useXulWrapper: true
+ });
+ this.previewTooltip.startTogglingOnHover(this.view.element,
+ this._onPreviewTooltipTargetHover.bind(this));
+
+ // MDN CSS help tooltip
+ this.cssDocs = new CssDocsTooltip(toolbox.doc);
+
+ if (this.isRuleView) {
+ // Color picker tooltip
+ this.colorPicker = new SwatchColorPickerTooltip(toolbox.doc, this.view.inspector);
+ // Cubic bezier tooltip
+ this.cubicBezier = new SwatchCubicBezierTooltip(toolbox.doc);
+ // Filter editor tooltip
+ this.filterEditor = new SwatchFilterTooltip(toolbox.doc,
+ this._cssProperties.getValidityChecker(this.view.inspector.panelDoc));
+ }
+
+ this._isStarted = true;
+ },
+
+ /**
+ * Remove the tooltips overlay from the view. This will stop tracking mouse
+ * movements and displaying tooltips
+ */
+ removeFromView: function () {
+ if (!this._isStarted || this._isDestroyed) {
+ return;
+ }
+
+ this.previewTooltip.stopTogglingOnHover(this.view.element);
+ this.previewTooltip.destroy();
+
+ if (this.colorPicker) {
+ this.colorPicker.destroy();
+ }
+
+ if (this.cubicBezier) {
+ this.cubicBezier.destroy();
+ }
+
+ if (this.cssDocs) {
+ this.cssDocs.destroy();
+ }
+
+ if (this.filterEditor) {
+ this.filterEditor.destroy();
+ }
+
+ this._isStarted = false;
+ },
+
+ /**
+ * Given a hovered node info, find out which type of tooltip should be shown,
+ * if any
+ *
+ * @param {Object} nodeInfo
+ * @return {String} The tooltip type to be shown, or null
+ */
+ _getTooltipType: function ({type, value: prop}) {
+ let tooltipType = null;
+ let inspector = this.view.inspector;
+
+ // Image preview tooltip
+ if (type === VIEW_NODE_IMAGE_URL_TYPE &&
+ inspector.hasUrlToImageDataResolver) {
+ tooltipType = TOOLTIP_IMAGE_TYPE;
+ }
+
+ // Font preview tooltip
+ if (type === VIEW_NODE_VALUE_TYPE && prop.property === "font-family") {
+ let value = prop.value.toLowerCase();
+ if (value !== "inherit" && value !== "unset" && value !== "initial") {
+ tooltipType = TOOLTIP_FONTFAMILY_TYPE;
+ }
+ }
+
+ return tooltipType;
+ },
+
+ /**
+ * Executed by the tooltip when the pointer hovers over an element of the
+ * view. Used to decide whether the tooltip should be shown or not and to
+ * actually put content in it.
+ * Checks if the hovered target is a css value we support tooltips for.
+ *
+ * @param {DOMNode} target The currently hovered node
+ * @return {Promise}
+ */
+ _onPreviewTooltipTargetHover: Task.async(function* (target) {
+ let nodeInfo = this.view.getNodeInfo(target);
+ if (!nodeInfo) {
+ // The hovered node isn't something we care about
+ return false;
+ }
+
+ let type = this._getTooltipType(nodeInfo);
+ if (!type) {
+ // There is no tooltip type defined for the hovered node
+ return false;
+ }
+
+ if (this.isRuleView && this.colorPicker.tooltip.isVisible()) {
+ this.colorPicker.revert();
+ this.colorPicker.hide();
+ }
+
+ if (this.isRuleView && this.cubicBezier.tooltip.isVisible()) {
+ this.cubicBezier.revert();
+ this.cubicBezier.hide();
+ }
+
+ if (this.isRuleView && this.cssDocs.tooltip.isVisible()) {
+ this.cssDocs.hide();
+ }
+
+ if (this.isRuleView && this.filterEditor.tooltip.isVisible()) {
+ this.filterEditor.revert();
+ this.filterEdtior.hide();
+ }
+
+ let inspector = this.view.inspector;
+
+ if (type === TOOLTIP_IMAGE_TYPE) {
+ try {
+ yield this._setImagePreviewTooltip(nodeInfo.value.url);
+ } catch (e) {
+ yield setBrokenImageTooltip(this.previewTooltip, this.view.inspector.panelDoc);
+ }
+ return true;
+ }
+
+ if (type === TOOLTIP_FONTFAMILY_TYPE) {
+ let font = nodeInfo.value.value;
+ let nodeFront = inspector.selection.nodeFront;
+ yield this._setFontPreviewTooltip(font, nodeFront);
+ return true;
+ }
+
+ return false;
+ }),
+
+ /**
+ * Set the content of the preview tooltip to display an image preview. The image URL can
+ * be relative, a call will be made to the debuggee to retrieve the image content as an
+ * imageData URI.
+ *
+ * @param {String} imageUrl
+ * The image url value (may be relative or absolute).
+ * @return {Promise} A promise that resolves when the preview tooltip content is ready
+ */
+ _setImagePreviewTooltip: Task.async(function* (imageUrl) {
+ let doc = this.view.inspector.panelDoc;
+ let maxDim = Services.prefs.getIntPref(PREF_IMAGE_TOOLTIP_SIZE);
+
+ let naturalWidth, naturalHeight;
+ if (imageUrl.startsWith("data:")) {
+ // If the imageUrl already is a data-url, save ourselves a round-trip
+ let size = yield getImageDimensions(doc, imageUrl);
+ naturalWidth = size.naturalWidth;
+ naturalHeight = size.naturalHeight;
+ } else {
+ let inspectorFront = this.view.inspector.inspector;
+ let {data, size} = yield inspectorFront.getImageDataFromURL(imageUrl, maxDim);
+ imageUrl = yield data.string();
+ naturalWidth = size.naturalWidth;
+ naturalHeight = size.naturalHeight;
+ }
+
+ yield setImageTooltip(this.previewTooltip, doc, imageUrl,
+ {maxDim, naturalWidth, naturalHeight});
+ }),
+
+ /**
+ * Set the content of the preview tooltip to display a font family preview.
+ *
+ * @param {String} font
+ * The font family value.
+ * @param {object} nodeFront
+ * The NodeActor that will used to retrieve the dataURL for the font
+ * family tooltip contents.
+ * @return {Promise} A promise that resolves when the preview tooltip content is ready
+ */
+ _setFontPreviewTooltip: Task.async(function* (font, nodeFront) {
+ if (!font || !nodeFront || typeof nodeFront.getFontFamilyDataURL !== "function") {
+ throw new Error("Unable to create font preview tooltip content.");
+ }
+
+ font = font.replace(/"/g, "'");
+ font = font.replace("!important", "");
+ font = font.trim();
+
+ let fillStyle = getColor("body-color");
+ let {data, size: maxDim} = yield nodeFront.getFontFamilyDataURL(font, fillStyle);
+
+ let imageUrl = yield data.string();
+ let doc = this.view.inspector.panelDoc;
+ let {naturalWidth, naturalHeight} = yield getImageDimensions(doc, imageUrl);
+
+ yield setImageTooltip(this.previewTooltip, doc, imageUrl,
+ {hideDimensionLabel: true, maxDim, naturalWidth, naturalHeight});
+ }),
+
+ _onNewSelection: function () {
+ if (this.previewTooltip) {
+ this.previewTooltip.hide();
+ }
+
+ if (this.colorPicker) {
+ this.colorPicker.hide();
+ }
+
+ if (this.cubicBezier) {
+ this.cubicBezier.hide();
+ }
+
+ if (this.cssDocs) {
+ this.cssDocs.hide();
+ }
+
+ if (this.filterEditor) {
+ this.filterEditor.hide();
+ }
+ },
+
+ /**
+ * Destroy this overlay instance, removing it from the view
+ */
+ destroy: function () {
+ this.removeFromView();
+
+ this.view.inspector.selection.off("new-node-front", this._onNewSelection);
+ this.view = null;
+
+ this._isDestroyed = true;
+ }
+};
+
+module.exports = TooltipsOverlay;