[mq]: wip draft
authorMantaroh Yoshinaga <mantaroh@gmail.com>
Wed, 19 Sep 2018 14:02:41 +0900
changeset 1700904 25303070e04f02dd5b3166884a341b9ea7353355
parent 1698134 5165e750ffabb930deb846410ab62dcc6d1f9e52
child 1700905 254ce82cc51af770d85bb71efcc98026845846ff
push id300052
push usermantaroh@gmail.com
push dateWed, 19 Sep 2018 05:10:38 +0000
treeherdertry@254ce82cc51a [default view] [failures only]
milestone64.0a1
[mq]: wip
devtools/client/inspector/markup/markup.js
devtools/client/inspector/markup/views/element-container.js
devtools/client/inspector/shared/test/browser_styleinspector_tooltip-closes-on-new-selection.js
devtools/client/inspector/shared/three-pane-onboarding-tooltip.js
devtools/client/responsive.html/setting-onboarding-tooltip.js
devtools/client/shadereditor/shadereditor.js
devtools/client/shared/autocomplete-popup.js
devtools/client/shared/test/browser_html_tooltip-01.js
devtools/client/shared/test/browser_html_tooltip-02.js
devtools/client/shared/test/browser_html_tooltip-03.js
devtools/client/shared/test/browser_html_tooltip-04.js
devtools/client/shared/test/browser_html_tooltip-05.js
devtools/client/shared/test/browser_html_tooltip_arrow-01.js
devtools/client/shared/test/browser_html_tooltip_arrow-02.js
devtools/client/shared/test/browser_html_tooltip_consecutive-show.js
devtools/client/shared/test/browser_html_tooltip_doorhanger-01.js
devtools/client/shared/test/browser_html_tooltip_doorhanger-02.js
devtools/client/shared/test/browser_html_tooltip_height-auto.js
devtools/client/shared/test/browser_html_tooltip_hover.js
devtools/client/shared/test/browser_html_tooltip_offset.js
devtools/client/shared/test/browser_html_tooltip_resize.js
devtools/client/shared/test/browser_html_tooltip_rtl.js
devtools/client/shared/test/browser_html_tooltip_variable-height.js
devtools/client/shared/test/browser_html_tooltip_width-auto.js
devtools/client/shared/test/browser_html_tooltip_xul-wrapper.js
devtools/client/shared/test/browser_html_tooltip_zoom.js
devtools/client/shared/test/helper_html_tooltip.js
devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
devtools/client/shared/widgets/tooltip/HTMLTooltip.js
devtools/client/shared/widgets/tooltip/ImageTooltipHelper.js
devtools/client/shared/widgets/tooltip/SwatchBasedEditorTooltip.js
devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
devtools/client/shared/widgets/tooltip/SwatchCubicBezierTooltip.js
devtools/client/shared/widgets/tooltip/SwatchFilterTooltip.js
devtools/client/shared/widgets/tooltip/TooltipToggle.js
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -91,16 +91,17 @@ function MarkupView(inspector, frame, co
   this._containers = new Map();
   // This weakmap will hold keys used with the _containers map, in order to retrieve the
   // slotted container for a given node front.
   this._slottedContainerKeys = new WeakMap();
 
   // Binding functions that need to be called in scope.
   this._handleRejectionIfNotDestroyed = this._handleRejectionIfNotDestroyed.bind(this);
   this._isImagePreviewTarget = this._isImagePreviewTarget.bind(this);
+  this._setImagePreview = this._setImagePreview.bind(this);
   this._mutationObserver = this._mutationObserver.bind(this);
   this._onBlur = this._onBlur.bind(this);
   this._onCopy = this._onCopy.bind(this);
   this._onCollapseAttributesPrefChange = this._onCollapseAttributesPrefChange.bind(this);
   this._onDisplayChange = this._onDisplayChange.bind(this);
   this._onFocus = this._onFocus.bind(this);
   this._onMouseClick = this._onMouseClick.bind(this);
   this._onMouseMove = this._onMouseMove.bind(this);
@@ -168,17 +169,18 @@ MarkupView.prototype = {
       type: "arrow",
       useXulWrapper: true,
     });
     this._enableImagePreviewTooltip();
   },
 
   _enableImagePreviewTooltip: function() {
     this.imagePreviewTooltip.startTogglingOnHover(this._elt,
-      this._isImagePreviewTarget);
+                                                  this._isImagePreviewTarget, {},
+                                                  this._setImagePreview);
   },
 
   _disableImagePreviewTooltip: function() {
     this.imagePreviewTooltip.stopTogglingOnHover();
   },
 
   _onToolboxPickerHover: function(nodeFront) {
     this.showNode(nodeFront).then(() => {
@@ -561,16 +563,28 @@ MarkupView.prototype = {
         break;
       }
     }
 
     // Recursively update each node starting with documentElement.
     updateChildren(documentElement);
   },
 
+  _getContainerFromTarget(target) {
+    let parent = target, container;
+    while (parent) {
+      if (parent.container) {
+        container = parent.container;
+        break;
+      }
+      parent = parent.parentNode;
+    }
+    return container;
+  },
+
   /**
    * Executed when the mouse hovers over a target in the markup-view and is used
    * to decide whether this target should be used to display an image preview
    * tooltip.
    * Delegates the actual decision to the corresponding MarkupContainer instance
    * if one is found.
    *
    * @return {Promise} the promise returned by
@@ -578,32 +592,32 @@ MarkupView.prototype = {
    */
   async _isImagePreviewTarget(target) {
     // From the target passed here, let's find the parent MarkupContainer
     // and ask it if the tooltip should be shown
     if (this.isDragging) {
       return false;
     }
 
-    let parent = target, container;
-    while (parent) {
-      if (parent.container) {
-        container = parent.container;
-        break;
-      }
-      parent = parent.parentNode;
-    }
-
+    let container = this._getContainerFromTarget(target);
     if (container instanceof MarkupElementContainer) {
       return container.isImagePreviewTarget(target, this.imagePreviewTooltip);
     }
 
     return false;
   },
 
+  async _setImagePreview(target, tooltip) {
+    let container = this._getContainerFromTarget(target);
+    if (!(container instanceof MarkupElementContainer)) {
+      return;
+    }
+    await container.showImagePreview(target, this.imagePreviewTooltip);
+  },
+
   /**
    * Given the known reason, should the current selection be briefly highlighted
    * In a few cases, we don't want to highlight the node:
    * - If the reason is null (used to reset the selection),
    * - if it's "inspector-open" (when the inspector opens up, let's not
    * highlight the default node)
    * - if it's "navigateaway" (since the page is being navigated away from)
    * - if it's "test" (this is a special case for mochitest. In tests, we often
--- a/devtools/client/inspector/markup/views/element-container.js
+++ b/devtools/client/inspector/markup/views/element-container.js
@@ -5,17 +5,17 @@
 "use strict";
 
 const PREVIEW_MAX_DIM_PREF = "devtools.inspector.imagePreviewTooltipSize";
 
 const promise = require("promise");
 const Services = require("Services");
 const nodeConstants = require("devtools/shared/dom-node-constants");
 const clipboardHelper = require("devtools/shared/platform/clipboard");
-const {setImageTooltip, setBrokenImageTooltip} =
+const {setImageTooltip, getImageTooltip, setBrokenImageTooltip, getBrokenImageTooltip} =
       require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
 const MarkupContainer = require("devtools/client/inspector/markup/views/markup-container");
 const ElementEditor = require("devtools/client/inspector/markup/views/element-editor");
 const {extend} = require("devtools/shared/extend");
 
 // Lazy load this module as _buildEventTooltipContent is only called on click
 loader.lazyRequireGetter(this, "setEventTooltip",
   "devtools/client/shared/widgets/tooltip/EventTooltipHelper", true);
@@ -102,17 +102,18 @@ MarkupElementContainer.prototype = exten
   async _buildEventTooltipContent(target) {
     const tooltip = this.markup.eventDetailsTooltip;
     await tooltip.hide();
 
     const listenerInfo = await this.node.getEventListenerInfo();
 
     const toolbox = this.markup.toolbox;
 
-    setEventTooltip(tooltip, listenerInfo, toolbox);
+    const { content, option: { width, height } } =
+          setEventTooltip(tooltip, listenerInfo, toolbox);
     // Disable the image preview tooltip while we display the event details
     this.markup._disableImagePreviewTooltip();
     tooltip.once("hidden", () => {
       // Enable the image preview tooltip after closing the event details
       this.markup._enableImagePreviewTooltip();
 
       // Allow clicks on the event badge to display the event popup again
       // (but allow the currently queued click event to run first).
@@ -122,17 +123,17 @@ MarkupElementContainer.prototype = exten
         }
       }, 0);
     });
 
     // Prevent clicks on the event badge to display the event popup again.
     if (this.editor._eventBadge) {
       this.editor._eventBadge.style.pointerEvents = "none";
     }
-    tooltip.show(target);
+    tooltip.show(target, { width, height }, content);
   },
 
   /**
    * Generates the an image preview for this Element. The element must be an
    * image or canvas (@see isPreviewable).
    *
    * @return {Promise} that is resolved with an object of form
    *         { data, size: { naturalWidth, naturalHeight, resizeRatio } } where
@@ -164,17 +165,17 @@ MarkupElementContainer.prototype = exten
       this.tooltipDataPromise = null;
       return { data, size: preview.size };
     }.bind(this))();
 
     return this.tooltipDataPromise;
   },
 
   /**
-   * Executed by MarkupView._isImagePreviewTarget which is itself called when
+   * Executed by MarkupView._isImagePrevieTwarget which is itself called when
    * the mouse hovers over a target in the markup-view.
    * Checks if the target is indeed something we want to have an image tooltip
    * preview over and, if so, inserts content into the tooltip.
    *
    * @return {Promise} that resolves when the tooltip content is ready. Resolves
    * true if the tooltip should be displayed, false otherwise.
    */
   async isImagePreviewTarget(target, tooltip) {
@@ -187,31 +188,39 @@ MarkupElementContainer.prototype = exten
     // over the src url. If not, the tooltip is shown when hovering over the tag
     // name.
     const src = this.editor.getAttributeElement("src");
     const expectedTarget = src ? src.querySelector(".link") : this.editor.tag;
     if (target !== expectedTarget) {
       return false;
     }
 
+    return true;
+  },
+
+  async showImagePreview(target, tooltip) {
+    let tooltipContent, tooltipOption;
     try {
       const { data, size } = await this._getPreview();
       // The preview is ready.
       const options = {
         naturalWidth: size.naturalWidth,
         naturalHeight: size.naturalHeight,
         maxDim: Services.prefs.getIntPref(PREVIEW_MAX_DIM_PREF)
       };
 
-      setImageTooltip(tooltip, this.markup.doc, data, options);
+      let { content: tooltipContent, option: tooltipOption } =
+          getImageTooltip(tooltip, this.markup.doc, data, options);
+      tooltip.show(target, tooltipOption, tooltipContent);
     } catch (e) {
       // Indicate the failure but show the tooltip anyway.
-      setBrokenImageTooltip(tooltip, this.markup.doc);
+      let { content: tooltipContent, option: tooltipOption } =
+          getBrokenImageTooltip(tooltip, this.markup.doc);
+      tooltip.show(target, tooltipOption, tooltipContent);
     }
-    return true;
   },
 
   copyImageDataUri: function() {
     // We need to send again a request to gettooltipData even if one was sent
     // for the tooltip, because we want the full-size image
     this.node.getImageData().then(data => {
       data.data.string().then(str => {
         clipboardHelper.copyString(str);
--- a/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-closes-on-new-selection.js
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-closes-on-new-selection.js
@@ -22,26 +22,27 @@ add_task(async function() {
   await testComputedView(view, inspector);
 });
 
 async function testRuleView(ruleView, inspector) {
   info("Showing the tooltip");
 
   const tooltip = ruleView.tooltips.getTooltip("previewTooltip");
   const tooltipContent = ruleView.styleDocument.createElementNS(XHTML_NS, "div");
-  await tooltip.setContent(tooltipContent, {width: 100, height: 30});
 
   // Stop listening for mouse movements because it's not needed for this test,
   // and causes intermittent failures on Linux. When this test runs in the suite
   // sometimes a mouseleave event is dispatched at the start, which causes the
   // tooltip to hide in the middle of being shown, which causes timeouts later.
   tooltip.stopTogglingOnHover();
 
   const onShown = tooltip.once("shown");
-  tooltip.show(ruleView.styleDocument.firstElementChild);
+  tooltip.show(ruleView.styleDocument.firstElementChild,
+               {width: 100, height: 30},
+               tooltipContent);
   await onShown;
 
   info("Selecting a new node");
   const onHidden = tooltip.once("hidden");
   await selectNode(".two", inspector);
   await onHidden;
 
   ok(true, "Rule view tooltip closed after a new node got selected");
--- a/devtools/client/inspector/shared/three-pane-onboarding-tooltip.js
+++ b/devtools/client/inspector/shared/three-pane-onboarding-tooltip.js
@@ -63,20 +63,20 @@ class ThreePaneOnboardingTooltip {
 
     this.closeButton = doc.createElementNS(XHTML_NS, "button");
     this.closeButton.className = "onboarding-close-button devtools-button";
     container.appendChild(this.closeButton);
 
     this.closeButton.addEventListener("click", this.onCloseButtonClick);
     this.learnMoreLink.addEventListener("click", this.onLearnMoreLinkClick);
 
-    this.tooltip.setContent(container, { width: CONTAINER_WIDTH });
     this.tooltip.show(this.doc.querySelector("#inspector-sidebar .sidebar-toggle"), {
       position: "top",
-    });
+      width: CONTAINER_WIDTH,
+    }, container);
   }
 
   destroy() {
     this.closeButton.removeEventListener("click", this.onCloseButtonClick);
     this.learnMoreLink.removeEventListener("click", this.onLearnMoreLinkClick);
 
     this.tooltip.destroy();
 
--- a/devtools/client/responsive.html/setting-onboarding-tooltip.js
+++ b/devtools/client/responsive.html/setting-onboarding-tooltip.js
@@ -37,20 +37,21 @@ class SettingOnboardingTooltip {
     container.appendChild(content);
 
     this.closeButton = doc.createElement("button");
     this.closeButton.className = "onboarding-close-button devtools-button";
     container.appendChild(this.closeButton);
 
     this.closeButton.addEventListener("click", this.onCloseButtonClick);
 
-    this.tooltip.setContent(container, { width: CONTAINER_WIDTH });
+//    this.tooltip.setContent(container, { width: CONTAINER_WIDTH });
     this.tooltip.show(this.doc.getElementById("settings-button"), {
       position: "bottom",
-    });
+      width: CONTAINER_WIDTH,
+    }, container);
   }
 
   destroy() {
     this.closeButton.removeEventListener("click", this.onCloseButtonClick);
 
     this.tooltip.destroy();
 
     this.closeButton = null;
--- a/devtools/client/shadereditor/shadereditor.js
+++ b/devtools/client/shadereditor/shadereditor.js
@@ -580,28 +580,40 @@ class ShadersEditorsView {
       return;
     }
 
     const tooltip = node._markerErrorsTooltip = new HTMLTooltip(document, {
       type: "arrow",
       useXulWrapper: true
     });
 
+    /*
     const div = document.createElementNS(XHTML_NS, "div");
     div.className = "devtools-shader-tooltip-container";
     for (const message of messages) {
       const messageDiv = document.createElementNS(XHTML_NS, "div");
       messageDiv.className = "devtools-tooltip-simple-text";
       messageDiv.textContent = message;
       div.appendChild(messageDiv);
     }
     tooltip.setContent(div);
+    */
 
     tooltip.startTogglingOnHover(node, () => true, {
       toggleDelay: GUTTER_ERROR_PANEL_DELAY
+    }, (target, tooltip) => {
+      const div = document.createElementNS(XHTML_NS, "div");
+      div.className = "devtools-shader-tooltip-container";
+      for (const message of messages) {
+        const messageDiv = document.createElementNS(XHTML_NS, "div");
+        messageDiv.className = "devtools-tooltip-simple-text";
+        messageDiv.textContent = message;
+        div.appendChild(messageDiv);
+      }
+      tooltip.show(target, {}, div);
     });
   }
 
   /**
    * Removes all the gutter markers and line classes from the editor.
    */
   _cleanEditor(type) {
     this._getEditor(type).then(editor => {
--- a/devtools/client/shared/autocomplete-popup.js
+++ b/devtools/client/shared/autocomplete-popup.js
@@ -67,17 +67,17 @@ function AutocompletePopup(toolboxDoc, o
     .getPropertyValue(paddingPropertyName)
     .replace("px", "");
 
   this._listPadding = 0;
   if (!Number.isNaN(Number(listPadding))) {
     this._listPadding = Number(listPadding);
   }
 
-  this._tooltip.setContent(this._list, { height: Infinity });
+//  this._tooltip.setContent(this._list, { height: Infinity });
 
   this.onClick = this.onClick.bind(this);
   this._list.addEventListener("click", this.onClick);
 
   // Array of raw autocomplete items
   this.items = [];
   // Map of autocompleteItem to HTMLElement
   this.elements = new WeakMap();
@@ -130,17 +130,18 @@ AutocompletePopup.prototype = {
     // We want the autocomplete items to be perflectly lined-up with the string the
     // user entered, so we need to remove the left-padding and the left-border from
     // the xOffset.
     const leftBorderSize = 1;
     this._tooltip.show(anchor, {
       x: xOffset - this._listPadding - leftBorderSize,
       y: yOffset,
       position: this.position,
-    });
+      height: Infinity,
+    }, this._list);
 
     this._tooltip.once("shown", () => {
       if (this.autoSelect) {
         this.selectItemAtIndex(index, options);
       }
 
       this.emit("popup-opened");
     });
--- a/devtools/client/shared/test/browser_html_tooltip-01.js
+++ b/devtools/client/shared/test/browser_html_tooltip-01.js
@@ -36,27 +36,28 @@ add_task(async function() {
   await runTests(doc);
 });
 
 async function runTests(doc) {
   await addTab("about:blank");
   const 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.");
 
   let shown = 0;
   tooltip.on("shown", () => shown++);
 
   const onShown = tooltip.once("shown");
-  tooltip.show(doc.getElementById("box1"));
+  tooltip.show(doc.getElementById("box1"),
+               {width: 100, height: 50},
+               getTooltipContent(doc));
 
   await onShown;
   is(shown, 1, "Event shown was fired once");
 
   await waitForReflow(tooltip);
   is(tooltip.isVisible(), true, "Tooltip is visible");
 
   info("Hide the tooltip and check the expected events are fired.");
--- a/devtools/client/shared/test/browser_html_tooltip-02.js
+++ b/devtools/client/shared/test/browser_html_tooltip-02.js
@@ -36,34 +36,38 @@ async function runTests(doc) {
   await testClickInOuterIframe(doc);
   await testClickInInnerIframe(doc);
 }
 
 async function testClickInTooltipContent(doc) {
   info("Test a tooltip is not closed when clicking inside itself");
 
   const tooltip = new HTMLTooltip(doc, {useXulWrapper});
-  tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
-  await showTooltip(tooltip, doc.getElementById("box1"));
+  await showTooltip(tooltip,
+                    doc.getElementById("box1"),
+                    {width: 100, height: 50},
+                    getTooltipContent(doc));
 
   const onTooltipContainerClick = once(tooltip.container, "click");
   EventUtils.synthesizeMouseAtCenter(tooltip.container, {}, doc.defaultView);
   await onTooltipContainerClick;
   is(tooltip.isVisible(), true, "Tooltip is still visible");
 
   tooltip.destroy();
 }
 
 async function testConsumeOutsideClicksFalse(doc) {
   info("Test closing a tooltip via click with consumeOutsideClicks: false");
   const box4 = doc.getElementById("box4");
 
   const tooltip = new HTMLTooltip(doc, {consumeOutsideClicks: false, useXulWrapper});
-  tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
-  await showTooltip(tooltip, doc.getElementById("box1"));
+  await showTooltip(tooltip,
+                    doc.getElementById("box1"),
+                    {width: 100, height: 50},
+                    getTooltipContent(doc));
 
   const onBox4Clicked = once(box4, "click");
   const onHidden = once(tooltip, "hidden");
   EventUtils.synthesizeMouseAtCenter(box4, {}, doc.defaultView);
   await onHidden;
   await onBox4Clicked;
 
   is(tooltip.isVisible(), false, "Tooltip is hidden");
@@ -75,36 +79,40 @@ async function testConsumeOutsideClicksT
   info("Test closing a tooltip via click with consumeOutsideClicks: true");
   const box4 = doc.getElementById("box4");
 
   // Count clicks on box4
   let box4clicks = 0;
   box4.addEventListener("click", () => box4clicks++);
 
   const tooltip = new HTMLTooltip(doc, {consumeOutsideClicks: true, useXulWrapper});
-  tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
-  await showTooltip(tooltip, doc.getElementById("box1"));
+  await showTooltip(tooltip,
+                    doc.getElementById("box1"),
+                    {width: 100, height: 50},
+                    getTooltipContent(doc));
 
   const onHidden = once(tooltip, "hidden");
   EventUtils.synthesizeMouseAtCenter(box4, {}, doc.defaultView);
   await onHidden;
 
   is(box4clicks, 0, "box4 catched no click event");
   is(tooltip.isVisible(), false, "Tooltip is hidden");
 
   tooltip.destroy();
 }
 
 async function testConsumeWithRightClick(doc) {
   info("Test closing a tooltip with a right-click, with consumeOutsideClicks: true");
   const box4 = doc.getElementById("box4");
 
   const tooltip = new HTMLTooltip(doc, {consumeOutsideClicks: true, useXulWrapper});
-  tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
-  await showTooltip(tooltip, doc.getElementById("box1"));
+  await showTooltip(tooltip,
+                    doc.getElementById("box1"),
+                    {width: 100, height: 50},
+                    getTooltipContent(doc));
 
   // Only left-click events should be consumed, so we expect to catch a click when using
   // {button: 2}, which simulates a right-click.
   info("Right click on box4, expect tooltip to be hidden, event should not be consumed");
   const onBox4Clicked = once(box4, "click");
   const onHidden = once(tooltip, "hidden");
   EventUtils.synthesizeMouseAtCenter(box4, {button: 2}, doc.defaultView);
   await onHidden;
@@ -115,18 +123,20 @@ async function testConsumeWithRightClick
   tooltip.destroy();
 }
 
 async function testClickInOuterIframe(doc) {
   info("Test clicking an iframe outside of the tooltip closes the tooltip");
   const frame = doc.getElementById("frame");
 
   const tooltip = new HTMLTooltip(doc, {useXulWrapper});
-  tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
-  await showTooltip(tooltip, doc.getElementById("box1"));
+  await showTooltip(tooltip,
+                    doc.getElementById("box1"),
+                    {width: 100, height: 50},
+                    getTooltipContent(doc));
 
   const onHidden = once(tooltip, "hidden");
   EventUtils.synthesizeMouseAtCenter(frame, {}, doc.defaultView);
   await onHidden;
 
   is(tooltip.isVisible(), false, "Tooltip is hidden");
   tooltip.destroy();
 }
@@ -134,18 +144,20 @@ async function testClickInOuterIframe(do
 async function testClickInInnerIframe(doc) {
   info("Test clicking an iframe inside the tooltip content does not close the tooltip");
 
   const tooltip = new HTMLTooltip(doc, {consumeOutsideClicks: false, useXulWrapper});
 
   const iframe = doc.createElementNS(HTML_NS, "iframe");
   iframe.style.width = "100px";
   iframe.style.height = "50px";
-  tooltip.setContent(iframe, {width: 100, height: 50});
-  await showTooltip(tooltip, doc.getElementById("box1"));
+  await showTooltip(tooltip,
+                    doc.getElementById("box1"),
+                    {width: 100, height: 50},
+                    iframe);
 
   const onTooltipContainerClick = once(tooltip.container, "click");
   EventUtils.synthesizeMouseAtCenter(tooltip.container, {}, doc.defaultView);
   await onTooltipContainerClick;
 
   is(tooltip.isVisible(), true, "Tooltip is still visible");
 
   tooltip.destroy();
--- a/devtools/client/shared/test/browser_html_tooltip-03.js
+++ b/devtools/client/shared/test/browser_html_tooltip-03.js
@@ -29,19 +29,22 @@ add_task(async function() {
   await runTests(doc);
 });
 
 async function runTests(doc) {
   await focusNode(doc, "#box4-input");
   ok(doc.activeElement.closest("#box4-input"), "Focus is in the #box4-input");
 
   info("Test a tooltip will not take focus");
-  const tooltip = await createTooltip(doc);
+  const { tooltip, content } = await createTooltip(doc);
 
-  await showTooltip(tooltip, doc.getElementById("box1"));
+  await showTooltip(tooltip,
+                    doc.getElementById("box1"),
+                    { width: 150, height: 50 },
+                    content);
   ok(doc.activeElement.closest("#box4-input"), "Focus is still in the #box4-input");
 
   await hideTooltip(tooltip);
   await blurNode(doc, "#box4-input");
 
   tooltip.destroy();
 }
 
@@ -81,10 +84,10 @@ function createTooltip(doc) {
   div.classList.add("tooltip-content");
   div.style.height = "50px";
 
   const input = doc.createElementNS(HTML_NS, "input");
   input.setAttribute("type", "text");
   div.appendChild(input);
 
   tooltip.setContent(div, {width: 150, height: 50});
-  return tooltip;
+  return { tooltip, content: div };
 }
--- a/devtools/client/shared/test/browser_html_tooltip-04.js
+++ b/devtools/client/shared/test/browser_html_tooltip-04.js
@@ -24,73 +24,84 @@ add_task(async function() {
 
   await addTab("about:blank");
   const [,, doc] = await createHost("bottom", TEST_URI);
 
   info("Create HTML tooltip");
   const tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
   const div = doc.createElementNS(HTML_NS, "div");
   div.style.height = "100%";
-  tooltip.setContent(div, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
 
   const box1 = doc.getElementById("box1");
   const box2 = doc.getElementById("box2");
   const box3 = doc.getElementById("box3");
   const box4 = doc.getElementById("box4");
   const height = TOOLTIP_HEIGHT, width = TOOLTIP_WIDTH;
 
   // box1: Can only fit below box1
   info("Display the tooltip on box1.");
-  await showTooltip(tooltip, box1);
+  await showTooltip(tooltip, box1, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT}, div);
   let expectedTooltipGeometry = {position: "bottom", height, width};
   checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   info("Try to display the tooltip on top of box1.");
-  await showTooltip(tooltip, box1, {position: "top"});
+  await showTooltip(tooltip,
+                    box1,
+                    {position: "top", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT},
+                    div);
   expectedTooltipGeometry = {position: "bottom", height, width};
   checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   // box2: Can fit above or below, will default to bottom, more height
   // available.
   info("Try to display the tooltip on box2.");
-  await showTooltip(tooltip, box2);
+  await showTooltip(tooltip, box2, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT}, div);
   expectedTooltipGeometry = {position: "bottom", height, width};
   checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   info("Try to display the tooltip on top of box2.");
-  await showTooltip(tooltip, box2, {position: "top"});
+  await showTooltip(tooltip,
+                    box2,
+                    {position: "top", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT},
+                    div);
   expectedTooltipGeometry = {position: "top", height, width};
   checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   // box3: Can fit above or below, will default to top, more height available.
   info("Try to display the tooltip on box3.");
-  await showTooltip(tooltip, box3);
+  await showTooltip(tooltip, box3, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT}, div);
   expectedTooltipGeometry = {position: "top", height, width};
   checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   info("Try to display the tooltip on bottom of box3.");
-  await showTooltip(tooltip, box3, {position: "bottom"});
+  await showTooltip(tooltip,
+                    box3,
+                    {position: "bottom", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT},
+                    div);
   expectedTooltipGeometry = {position: "bottom", height, width};
   checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   // box4: Can only fit above box4
   info("Display the tooltip on box4.");
-  await showTooltip(tooltip, box4);
+  await showTooltip(tooltip, box4, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT}, div);
   expectedTooltipGeometry = {position: "top", height, width};
   checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   info("Try to display the tooltip on bottom of box4.");
-  await showTooltip(tooltip, box4, {position: "bottom"});
+  await showTooltip(tooltip,
+                    box4,
+                    {position: "bottom", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT},
+                    div);
   expectedTooltipGeometry = {position: "top", height, width};
   checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   is(tooltip.isVisible(), false, "Tooltip is not visible");
 
   tooltip.destroy();
 });
--- a/devtools/client/shared/test/browser_html_tooltip-05.js
+++ b/devtools/client/shared/test/browser_html_tooltip-05.js
@@ -23,76 +23,87 @@ add_task(async function() {
   await pushPref("devtools.toolbox.footer.height", 200);
   await addTab("about:blank");
   const [,, doc] = await createHost("bottom", TEST_URI);
 
   info("Create HTML tooltip");
   const tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
   const div = doc.createElementNS(HTML_NS, "div");
   div.style.height = "100%";
-  tooltip.setContent(div, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
 
   const box1 = doc.getElementById("box1");
   const box2 = doc.getElementById("box2");
   const box3 = doc.getElementById("box3");
   const box4 = doc.getElementById("box4");
   const width = TOOLTIP_WIDTH;
 
   // box1: Can not fit above or below box1, default to bottom with a reduced
   // height of 150px.
   info("Display the tooltip on box1.");
-  await showTooltip(tooltip, box1);
+  await showTooltip(tooltip, box1, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT}, div);
   let expectedTooltipGeometry = {position: "bottom", height: 150, width};
   checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   info("Try to display the tooltip on top of box1.");
-  await showTooltip(tooltip, box1, {position: "top"});
+  await showTooltip(tooltip,
+                    box1,
+                    {position: "top", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT},
+                    div);
   expectedTooltipGeometry = {position: "bottom", height: 150, width};
   checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
   await 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.");
-  await showTooltip(tooltip, box2);
+  await showTooltip(tooltip, box2, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT}, div);
   expectedTooltipGeometry = {position: "bottom", height: 100, width};
   checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   info("Try to display the tooltip on top of box2.");
-  await showTooltip(tooltip, box2, {position: "top"});
+  await showTooltip(tooltip,
+                    box2,
+                    {position: "top", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT},
+                    div);
   expectedTooltipGeometry = {position: "bottom", height: 100, width};
   checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   // box3: Can not fit above or below box3, default to top with a reduced height
   // of 100px.
   info("Try to display the tooltip on box3.");
-  await showTooltip(tooltip, box3);
+  await showTooltip(tooltip, box3, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT}, div);
   expectedTooltipGeometry = {position: "top", height: 100, width};
   checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   info("Try to display the tooltip on bottom of box3.");
-  await showTooltip(tooltip, box3, {position: "bottom"});
+  await showTooltip(tooltip,
+                    box3,
+                    {position: "bottom", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT},
+                    div);
   expectedTooltipGeometry = {position: "top", height: 100, width};
   checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   // box4: Can not fit above or below box4, default to top with a reduced height
   // of 150px.
   info("Display the tooltip on box4.");
-  await showTooltip(tooltip, box4);
+  await showTooltip(tooltip, box4, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT}, div);
   expectedTooltipGeometry = {position: "top", height: 150, width};
   checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   info("Try to display the tooltip on bottom of box4.");
-  await showTooltip(tooltip, box4, {position: "bottom"});
+  await showTooltip(tooltip,
+                    box4,
+                    {position: "bottom", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT},
+                    div);
   expectedTooltipGeometry = {position: "top", height: 150, width};
   checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
   await hideTooltip(tooltip);
 
   is(tooltip.isVisible(), false, "Tooltip is not visible");
 
   tooltip.destroy();
 });
--- a/devtools/client/shared/test/browser_html_tooltip_arrow-01.js
+++ b/devtools/client/shared/test/browser_html_tooltip_arrow-01.js
@@ -33,24 +33,23 @@ add_task(async function() {
   await runTests(doc);
 });
 
 async function runTests(doc) {
   info("Create HTML tooltip");
   const tooltip = new HTMLTooltip(doc, {type: "arrow", useXulWrapper});
   const div = doc.createElementNS(HTML_NS, "div");
   div.style.height = "35px";
-  tooltip.setContent(div, {width: 200, height: 35});
 
   const {right: docRight} = doc.documentElement.getBoundingClientRect();
 
   const elements = [...doc.querySelectorAll(".anchor")];
   for (const el of elements) {
     info("Display the tooltip on an anchor.");
-    await showTooltip(tooltip, el);
+    await showTooltip(tooltip, el, {width: 200, height: 35}, div);
 
     const arrow = tooltip.arrow;
     ok(arrow, "Tooltip has an arrow");
 
     // Get the geometry of the anchor, the tooltip panel & arrow.
     const arrowBounds = arrow.getBoxQuads({relativeTo: doc})[0].getBounds();
     const panelBounds = tooltip.panel.getBoxQuads({relativeTo: doc})[0].getBounds();
     const anchorBounds = el.getBoxQuads({relativeTo: doc})[0].getBounds();
--- a/devtools/client/shared/test/browser_html_tooltip_arrow-02.js
+++ b/devtools/client/shared/test/browser_html_tooltip_arrow-02.js
@@ -32,24 +32,23 @@ add_task(async function() {
   await runTests(doc);
 });
 
 async function runTests(doc) {
   info("Create HTML tooltip");
   const tooltip = new HTMLTooltip(doc, {type: "arrow", useXulWrapper});
   const div = doc.createElementNS(HTML_NS, "div");
   div.style.height = "35px";
-  tooltip.setContent(div, {width: 200, height: 35});
 
   const {right: docRight} = doc.documentElement.getBoundingClientRect();
 
   const elements = [...doc.querySelectorAll(".anchor")];
   for (const el of elements) {
     info("Display the tooltip on an anchor.");
-    await showTooltip(tooltip, el);
+    await showTooltip(tooltip, el, {width: 200, height: 35}, div);
 
     const arrow = tooltip.arrow;
     ok(arrow, "Tooltip has an arrow");
 
     // Get the geometry of the anchor, the tooltip panel & arrow.
     const arrowBounds = arrow.getBoxQuads({relativeTo: doc})[0].getBounds();
     const panelBounds = tooltip.panel.getBoxQuads({relativeTo: doc})[0].getBounds();
     const anchorBounds = el.getBoxQuads({relativeTo: doc})[0].getBounds();
--- a/devtools/client/shared/test/browser_html_tooltip_consecutive-show.js
+++ b/devtools/client/shared/test/browser_html_tooltip_consecutive-show.js
@@ -28,33 +28,32 @@ add_task(async function() {
   const box1 = doc.getElementById("box1");
   const box2 = doc.getElementById("box2");
   const box3 = doc.getElementById("box3");
   const box4 = doc.getElementById("box4");
 
   const width = 100, height = 50;
 
   const 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);
+  tooltip.show(box1, {width, height}, getTooltipContent(doc));
   checkTooltipGeometry(tooltip, box1, {position: "bottom", width, height});
 
   info("Show tooltip on box2");
-  tooltip.show(box2);
+  tooltip.show(box2, {width, height}, getTooltipContent(doc));
   checkTooltipGeometry(tooltip, box2, {position: "bottom", width, height});
 
   info("Show tooltip on box3");
-  tooltip.show(box3);
+  tooltip.show(box3, {width, height}, getTooltipContent(doc));
   checkTooltipGeometry(tooltip, box3, {position: "top", width, height});
 
   info("Show tooltip on box4");
-  tooltip.show(box4);
+  tooltip.show(box4, {width, height}, getTooltipContent(doc));
   checkTooltipGeometry(tooltip, box4, {position: "top", width, height});
 
   info("Hide tooltip before leaving test");
   await hideTooltip(tooltip);
 
   tooltip.destroy();
 });
--- a/devtools/client/shared/test/browser_html_tooltip_doorhanger-01.js
+++ b/devtools/client/shared/test/browser_html_tooltip_doorhanger-01.js
@@ -35,24 +35,23 @@ add_task(async function() {
 });
 
 async function runTests(doc) {
   info("Create HTML tooltip");
   const tooltip = new HTMLTooltip(doc, {type: "doorhanger", useXulWrapper});
   const div = doc.createElementNS(HTML_NS, "div");
   div.style.width = "200px";
   div.style.height = "35px";
-  tooltip.setContent(div);
 
   const docBounds = doc.documentElement.getBoundingClientRect();
 
   const elements = [...doc.querySelectorAll(".anchor")];
   for (const el of elements) {
     info("Display the tooltip on an anchor.");
-    await showTooltip(tooltip, el);
+    await showTooltip(tooltip, el, {}, div);
 
     const arrow = tooltip.arrow;
     ok(arrow, "Tooltip has an arrow");
 
     // Get the geometry of the anchor, the tooltip panel & arrow.
     const anchorBounds = el.getBoxQuads({ relativeTo: doc })[0].getBounds();
     const panelBounds =
       tooltip.panel.getBoxQuads({ relativeTo: doc })[0].getBounds();
--- a/devtools/client/shared/test/browser_html_tooltip_doorhanger-02.js
+++ b/devtools/client/shared/test/browser_html_tooltip_doorhanger-02.js
@@ -35,22 +35,21 @@ add_task(async function() {
 });
 
 async function runTests(doc) {
   info("Create HTML tooltip");
   const tooltip = new HTMLTooltip(doc, {type: "doorhanger", useXulWrapper});
   const div = doc.createElementNS(HTML_NS, "div");
   div.style.width = "200px";
   div.style.height = "35px";
-  tooltip.setContent(div);
 
   const elements = [...doc.querySelectorAll(".anchor")];
   for (const el of elements) {
     info("Display the tooltip on an anchor.");
-    await showTooltip(tooltip, el);
+    await showTooltip(tooltip, el, {}, div);
 
     const arrow = tooltip.arrow;
     ok(arrow, "Tooltip has an arrow");
 
     // Get the geometry of the anchor and arrow.
     const anchorBounds = el.getBoxQuads({ relativeTo: doc })[0].getBounds();
     const arrowBounds = arrow.getBoxQuads({ relativeTo: doc })[0].getBounds();
 
--- a/devtools/client/shared/test/browser_html_tooltip_height-auto.js
+++ b/devtools/client/shared/test/browser_html_tooltip_height-auto.js
@@ -34,34 +34,32 @@ add_task(async function() {
 async function runTests(doc) {
   const tooltip = new HTMLTooltip(doc, {useXulWrapper});
   info("Create tooltip content height to 150px");
   const tooltipContent = doc.createElementNS(HTML_NS, "div");
   tooltipContent.style.cssText =
     "width: 300px; height: 150px; background: red;";
 
   info("Set tooltip content using width:auto and height:auto");
-  tooltip.setContent(tooltipContent);
 
   info("Show the tooltip and check the tooltip panel dimensions.");
-  await showTooltip(tooltip, doc.getElementById("box1"));
+  await showTooltip(tooltip, doc.getElementById("box1"), {}, tooltipContent);
 
   let panelRect = tooltip.panel.getBoundingClientRect();
   is(panelRect.width, 300, "Tooltip panel has the expected width.");
   is(panelRect.height, 150, "Tooltip panel has the expected width.");
 
   await hideTooltip(tooltip);
 
   info("Set tooltip content using fixed width and height:auto");
   tooltipContent.style.cssText =
     "width: auto; height: 200px; background: red;";
-  tooltip.setContent(tooltipContent, { width: 400 });
 
   info("Show the tooltip and check the tooltip panel height.");
-  await showTooltip(tooltip, doc.getElementById("box1"));
+  await showTooltip(tooltip, doc.getElementById("box1"), { width: 400 }, tooltipContent);
 
   panelRect = tooltip.panel.getBoundingClientRect();
   is(panelRect.height, 200, "Tooltip panel has the expected width.");
 
   await hideTooltip(tooltip);
 
   tooltip.destroy();
 }
--- a/devtools/client/shared/test/browser_html_tooltip_hover.js
+++ b/devtools/client/shared/test/browser_html_tooltip_hover.js
@@ -18,20 +18,24 @@ add_task(async function() {
   const [,, doc] = await createHost("bottom", TEST_URI);
   // Wait for full page load before synthesizing events on the page.
   await waitUntil(() => doc.readyState === "complete");
 
   const width = 100, height = 50;
   const tooltipContent = doc.createElementNS(HTML_NS, "div");
   tooltipContent.textContent = "tooltip";
   const tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
-  tooltip.setContent(tooltipContent, {width, height});
 
   const container = doc.getElementById("container");
-  tooltip.startTogglingOnHover(container, () => true);
+  tooltip.startTogglingOnHover(container,
+                               () => true,
+                               {},
+                               (target) => {
+                                 tooltip.show(target, {width, height}, tooltipContent);
+                               });
 
   info("Hover on each of the 4 boxes, expect the tooltip to appear");
   async function showAndCheck(boxId, position) {
     info(`Show tooltip on ${boxId}`);
     const box = doc.getElementById(boxId);
     const shown = tooltip.once("shown");
     EventUtils.synthesizeMouseAtCenter(box, { type: "mousemove" }, doc.defaultView);
     await shown;
--- a/devtools/client/shared/test/browser_html_tooltip_offset.js
+++ b/devtools/client/shared/test/browser_html_tooltip_offset.js
@@ -27,55 +27,54 @@ add_task(async function() {
   const box4 = doc.getElementById("box4");
 
   const tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
 
   const 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.");
-  await showTooltip(tooltip, box1, {x: 5, y: 10});
+  await showTooltip(tooltip, box1, {x: 5, y: 10, width: 50, height: 100}, div);
 
   let panelRect = tooltip.container.getBoundingClientRect();
   let anchorRect = box1.getBoundingClientRect();
 
   // Tooltip will be displayed below box1
   is(panelRect.top, anchorRect.bottom + 10, "Tooltip top has 10px offset");
   is(panelRect.left, anchorRect.left + 5, "Tooltip left has 5px offset");
   is(panelRect.height, 100, "Tooltip height is at 100px as expected");
 
   info("Display the tooltip on box2.");
-  await showTooltip(tooltip, box2, {x: 5, y: 10});
+  await showTooltip(tooltip, box2, {x: 5, y: 10, width: 50, height: 100}, div);
 
   panelRect = tooltip.container.getBoundingClientRect();
   anchorRect = box2.getBoundingClientRect();
 
   // Tooltip will be displayed below box2, but can't be fully displayed because of the
   // offset
   is(panelRect.top, anchorRect.bottom + 10, "Tooltip top has 10px offset");
   is(panelRect.left, anchorRect.left + 5, "Tooltip left has 5px offset");
   is(panelRect.height, 90, "Tooltip height is only 90px");
 
   info("Display the tooltip on box3.");
-  await showTooltip(tooltip, box3, {x: 5, y: 10});
+  await showTooltip(tooltip, box3, {x: 5, y: 10, width: 50, height: 100}, div);
 
   panelRect = tooltip.container.getBoundingClientRect();
   anchorRect = box3.getBoundingClientRect();
 
   // Tooltip will be displayed above box3, but can't be fully displayed because of the
   // offset
   is(panelRect.bottom, anchorRect.top - 10, "Tooltip bottom is 10px above anchor");
   is(panelRect.left, anchorRect.left + 5, "Tooltip left has 5px offset");
   is(panelRect.height, 90, "Tooltip height is only 90px");
 
   info("Display the tooltip on box4.");
-  await showTooltip(tooltip, box4, {x: 5, y: 10});
+  await showTooltip(tooltip, box4, {x: 5, y: 10, width: 50, height: 100}, div);
 
   panelRect = tooltip.container.getBoundingClientRect();
   anchorRect = box4.getBoundingClientRect();
 
   // Tooltip will be displayed above box4
   is(panelRect.bottom, anchorRect.top - 10, "Tooltip bottom is 10px above anchor");
   is(panelRect.left, anchorRect.left + 5, "Tooltip left has 5px offset");
   is(panelRect.height, 100, "Tooltip height is at 100px as expected");
--- a/devtools/client/shared/test/browser_html_tooltip_resize.js
+++ b/devtools/client/shared/test/browser_html_tooltip_resize.js
@@ -23,21 +23,20 @@ add_task(async function() {
 
   info("Test resizing of a tooltip");
 
   const tooltip =
     new HTMLTooltip(doc, { useXulWrapper: true, type: "doorhanger" });
   const div = doc.createElementNS(HTML_NS, "div");
   div.textContent = "tooltip";
   div.style.cssText = "width: 100px; height: 40px";
-  tooltip.setContent(div);
 
   const box1 = doc.getElementById("box1");
 
-  await showTooltip(tooltip, box1, { position: "top" });
+  await showTooltip(tooltip, box1, { position: "top" }, div);
 
   // Get the original position of the panel and arrow.
   const originalPanelBounds =
     tooltip.panel.getBoxQuads({ relativeTo: doc })[0].getBounds();
   const originalArrowBounds =
     tooltip.arrow.getBoxQuads({ relativeTo: doc })[0].getBounds();
 
   // Resize the content
--- a/devtools/client/shared/test/browser_html_tooltip_rtl.js
+++ b/devtools/client/shared/test/browser_html_tooltip_rtl.js
@@ -25,28 +25,27 @@ add_task(async function() {
   const [,, doc] = await createHost("right", TEST_URI);
 
   info("Test the positioning of tooltips in RTL and LTR directions");
 
   const tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
   const div = doc.createElementNS(HTML_NS, "div");
   div.textContent = "tooltip";
   div.style.cssText = "box-sizing: border-box; border: 1px solid black";
-  tooltip.setContent(div, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
 
-  await testRtlAnchors(doc, tooltip);
-  await testLtrAnchors(doc, tooltip);
+  await testRtlAnchors(doc, tooltip, div);
+  await testLtrAnchors(doc, tooltip, div);
   await hideTooltip(tooltip);
 
   tooltip.destroy();
 
   await testRtlArrow(doc);
 });
 
-async function testRtlAnchors(doc, tooltip) {
+async function testRtlAnchors(doc, tooltip, content) {
   /*
    * The layout of the test page is as follows:
    *   _______________________________
    *  | toolbox                       |
    *  | _____   _____   _____   _____ |
    *  ||     | |     | |     | |     ||
    *  || box1| | box2| | box3| | box4||
    *  ||_____| |_____| |_____| |_____||
@@ -56,40 +55,46 @@ async function testRtlAnchors(doc, toolt
    * - box2 is displayed right after box1
    * - total toolbox width is 500px so each box is 125px wide
   */
 
   const box1 = doc.getElementById("box1");
   const box2 = doc.getElementById("box2");
 
   info("Display the tooltip on box1.");
-  await showTooltip(tooltip, box1, {position: "bottom"});
+  await showTooltip(tooltip,
+                    box1,
+                    { position: "bottom", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT },
+                    content);
 
   let panelRect = tooltip.container.getBoundingClientRect();
   let anchorRect = box1.getBoundingClientRect();
 
   // box1 uses RTL direction, so the tooltip should be aligned with the right edge of the
   // anchor, but it is shifted to the right to fit in the toolbox.
   is(panelRect.left, 0, "Tooltip is aligned with left edge of the toolbox");
   is(panelRect.top, anchorRect.bottom, "Tooltip aligned with the anchor bottom edge");
   is(panelRect.height, TOOLTIP_HEIGHT, "Tooltip height is at 100px as expected");
 
   info("Display the tooltip on box2.");
-  await showTooltip(tooltip, box2, {position: "bottom"});
+  await showTooltip(tooltip,
+                    box2,
+                    { position: "bottom", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT },
+                    content);
 
   panelRect = tooltip.container.getBoundingClientRect();
   anchorRect = box2.getBoundingClientRect();
 
   // box2 uses RTL direction, so the tooltip is aligned with the right edge of the anchor
   is(panelRect.right, anchorRect.right, "Tooltip is aligned with right edge of anchor");
   is(panelRect.top, anchorRect.bottom, "Tooltip aligned with the anchor bottom edge");
   is(panelRect.height, TOOLTIP_HEIGHT, "Tooltip height is at 100px as expected");
 }
 
-async function testLtrAnchors(doc, tooltip) {
+async function testLtrAnchors(doc, tooltip, content) {
     /*
    * The layout of the test page is as follows:
    *   _______________________________
    *  | toolbox                       |
    *  | _____   _____   _____   _____ |
    *  ||     | |     | |     | |     ||
    *  || box1| | box2| | box3| | box4||
    *  ||_____| |_____| |_____| |_____||
@@ -99,28 +104,34 @@ async function testLtrAnchors(doc, toolt
    * - box4 is aligned with the right edge of the toolbox
    * - total toolbox width is 500px so each box is 125px wide
   */
 
   const box3 = doc.getElementById("box3");
   const box4 = doc.getElementById("box4");
 
   info("Display the tooltip on box3.");
-  await showTooltip(tooltip, box3, {position: "bottom"});
+  await showTooltip(tooltip,
+                    box3,
+                    {position: "bottom", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT},
+                    content);
 
   let panelRect = tooltip.container.getBoundingClientRect();
   let anchorRect = box3.getBoundingClientRect();
 
   // box3 uses LTR direction, so the tooltip is aligned with the left edge of the anchor.
   is(panelRect.left, anchorRect.left, "Tooltip is aligned with left edge of anchor");
   is(panelRect.top, anchorRect.bottom, "Tooltip aligned with the anchor bottom edge");
   is(panelRect.height, TOOLTIP_HEIGHT, "Tooltip height is at 100px as expected");
 
   info("Display the tooltip on box4.");
-  await showTooltip(tooltip, box4, {position: "bottom"});
+  await showTooltip(tooltip,
+                    box4,
+                    {position: "bottom", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT},
+                    content);
 
   panelRect = tooltip.container.getBoundingClientRect();
   anchorRect = box4.getBoundingClientRect();
 
   // box4 uses LTR direction, so the tooltip should be aligned with the left edge of the
   // anchor, but it is shifted to the left to fit in the toolbox.
   is(panelRect.right, TOOLBOX_WIDTH, "Tooltip is aligned with right edge of toolbox");
   is(panelRect.top, anchorRect.bottom, "Tooltip aligned with the anchor bottom edge");
@@ -129,27 +140,26 @@ async function testLtrAnchors(doc, toolt
 
 async function testRtlArrow(doc) {
   // Set up the arrow-style tooltip
   const arrowTooltip =
     new HTMLTooltip(doc, { type: "arrow", useXulWrapper: false });
   const div = doc.createElementNS(HTML_NS, "div");
   div.textContent = "tooltip";
   div.style.cssText = "box-sizing: border-box; border: 1px solid black";
-  arrowTooltip.setContent(div, {
-    width: TOOLTIP_WIDTH,
-    height: TOOLTIP_HEIGHT,
-  });
 
   // box2 uses RTL direction and is far enough from the edge that the arrow
   // should not be squashed in the wrong direction.
   const box2 = doc.getElementById("box2");
 
   info("Display the arrow tooltip on box2.");
-  await showTooltip(arrowTooltip, box2, { position: "top" });
+  await showTooltip(arrowTooltip,
+                    box2,
+                    { position: "top", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT },
+                    div);
 
   const arrow = arrowTooltip.arrow;
   ok(arrow, "Tooltip has an arrow");
 
   const panelRect = arrowTooltip.container.getBoundingClientRect();
   const arrowRect = arrow.getBoundingClientRect();
 
   // The arrow should be offset from the right edge, but still closer to the
--- a/devtools/client/shared/test/browser_html_tooltip_variable-height.js
+++ b/devtools/client/shared/test/browser_html_tooltip_variable-height.js
@@ -24,34 +24,40 @@ add_task(async function() {
 
   await addTab("about:blank");
   const [,, doc] = await createHost("bottom", TEST_URI);
 
   const tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
   info("Set tooltip content 50px tall, but request a container 200px tall");
   const tooltipContent = doc.createElementNS(HTML_NS, "div");
   tooltipContent.style.cssText = "height: " + TOOLTIP_HEIGHT + "px; background: red;";
-  tooltip.setContent(tooltipContent, {width: CONTAINER_WIDTH, height: Infinity});
 
   info("Show the tooltip and check the container and panel height.");
-  await showTooltip(tooltip, doc.getElementById("box1"));
+  await showTooltip(
+    tooltip,
+    doc.getElementById("box1"),
+    {width: CONTAINER_WIDTH, height: Infinity},
+    tooltipContent);
 
   const containerRect = tooltip.container.getBoundingClientRect();
   const panelRect = tooltip.panel.getBoundingClientRect();
   is(containerRect.height, CONTAINER_HEIGHT,
     "Tooltip container has the expected height.");
   is(panelRect.height, TOOLTIP_HEIGHT, "Tooltip panel has the expected height.");
 
   info("Click below the tooltip panel but in the tooltip filler element.");
   let onHidden = once(tooltip, "hidden");
   EventUtils.synthesizeMouse(tooltip.container, 100, 100, {}, doc.defaultView);
   await onHidden;
 
   info("Show the tooltip one more time, and increase the content height");
-  await showTooltip(tooltip, doc.getElementById("box1"));
+  await showTooltip(tooltip,
+                    doc.getElementById("box1"),
+                    {width: CONTAINER_WIDTH, height: Infinity},
+                    tooltipContent);
   tooltipContent.style.height = (2 * CONTAINER_HEIGHT) + "px";
 
   info("Click at the same coordinates as earlier, this time it should hit the tooltip.");
   const onPanelClick = once(tooltip.panel, "click");
   EventUtils.synthesizeMouse(tooltip.container, 100, 100, {}, doc.defaultView);
   await onPanelClick;
   is(tooltip.isVisible(), true, "Tooltip is still visible");
 
--- a/devtools/client/shared/test/browser_html_tooltip_width-auto.js
+++ b/devtools/client/shared/test/browser_html_tooltip_width-auto.js
@@ -31,20 +31,22 @@ add_task(async function() {
 
 async function runTests(doc) {
   const tooltip = new HTMLTooltip(doc, {useXulWrapper});
   info("Create tooltip content width to 150px");
   const 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.");
-  await showTooltip(tooltip, doc.getElementById("box1"));
+  await showTooltip(tooltip,
+                    doc.getElementById("box1"),
+                    { width: "auto", height: 50 },
+                    tooltipContent);
 
   const panelRect = tooltip.panel.getBoundingClientRect();
   is(panelRect.width, 150, "Tooltip panel has the expected width.");
 
   await hideTooltip(tooltip);
 
   tooltip.destroy();
 }
--- a/devtools/client/shared/test/browser_html_tooltip_xul-wrapper.js
+++ b/devtools/client/shared/test/browser_html_tooltip_xul-wrapper.js
@@ -40,29 +40,34 @@ add_task(async function() {
     win.top.moveTo(originalTop, originalLeft);
   });
 
   info("Create HTML tooltip");
   const tooltip = new HTMLTooltip(doc, {useXulWrapper: true});
   const div = doc.createElementNS(HTML_NS, "div");
   div.style.height = "200px";
   div.style.background = "red";
-  tooltip.setContent(div, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
 
   const box1 = doc.getElementById("box1");
 
   // Above box1: check that the tooltip can overflow onto the content page.
   info("Display the tooltip above box1.");
-  await showTooltip(tooltip, box1, {position: "top"});
+  await showTooltip(tooltip,
+                    box1,
+                    { position: "top", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT },
+                    div);
   checkTooltip(tooltip, "top", TOOLTIP_HEIGHT);
   await hideTooltip(tooltip);
 
   // Below box1: check that the tooltip can overflow out of the browser window.
   info("Display the tooltip below box1.");
-  await showTooltip(tooltip, box1, {position: "bottom"});
+  await showTooltip(tooltip,
+                    box1,
+                    { position: "bottom", width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT },
+                    div);
   checkTooltip(tooltip, "bottom", TOOLTIP_HEIGHT);
   await hideTooltip(tooltip);
 
   is(tooltip.isVisible(), false, "Tooltip is not visible");
 
   tooltip.destroy();
 });
 
--- a/devtools/client/shared/test/browser_html_tooltip_zoom.js
+++ b/devtools/client/shared/test/browser_html_tooltip_zoom.js
@@ -28,23 +28,24 @@ add_task(async function() {
   await pushPref("devtools.toolbox.zoomValue", zoom.toString(10));
 
   // Change this xul zoom to the x1.5 since this test doesn't use the toolbox preferences.
   const contentViewer = host.frame.docShell.contentViewer;
   contentViewer.fullZoom = zoom;
   const tooltip = new HTMLTooltip(doc, {useXulWrapper: true});
 
   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.");
   const onShown = tooltip.once("shown");
-  tooltip.show(doc.getElementById("box1"));
+  tooltip.show(doc.getElementById("box1"),
+               { width: 100, height: 50 },
+               getTooltipContent(doc));
   await onShown;
 
   const menuRect = doc.querySelector(".tooltip-xul-wrapper")
                       .getBoxQuads({relativeTo: doc})[0]
                       .getBounds();
   const anchorRect = doc.getElementById("box1")
                         .getBoxQuads({relativeTo: doc})[0]
                         .getBounds();
--- a/devtools/client/shared/test/helper_html_tooltip.js
+++ b/devtools/client/shared/test/helper_html_tooltip.js
@@ -15,19 +15,22 @@
  * @param {HTMLTooltip} tooltip
  *        The tooltip instance to display
  * @param {Node} anchor
  *        The anchor that should be used to display the tooltip
  * @param {Object} see HTMLTooltip:show documentation
  * @return {Promise} promise that resolves when "shown" has been fired, reflow
  *         and repaint done.
  */
-async function showTooltip(tooltip, anchor, {position, x, y} = {}) {
+async function showTooltip(tooltip,
+                           anchor,
+                           {position, x, y, width, height} = {},
+                           content) {
   const onShown = tooltip.once("shown");
-  tooltip.show(anchor, {position, x, y});
+  tooltip.show(anchor, {position, x, y, width, height}, content);
   await onShown;
   return waitForReflow(tooltip);
 }
 
 /**
  * Hide an existing HTMLTooltip. After the tooltip "hidden" event has been fired
  * a reflow will be triggered.
  *
--- a/devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
+++ b/devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
@@ -24,17 +24,17 @@ const CONTAINER_WIDTH = 500;
  *        The tooltip instance on which the event details content should be set
  * @param {Array} eventListenerInfos
  *        A list of event listeners
  * @param {Toolbox} toolbox
  *        Toolbox used to select debugger panel
  */
 function setEventTooltip(tooltip, eventListenerInfos, toolbox) {
   const eventTooltip = new EventTooltip(tooltip, eventListenerInfos, toolbox);
-  eventTooltip.init();
+  return eventTooltip.init();
 }
 
 function EventTooltip(tooltip, eventListenerInfos, toolbox) {
   this._tooltip = tooltip;
   this._eventListenerInfos = eventListenerInfos;
   this._toolbox = toolbox;
   this._eventEditors = new WeakMap();
 
@@ -192,21 +192,26 @@ EventTooltip.prototype = {
       });
 
       content.className = "event-tooltip-content-box";
       this.container.appendChild(content);
 
       this._addContentListeners(header);
     }
 
-    this._tooltip.setContent(
-      this.container,
-      {width: CONTAINER_WIDTH, height: Infinity}
-    );
+//    this._tooltip.setContent(
+//      this.container,
+//      {width: CONTAINER_WIDTH, height: Infinity}
+//    );
     this._tooltip.on("hidden", this.destroy);
+
+    return {
+      content: this.container,
+      option: { width: CONTAINER_WIDTH, height: Infinity}
+           };
   },
 
   _addContentListeners: function(header) {
     header.addEventListener("click", this._headerClicked);
   },
 
   _headerClicked: function(event) {
     if (event.target.classList.contains("event-tooltip-debugger-icon")) {
--- a/devtools/client/shared/widgets/tooltip/HTMLTooltip.js
+++ b/devtools/client/shared/widgets/tooltip/HTMLTooltip.js
@@ -311,18 +311,19 @@ function HTMLTooltip(toolboxDoc, {
   EventEmitter.decorate(this);
 
   this.doc = toolboxDoc;
   this.id = id;
   this.className = className;
   this.type = type;
   this.consumeOutsideClicks = consumeOutsideClicks;
   this.useXulWrapper = this._isXUL() && useXulWrapper;
-  this.preferredWidth = "auto";
-  this.preferredHeight = "auto";
+  // T.B.D Remove this definition.
+  //  this.preferredWidth = "auto";
+  //  this.preferredHeight = "auto";
 
   // The top window is used to attach click event listeners to close the tooltip if the
   // user clicks on the content page.
   this.topWindow = this._getTopWindow();
 
   this._position = null;
 
   this._onClick = this._onClick.bind(this);
@@ -399,23 +400,25 @@ HTMLTooltip.prototype = {
    *          For tooltips whose content height may change while being
    *          displayed, the special value Infinity may be used to produce
    *          a flexible container that accommodates resizing content. Note,
    *          however, that when used in combination with the XUL wrapper the
    *          unfilled part of this container will consume all mouse events
    *          making content behind this area inaccessible until the tooltip is
    *          dismissed.
    */
+  /*
   setContent: function(content, {width = "auto", height = "auto"} = {}) {
     this.preferredWidth = width;
     this.preferredHeight = height;
 
     this.panel.innerHTML = "";
     this.panel.appendChild(content);
   },
+  */
 
   /**
    * Show the tooltip next to the provided anchor element. A preferred position
    * can be set. The event "shown" will be fired after the tooltip is displayed.
    *
    * @param {Element} anchor
    *        The reference element with which the tooltip should be aligned
    * @param {Object} options
@@ -424,18 +427,31 @@ HTMLTooltip.prototype = {
    *        Optional, possible values: top|bottom
    *        If layout permits, the tooltip will be displayed on top/bottom
    *        of the anchor. If omitted, the tooltip will be displayed where
    *        more space is available.
    * @param {Number} options.x
    *        Optional, horizontal offset between the anchor and the tooltip.
    * @param {Number} options.y
    *        Optional, vertical offset between the anchor and the tooltip.
+   * @param {Number} option.width
+   *        Optional, width of the tooltip.
+   * @param {Number} option.height
+   *        Optional, height of the tooltip.
+   * @param {Object}
+   *        Require, target content
    */
-  async show(anchor, options) {
+  async show(anchor, options, content) {
+    if (content) {
+      this.panel.innerHTML = "";
+      this.panel.appendChild(content);
+      // flush
+      this.topWindow.getComputedStyle(content).width;
+    }
+
     const { left, top } = this._updateContainerBounds(anchor, options);
 
     if (this.useXulWrapper) {
       await this._showXulWrapperAt(left, top);
     } else {
       this.container.style.left = left + "px";
       this.container.style.top = top + "px";
     }
@@ -471,78 +487,78 @@ HTMLTooltip.prototype = {
     if (this.useXulWrapper) {
       this._moveXulWrapperTo(left, top);
     } else {
       this.container.style.left = left + "px";
       this.container.style.top = top + "px";
     }
   },
 
-  _updateContainerBounds(anchor, {position, x = 0, y = 0} = {}) {
+  _updateContainerBounds(anchor, {position, x = 0, y = 0, width = "auto", height = "auto"} = {}) {
     // Get anchor geometry
     let anchorRect = getRelativeRect(anchor, this.doc);
     if (this.useXulWrapper) {
       anchorRect = this._convertToScreenRect(anchorRect);
     }
 
     const { viewportRect, windowRect } = this._getBoundingRects();
 
     // Calculate the horizonal position and width
     let preferredWidth;
     // Record the height too since it might save us from having to look it up
     // later.
     let measuredHeight;
-    if (this.preferredWidth === "auto") {
+    if (width === "auto") {
       // Reset any styles that constrain the dimensions we want to calculate.
       this.container.style.width = "auto";
-      if (this.preferredHeight === "auto") {
+      if (height === "auto") {
         this.container.style.height = "auto";
       }
       ({
         width: preferredWidth,
         height: measuredHeight,
       } = this._measureContainerSize());
     } else {
       const themeWidth = 2 * EXTRA_BORDER[this.type];
-      preferredWidth = this.preferredWidth + themeWidth;
+      preferredWidth = width + themeWidth;
     }
 
     const anchorWin = anchor.ownerDocument.defaultView;
     const anchorCS = anchorWin.getComputedStyle(anchor);
     const isRtl = anchorCS.direction === "rtl";
 
     let borderRadius = 0;
     if (this.type === TYPE.DOORHANGER) {
       borderRadius = parseFloat(
         anchorCS.getPropertyValue("--theme-arrowpanel-border-radius")
       );
       if (Number.isNaN(borderRadius)) {
         borderRadius = 0;
       }
     }
 
-    const {left, width, arrowLeft} = calculateHorizontalPosition(
+    const {left, width: calculatedWidth, arrowLeft} = calculateHorizontalPosition(
       anchorRect,
       viewportRect,
       windowRect,
       preferredWidth,
       this.type,
       x,
       borderRadius,
       isRtl
     );
 
     // If we constrained the width, then any measured height we have is no
     // longer valid.
-    if (measuredHeight && width !== preferredWidth) {
+    if (measuredHeight && calculatedWidth !== preferredWidth) {
       measuredHeight = undefined;
     }
 
     // Apply width and arrow positioning
-    this.container.style.width = width + "px";
+    this.container.style.width = calculatedWidth + "px";
     if (this.type === TYPE.ARROW || this.type === TYPE.DOORHANGER) {
       this.arrow.style.left = arrowLeft + "px";
     }
 
     // Work out how much vertical margin we have.
     //
     // This relies on us having set either .tooltip-top or .tooltip-bottom
     // and on the margins for both being symmetrical. Fortunately the call to
@@ -551,46 +567,46 @@ HTMLTooltip.prototype = {
     const panelWindow = this.panel.ownerDocument.defaultView;
     const panelComputedStyle = panelWindow.getComputedStyle(this.panel);
     const verticalMargin =
       parseFloat(panelComputedStyle.marginTop) +
       parseFloat(panelComputedStyle.marginBottom);
 
     // Calculate the vertical position and height
     let preferredHeight;
-    if (this.preferredHeight === "auto") {
+    if (height === "auto") {
       if (measuredHeight) {
         this.container.style.height = "auto";
         preferredHeight = measuredHeight;
       } else {
         ({ height: preferredHeight } = this._measureContainerSize());
       }
       preferredHeight += verticalMargin;
     } else {
       const themeHeight =
         EXTRA_HEIGHT[this.type] +
         verticalMargin +
         2 * EXTRA_BORDER[this.type];
-      preferredHeight = this.preferredHeight + themeHeight;
+      preferredHeight = height + themeHeight;
     }
 
-    const {top, height, computedPosition} =
+    const {top, height: calculatedHeight, computedPosition} =
       calculateVerticalPosition(anchorRect, viewportRect, preferredHeight, position, y);
 
     this._position = computedPosition;
     const isTop = computedPosition === POSITION.TOP;
     this.container.classList.toggle("tooltip-top", isTop);
     this.container.classList.toggle("tooltip-bottom", !isTop);
 
     // If the preferred height is set to Infinity, the tooltip container should grow based
     // on its content's height and use as much height as possible.
     this.container.classList.toggle("tooltip-flexible-height",
-      this.preferredHeight === Infinity);
+      preferredHeight === Infinity);
 
-    this.container.style.height = height + "px";
+    this.container.style.height = calculatedHeight + "px";
 
     return { left, top };
   },
 
   /**
    * Calculate the following boundary rectangles:
    *
    * - Viewport rect: This is the region that limits the tooltip dimensions.
--- a/devtools/client/shared/widgets/tooltip/ImageTooltipHelper.js
+++ b/devtools/client/shared/widgets/tooltip/ImageTooltipHelper.js
@@ -114,16 +114,76 @@ function setImageTooltip(tooltip, doc, i
   if (!hideDimensionLabel) {
     height += LABEL_HEIGHT;
   }
   const width = Math.max(CONTAINER_MIN_WIDTH, imgWidth + 2 * IMAGE_PADDING);
 
   tooltip.setContent(div, {width, height});
 }
 
+function getImageTooltip(tooltip, doc, imageUrl, options) {
+  let {naturalWidth, naturalHeight, hideDimensionLabel,
+       hideCheckeredBackground, maxDim} = options;
+  maxDim = maxDim || MAX_DIMENSION;
+
+  let imgHeight = naturalHeight;
+  let imgWidth = naturalWidth;
+  if (imgHeight > maxDim || imgWidth > maxDim) {
+    const scale = maxDim / Math.max(imgHeight, imgWidth);
+    // Only allow integer values to avoid rounding errors.
+    imgHeight = Math.floor(scale * naturalHeight);
+    imgWidth = Math.ceil(scale * naturalWidth);
+  }
+
+  let imageClass = "";
+  if (!hideCheckeredBackground) {
+    imageClass = "devtools-tooltip-tiles";
+  }
+
+  // Create tooltip content
+  const div = doc.createElementNS(XHTML_NS, "div");
+  div.style.cssText = `
+    height: 100%;
+    min-width: 100px;
+    display: flex;
+    flex-direction: column;
+    text-align: center;`;
+  let html = `
+    <div style="flex: 1;
+                display: flex;
+                padding: ${IMAGE_PADDING}px;
+                align-items: center;
+                justify-content: center;
+                min-height: 1px;">
+      <img class="${imageClass}"
+           style="height: ${imgHeight}px; max-height: 100%;"
+           src="${encodeURI(imageUrl)}"/>
+    </div>`;
+
+  if (!hideDimensionLabel) {
+    const label = naturalWidth + " \u00D7 " + naturalHeight;
+    html += `
+      <div style="height: ${LABEL_HEIGHT}px;
+                  text-align: center;">
+        <span class="theme-comment devtools-tooltip-caption">${label}</span>
+      </div>`;
+  }
+  // eslint-disable-next-line no-unsanitized/property
+  div.innerHTML = html;
+
+  // Calculate tooltip dimensions
+  let height = imgHeight + 2 * IMAGE_PADDING;
+  if (!hideDimensionLabel) {
+    height += LABEL_HEIGHT;
+  }
+  const width = Math.max(CONTAINER_MIN_WIDTH, imgWidth + 2 * IMAGE_PADDING);
+
+  return { content: div, option: { width, height } };
+}
+
 /*
  * Set the tooltip content of a provided HTMLTooltip instance to display a
  * fallback error message when an image preview tooltip can not be displayed.
  *
  * @param {HTMLTooltip} tooltip
  *        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
@@ -131,11 +191,21 @@ function setImageTooltip(tooltip, doc, i
 function setBrokenImageTooltip(tooltip, doc) {
   const div = doc.createElementNS(XHTML_NS, "div");
   div.className = "theme-comment devtools-tooltip-image-broken";
   const message = L10N.getStr("previewTooltip.image.brokenImage");
   div.textContent = message;
   tooltip.setContent(div);
 }
 
+function getBrokenImageTooltip(tooltip, doc) {
+  const div = doc.createElementNS(XHTML_NS, "div");
+  div.className = "theme-comment devtools-tooltip-image-broken";
+  const message = L10N.getStr("previewTooltip.image.brokenImage");
+  div.textContent = message;
+  return { content: div, option: {} };
+}
+
 module.exports.getImageDimensions = getImageDimensions;
 module.exports.setImageTooltip = setImageTooltip;
+module.exports.getImageTooltip = getImageTooltip;
 module.exports.setBrokenImageTooltip = setBrokenImageTooltip;
+module.exports.getBrokenImageTooltip = getBrokenImageTooltip;
--- a/devtools/client/shared/widgets/tooltip/SwatchBasedEditorTooltip.js
+++ b/devtools/client/shared/widgets/tooltip/SwatchBasedEditorTooltip.js
@@ -71,16 +71,19 @@ class SwatchBasedEditorTooltip {
     this.swatches = new Map();
 
     // When a swatch is clicked, and for as long as the tooltip is shown, the
     // activeSwatch property will hold the reference to the swatch DOM element
     // that was clicked
     this.activeSwatch = null;
 
     this._onSwatchClick = this._onSwatchClick.bind(this);
+
+    this._tooltipContent = null;
+    this._tooltipOption = null;
   }
 
  /**
    * Reports if the tooltip is currently shown
    *
    * @return {Boolean} True if the tooltip is displayed.
    */
   isVisible() {
@@ -105,17 +108,21 @@ class SwatchBasedEditorTooltip {
   show() {
     const tooltipAnchor = this.useInline ?
       this.activeSwatch.closest(`.${INLINE_TOOLTIP_CLASS}`) :
       this.activeSwatch;
 
     if (tooltipAnchor) {
       const onShown = this.tooltip.once("shown");
 
-      this.tooltip.show(tooltipAnchor, "topcenter bottomleft");
+      this.tooltip.show(tooltipAnchor,
+                        !this._tooltipOption
+                        ? "topcenter bottomleft"
+                        : Object.assign({ position: "topcenter bottomleft" }, this._tooltipOption),
+                       this._tooltipContent);
       this.tooltip.once("hidden", () => this.onTooltipHidden());
 
       return onShown;
     }
 
     return Promise.resolve();
   }
 
--- a/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
+++ b/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
@@ -55,17 +55,19 @@ class SwatchColorPickerTooltip extends S
     const container = doc.createElementNS(XHTML_NS, "div");
     container.id = "spectrum-tooltip";
 
     const node = doc.createElementNS(XHTML_NS, "div");
     node.id = "spectrum";
     container.appendChild(node);
 
     const widget = new Spectrum(node, color);
-    this.tooltip.setContent(container, { width: 218, height: 224 });
+    this._tooltipContent = container;
+    this._tooltipOption = { width: 218, height: 224 };
+//    this.tooltip.setContent(container, { width: 218, height: 224 });
 
     widget.inspector = this.inspector;
 
     const eyedropper = doc.createElementNS(XHTML_NS, "button");
     eyedropper.id = "eyedropper-button";
     eyedropper.className = "devtools-button";
     /* pointerEvents for eyedropper has to be set auto to display tooltip when
      * eyedropper is disabled in non-HTML documents.
--- a/devtools/client/shared/widgets/tooltip/SwatchCubicBezierTooltip.js
+++ b/devtools/client/shared/widgets/tooltip/SwatchCubicBezierTooltip.js
@@ -40,17 +40,19 @@ class SwatchCubicBezierTooltip extends S
    */
 
   setCubicBezierContent(bezier) {
     const { doc } = this.tooltip;
 
     const container = doc.createElementNS(XHTML_NS, "div");
     container.className = "cubic-bezier-container";
 
-    this.tooltip.setContent(container, { width: 510, height: 370 });
+//    this.tooltip.setContent(container, { width: 510, height: 370 });
+    this._tooltipContent = container;
+    this._tooltipOption = { width: 510, height:370 };
 
     const def = defer();
 
     // Wait for the tooltip to be shown before calling instanciating the widget
     // as it expect its DOM elements to be visible.
     this.tooltip.once("shown", () => {
       const widget = new CubicBezierWidget(container, bezier);
       def.resolve(widget);
--- a/devtools/client/shared/widgets/tooltip/SwatchFilterTooltip.js
+++ b/devtools/client/shared/widgets/tooltip/SwatchFilterTooltip.js
@@ -42,17 +42,19 @@ class SwatchFilterTooltip extends Swatch
    */
 
   setFilterContent(filter) {
     const { doc } = this.tooltip;
 
     const container = doc.createElementNS(XHTML_NS, "div");
     container.id = "filter-container";
 
-    this.tooltip.setContent(container, { width: 510, height: 200 });
+//    this.tooltip.setContent(container, { width: 510, height: 200 });
+    this._tooltipContent = container;
+    this._tooltipOption = { width: 510, height: 200 };
 
     return new CSSFilterEditorWidget(container, filter, this._cssIsValid);
   }
 
   async show() {
     // Call the parent class' show function
     await super.show();
     // Then set the filter value and listen to changes to preview them
--- a/devtools/client/shared/widgets/tooltip/TooltipToggle.js
+++ b/devtools/client/shared/widgets/tooltip/TooltipToggle.js
@@ -53,38 +53,43 @@ TooltipToggle.prototype = {
    *        should be displayed. Possible return values are:
    *        - false (or a falsy value) if the tooltip should not be displayed
    *        - true if the tooltip should be displayed
    *        - a DOM node to display the tooltip on the returned anchor
    *        The function can also return a promise that will resolve to one of
    *        the values listed above.
    *        If omitted, the tooltip will be shown everytime.
    * @param {Object} options
-            Set of optional arguments:
+   *        Set of optional arguments:
    *        - {Number} toggleDelay
    *          An optional delay (in ms) that will be observed before showing
    *          and before hiding the tooltip. Defaults to DEFAULT_TOGGLE_DELAY.
    *        - {Boolean} interactive
    *          If enabled, the tooltip is not hidden when mouse leaves the
    *          target element and enters the tooltip. Allows the tooltip
    *          content to be interactive.
+   * @param {Function} onShowCb
+   *        A function to be invoked before showing the panel.
+   *        This function will be called with tooltip object parameter.
    */
   start: function(baseNode, targetNodeCb,
-                   {toggleDelay = DEFAULT_TOGGLE_DELAY, interactive = false} = {}) {
+                  {toggleDelay = DEFAULT_TOGGLE_DELAY, interactive = false} = {},
+                  onShowCb) {
     this.stop();
 
     if (!baseNode) {
       // Calling tool is in the process of being destroyed.
       return;
     }
 
     this._baseNode = baseNode;
     this._targetNodeCb = targetNodeCb || (() => true);
     this._toggleDelay = toggleDelay;
     this._interactive = interactive;
+    this._onShowCb = onShowCb;
 
     baseNode.addEventListener("mousemove", this._onMouseMove);
     baseNode.addEventListener("mouseout", this._onMouseOut);
 
     if (this._interactive) {
       this.tooltip.container.addEventListener("mouseover", this._onTooltipMouseOver);
       this.tooltip.container.addEventListener("mouseout", this._onTooltipMouseOut);
     }
@@ -122,17 +127,21 @@ TooltipToggle.prototype = {
       this.win.clearTimeout(this.toggleTimer);
       this.toggleTimer = this.win.setTimeout(() => {
         this.tooltip.hide();
         this.isValidHoverTarget(event.target).then(target => {
           if (target === null || !this._baseNode) {
             // bail out if no target or if the toggle has been destroyed.
             return;
           }
-          this.tooltip.show(target);
+          if (this._onShowCb) {
+            this._onShowCb(target, this.tooltip);
+          } else {
+            this.tooltip.show(target);
+          }
         }, reason => {
           console.error("isValidHoverTarget rejected with unexpected reason:");
           console.error(reason);
         });
       }, this._toggleDelay);
     }
   },