--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -30,21 +30,20 @@ const HTML_VOID_ELEMENTS = [ "area", "ba
"track", "wbr" ];
const {UndoStack} = require("devtools/client/shared/undo");
const {editableField, InplaceEditor} =
require("devtools/client/shared/inplace-editor");
const {HTMLEditor} = require("devtools/client/inspector/markup/html-editor");
const promise = require("promise");
const Services = require("Services");
-const {Tooltip} = require("devtools/client/shared/widgets/Tooltip");
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
const {setImageTooltip, setBrokenImageTooltip} =
require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
-
+const {setEventTooltip} = require("devtools/client/shared/widgets/tooltip/EventTooltipHelper");
const EventEmitter = require("devtools/shared/event-emitter");
const Heritage = require("sdk/core/heritage");
const {parseAttribute} =
require("devtools/client/shared/node-attribute-parser");
const {Task} = require("devtools/shared/task");
const {scrollIntoViewIfNeeded} = require("devtools/shared/layout/utils");
const {PrefObserver} = require("devtools/client/styleeditor/utils");
const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
@@ -169,17 +168,18 @@ MarkupView.prototype = {
/**
* How long does a node flash when it mutates (in ms).
*/
CONTAINER_FLASHING_DURATION: 500,
_selectedContainer: null,
_initTooltips: function () {
- this.eventDetailsTooltip = new Tooltip(this._inspector.panelDoc);
+ this.eventDetailsTooltip = new HTMLTooltip(this._inspector.toolbox,
+ {type: "arrow"});
this.imagePreviewTooltip = new HTMLTooltip(this._inspector.toolbox,
{type: "arrow"});
this._enableImagePreviewTooltip();
},
_enableImagePreviewTooltip: function () {
this.imagePreviewTooltip.startTogglingOnHover(this._elt,
this._isImagePreviewTarget);
@@ -2610,21 +2610,18 @@ function MarkupElementContainer(markupVi
}
MarkupElementContainer.prototype = Heritage.extend(MarkupContainer.prototype, {
_buildEventTooltipContent: function (target, tooltip) {
if (target.hasAttribute("data-event")) {
tooltip.hide(target);
this.node.getEventListenerInfo().then(listenerInfo => {
- tooltip.setEventContent({
- eventListenerInfos: listenerInfo,
- toolbox: this.markup._inspector.toolbox
- });
-
+ let toolbox = this.markup._inspector.toolbox;
+ 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();
});
tooltip.show(target);
});
--- a/devtools/client/inspector/markup/test/browser_markup_events-overflow.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events-overflow.js
@@ -38,17 +38,17 @@ add_task(function* () {
let tooltip = inspector.markup.eventDetailsTooltip;
info("Clicking to open event tooltip.");
EventUtils.synthesizeMouseAtCenter(evHolder, {},
inspector.markup.doc.defaultView);
yield tooltip.once("shown");
info("EventTooltip visible.");
- let container = tooltip.content;
+ let container = tooltip.panel;
let containerRect = container.getBoundingClientRect();
let headers = container.querySelectorAll(".event-header");
for (let data of TEST_DATA) {
info("Testing scrolling when " + data.desc);
if (data.initialScrollTop < 0) {
info("Scrolling container to the bottom.");
--- a/devtools/client/inspector/markup/test/helper_events_test_runner.js
+++ b/devtools/client/inspector/markup/test/helper_events_test_runner.js
@@ -65,42 +65,45 @@ function* checkEventsForNode(test, inspe
// Click button to show tooltip
info("Clicking evHolder");
EventUtils.synthesizeMouseAtCenter(evHolder, {},
inspector.markup.doc.defaultView);
yield tooltip.once("shown");
info("tooltip shown");
// Check values
- let headers = tooltip.content.querySelectorAll(".event-header");
+ let headers = tooltip.panel.querySelectorAll(".event-header");
let nodeFront = container.node;
let cssSelector = nodeFront.nodeName + "#" + nodeFront.id;
for (let i = 0; i < headers.length; i++) {
info("Processing header[" + i + "] for " + cssSelector);
let header = headers[i];
let type = header.querySelector(".event-tooltip-event-type");
let filename = header.querySelector(".event-tooltip-filename");
let attributes = header.querySelectorAll(".event-tooltip-attributes");
let contentBox = header.nextElementSibling;
- is(type.getAttribute("value"), expected[i].type,
+ is(type.textContent, expected[i].type,
"type matches for " + cssSelector);
- is(filename.getAttribute("value"), expected[i].filename,
+ is(filename.textContent, expected[i].filename,
"filename matches for " + cssSelector);
is(attributes.length, expected[i].attributes.length,
"we have the correct number of attributes");
for (let j = 0; j < expected[i].attributes.length; j++) {
- is(attributes[j].getAttribute("value"), expected[i].attributes[j],
+ is(attributes[j].textContent, expected[i].attributes[j],
"attribute[" + j + "] matches for " + cssSelector);
}
+ // Make sure the header is not hidden by scrollbars before clicking.
+ header.scrollIntoView();
+
EventUtils.synthesizeMouseAtCenter(header, {}, type.ownerGlobal);
yield tooltip.once("event-tooltip-ready");
let editor = tooltip.eventEditors.get(contentBox).editor;
is(editor.getText(), expected[i].handler,
"handler matches for " + cssSelector);
}
--- a/devtools/client/shared/widgets/HTMLTooltip.js
+++ b/devtools/client/shared/widgets/HTMLTooltip.js
@@ -105,24 +105,25 @@ HTMLTooltip.prototype = {
/**
* Set the tooltip content element. The preferred width/height should also be
* specified here.
*
* @param {Element} content
* The tooltip content, should be a HTML element.
* @param {Number} width
* Preferred width for the tooltip container
- * @param {Number} height
+ * @param {Number} height (optional)
* Preferred height for the tooltip container. If the content height is
* smaller than the container's height, the tooltip will automatically
- * shrink around the content.
+ * shrink around the content. If not specified, will use all the height
+ * available.
* @return {Promise} a promise that will resolve when the content has been
* added in the tooltip container.
*/
- setContent: function (content, width, height) {
+ setContent: function (content, width, height = Infinity) {
let themeHeight = EXTRA_HEIGHT[this.type] + 2 * EXTRA_BORDER[this.type];
let themeWidth = 2 * EXTRA_BORDER[this.type];
this.preferredWidth = width + themeWidth;
this.preferredHeight = height + themeHeight;
this.panel.innerHTML = "";
this.panel.appendChild(content);
--- a/devtools/client/shared/widgets/Tooltip.js
+++ b/devtools/client/shared/widgets/Tooltip.js
@@ -11,17 +11,16 @@ const {CubicBezierWidget} =
require("devtools/client/shared/widgets/CubicBezierWidget");
const {MdnDocsWidget} = require("devtools/client/shared/widgets/MdnDocsWidget");
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipToggle");
const EventEmitter = require("devtools/shared/event-emitter");
const {colorUtils} = require("devtools/client/shared/css-color");
const Heritage = require("sdk/core/heritage");
const {Eyedropper} = require("devtools/client/eyedropper/eyedropper");
-const Editor = require("devtools/client/sourceeditor/editor");
const Services = require("Services");
loader.lazyRequireGetter(this, "beautify", "devtools/shared/jsbeautify/beautify");
loader.lazyRequireGetter(this, "setNamedTimeout", "devtools/client/shared/widgets/view-helpers", true);
loader.lazyRequireGetter(this, "clearNamedTimeout", "devtools/client/shared/widgets/view-helpers", true);
loader.lazyRequireGetter(this, "setNamedTimeout", "devtools/client/shared/widgets/view-helpers", true);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@@ -430,29 +429,16 @@ Tooltip.prototype = {
hbox.appendChild(vbox);
this.content = hbox;
} else {
this.content = vbox;
}
},
/**
- * Sets some event listener info as the content of this tooltip.
- *
- * @param {Object} (destructuring assignment)
- * @0 {array} eventListenerInfos
- * A list of event listeners.
- * @1 {toolbox} toolbox
- * Toolbox used to select debugger panel.
- */
- setEventContent: function ({ eventListenerInfos, toolbox }) {
- new EventTooltip(this, eventListenerInfos, toolbox);
- },
-
- /**
* Fill the tooltip with a variables view, inspecting an object via its
* corresponding object actor, as specified in the remote debugging protocol.
*
* @param {object} objectActor
* The value grip for the object actor.
* @param {object} viewOptions [optional]
* Options for the variables view visualization.
* @param {object} controllerOptions [optional]
@@ -1061,308 +1047,16 @@ Heritage.extend(SwatchBasedEditorTooltip
this.currentSwatchColor = null;
this.spectrum.then(spectrum => {
spectrum.off("changed", this._onSpectrumColorChange);
spectrum.destroy();
});
}
});
-function EventTooltip(tooltip, eventListenerInfos, toolbox) {
- this._tooltip = tooltip;
- this._eventListenerInfos = eventListenerInfos;
- this._toolbox = toolbox;
- this._tooltip.eventEditors = new WeakMap();
-
- this._headerClicked = this._headerClicked.bind(this);
- this._debugClicked = this._debugClicked.bind(this);
- this.destroy = this.destroy.bind(this);
-
- this._init();
-}
-
-EventTooltip.prototype = {
- _init: function () {
- let config = {
- mode: Editor.modes.js,
- lineNumbers: false,
- lineWrapping: false,
- readOnly: true,
- styleActiveLine: true,
- extraKeys: {},
- theme: "mozilla markup-view"
- };
-
- let doc = this._tooltip.doc;
- let container = doc.createElement("vbox");
- container.setAttribute("id", "devtools-tooltip-events-container");
-
- for (let listener of this._eventListenerInfos) {
- let phase = listener.capturing ? "Capturing" : "Bubbling";
- let level = listener.DOM0 ? "DOM0" : "DOM2";
-
- // Header
- let header = doc.createElement("hbox");
- header.className = "event-header devtools-toolbar";
- container.appendChild(header);
-
- if (!listener.hide.debugger) {
- let debuggerIcon = doc.createElement("image");
- debuggerIcon.className = "event-tooltip-debugger-icon";
- debuggerIcon.setAttribute("src", "chrome://devtools/skin/images/tool-debugger.svg");
- let openInDebugger =
- l10n.strings.GetStringFromName("eventsTooltip.openInDebugger");
- debuggerIcon.setAttribute("tooltiptext", openInDebugger);
- header.appendChild(debuggerIcon);
- }
-
- if (!listener.hide.type) {
- let eventTypeLabel = doc.createElement("label");
- eventTypeLabel.className = "event-tooltip-event-type";
- eventTypeLabel.setAttribute("value", listener.type);
- eventTypeLabel.setAttribute("tooltiptext", listener.type);
- header.appendChild(eventTypeLabel);
- }
-
- if (!listener.hide.filename) {
- let filename = doc.createElement("label");
- filename.className = "event-tooltip-filename devtools-monospace";
- filename.setAttribute("value", listener.origin);
- filename.setAttribute("tooltiptext", listener.origin);
- filename.setAttribute("crop", "left");
- header.appendChild(filename);
- }
-
- let attributesContainer = doc.createElement("hbox");
- attributesContainer.setAttribute("class",
- "event-tooltip-attributes-container");
- header.appendChild(attributesContainer);
-
- if (!listener.hide.capturing) {
- let attributesBox = doc.createElement("box");
- attributesBox.setAttribute("class", "event-tooltip-attributes-box");
- attributesContainer.appendChild(attributesBox);
-
- let capturing = doc.createElement("label");
- capturing.className = "event-tooltip-attributes";
- capturing.setAttribute("value", phase);
- capturing.setAttribute("tooltiptext", phase);
- attributesBox.appendChild(capturing);
- }
-
- if (listener.tags) {
- for (let tag of listener.tags.split(",")) {
- let attributesBox = doc.createElement("box");
- attributesBox.setAttribute("class", "event-tooltip-attributes-box");
- attributesContainer.appendChild(attributesBox);
-
- let tagBox = doc.createElement("label");
- tagBox.className = "event-tooltip-attributes";
- tagBox.setAttribute("value", tag);
- tagBox.setAttribute("tooltiptext", tag);
- attributesBox.appendChild(tagBox);
- }
- }
-
- if (!listener.hide.dom0) {
- let attributesBox = doc.createElement("box");
- attributesBox.setAttribute("class", "event-tooltip-attributes-box");
- attributesContainer.appendChild(attributesBox);
-
- let dom0 = doc.createElement("label");
- dom0.className = "event-tooltip-attributes";
- dom0.setAttribute("value", level);
- dom0.setAttribute("tooltiptext", level);
- attributesBox.appendChild(dom0);
- }
-
- // Content
- let content = doc.createElement("box");
- let editor = new Editor(config);
- this._tooltip.eventEditors.set(content, {
- editor: editor,
- handler: listener.handler,
- searchString: listener.searchString,
- uri: listener.origin,
- dom0: listener.DOM0,
- appended: false
- });
-
- content.className = "event-tooltip-content-box";
- container.appendChild(content);
-
- this._addContentListeners(header);
- }
-
- this._tooltip.content = container;
- this._tooltip.panel.setAttribute("clamped-dimensions-no-max-or-min-height",
- "");
- this._tooltip.panel.setAttribute("wide", "");
-
- this._tooltip.panel.addEventListener("popuphiding", () => {
- this.destroy(container);
- }, false);
- },
-
- _addContentListeners: function (header) {
- header.addEventListener("click", this._headerClicked);
- },
-
- _headerClicked: function (event) {
- if (event.target.classList.contains("event-tooltip-debugger-icon")) {
- this._debugClicked(event);
- event.stopPropagation();
- return;
- }
-
- let doc = this._tooltip.doc;
- let header = event.currentTarget;
- let content = header.nextElementSibling;
-
- if (content.hasAttribute("open")) {
- content.removeAttribute("open");
- } else {
- let contentNodes = doc.querySelectorAll(".event-tooltip-content-box");
-
- for (let node of contentNodes) {
- if (node !== content) {
- node.removeAttribute("open");
- }
- }
-
- content.setAttribute("open", "");
-
- let eventEditors = this._tooltip.eventEditors.get(content);
-
- if (eventEditors.appended) {
- return;
- }
-
- let {editor, handler} = eventEditors;
-
- let iframe = doc.createElement("iframe");
- iframe.setAttribute("style", "width:100%;");
-
- editor.appendTo(content, iframe).then(() => {
- /* eslint-disable camelcase */
- let tidied = beautify.js(handler, { indent_size: 2 });
- /* eslint-enable */
- editor.setText(tidied);
-
- eventEditors.appended = true;
-
- let container = header.parentElement.getBoundingClientRect();
- if (header.getBoundingClientRect().top < container.top) {
- header.scrollIntoView(true);
- } else if (content.getBoundingClientRect().bottom > container.bottom) {
- content.scrollIntoView(false);
- }
-
- this._tooltip.emit("event-tooltip-ready");
- });
- }
- },
-
- _debugClicked: function (event) {
- let header = event.currentTarget;
- let content = header.nextElementSibling;
-
- let {uri, searchString, dom0} =
- this._tooltip.eventEditors.get(content);
-
- if (uri && uri !== "?") {
- // Save a copy of toolbox as it will be set to null when we hide the
- // tooltip.
- let toolbox = this._toolbox;
-
- this._tooltip.hide();
-
- uri = uri.replace(/"/g, "");
-
- let showSource = ({ DebuggerView }) => {
- let matches = uri.match(/(.*):(\d+$)/);
- let line = 1;
-
- if (matches) {
- uri = matches[1];
- line = matches[2];
- }
-
- let item = DebuggerView.Sources.getItemForAttachment(
- a => a.source.url === uri
- );
- if (item) {
- let actor = item.attachment.source.actor;
- DebuggerView.setEditorLocation(
- actor, line, {noDebug: true}
- ).then(() => {
- if (dom0) {
- let text = DebuggerView.editor.getText();
- let index = text.indexOf(searchString);
- let lastIndex = text.lastIndexOf(searchString);
-
- // To avoid confusion we only search for DOM0 event handlers when
- // there is only one possible match in the file.
- if (index !== -1 && index === lastIndex) {
- text = text.substr(0, index);
- let newlineMatches = text.match(/\n/g);
-
- if (newlineMatches) {
- DebuggerView.editor.setCursor({
- line: newlineMatches.length
- });
- }
- }
- }
- });
- }
- };
-
- let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger");
- toolbox.selectTool("jsdebugger").then(({ panelWin: dbg }) => {
- if (debuggerAlreadyOpen) {
- showSource(dbg);
- } else {
- dbg.once(dbg.EVENTS.SOURCES_ADDED, () => showSource(dbg));
- }
- });
- }
- },
-
- destroy: function (container) {
- if (this._tooltip) {
- this._tooltip.panel.removeEventListener("popuphiding", this.destroy,
- false);
-
- let boxes = container.querySelectorAll(".event-tooltip-content-box");
-
- for (let box of boxes) {
- let {editor} = this._tooltip.eventEditors.get(box);
- editor.destroy();
- }
-
- this._tooltip.eventEditors = null;
- }
-
- let headerNodes = container.querySelectorAll(".event-header");
-
- for (let node of headerNodes) {
- node.removeEventListener("click", this._headerClicked);
- }
-
- let sourceNodes =
- container.querySelectorAll(".event-tooltip-debugger-icon");
- for (let node of sourceNodes) {
- node.removeEventListener("click", this._debugClicked);
- }
-
- this._eventListenerInfos = this._toolbox = this._tooltip = null;
- }
-};
-
/**
* The swatch cubic-bezier tooltip class is a specific class meant to be used
* along with rule-view's generated cubic-bezier swatches.
* It extends the parent SwatchBasedEditorTooltip class.
* It just wraps a standard Tooltip and sets its content with an instance of a
* CubicBezierWidget.
*
* @param {XULDocument} doc
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
@@ -0,0 +1,315 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript 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";
+
+const Services = require("Services");
+loader.lazyGetter(this, "GetStringFromName", () => {
+ let bundle = Services.strings.createBundle(
+ "chrome://devtools/locale/inspector.properties");
+ return key => {
+ return bundle.GetStringFromName(key);
+ };
+});
+
+loader.lazyRequireGetter(this, "Editor", "devtools/client/sourceeditor/editor");
+loader.lazyRequireGetter(this, "beautify", "devtools/shared/jsbeautify/beautify");
+
+const XHTML_NS = "http://www.w3.org/1999/xhtml";
+const CONTAINER_WIDTH = 500;
+
+/**
+ * Set the content of a provided HTMLTooltip instance to display a list of event
+ * listeners, with their event type, capturing argument and a link to the code
+ * of the event handler.
+ *
+ * @param {HTMLTooltip} tooltip
+ * 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) {
+ let eventTooltip = new EventTooltip(tooltip, eventListenerInfos, toolbox);
+ eventTooltip.init();
+}
+
+function EventTooltip(tooltip, eventListenerInfos, toolbox) {
+ this._tooltip = tooltip;
+ this._eventListenerInfos = eventListenerInfos;
+ this._toolbox = toolbox;
+ this._tooltip.eventEditors = new WeakMap();
+
+ this._headerClicked = this._headerClicked.bind(this);
+ this._debugClicked = this._debugClicked.bind(this);
+ this.destroy = this.destroy.bind(this);
+}
+
+EventTooltip.prototype = {
+ init: function () {
+ let config = {
+ mode: Editor.modes.js,
+ lineNumbers: false,
+ lineWrapping: true,
+ readOnly: true,
+ styleActiveLine: true,
+ extraKeys: {},
+ theme: "mozilla markup-view"
+ };
+
+ let doc = this._tooltip.doc;
+ this.container = doc.createElementNS(XHTML_NS, "div");
+ this.container.className = "devtools-tooltip-events-container";
+
+ for (let listener of this._eventListenerInfos) {
+ let phase = listener.capturing ? "Capturing" : "Bubbling";
+ let level = listener.DOM0 ? "DOM0" : "DOM2";
+
+ // Header
+ let header = doc.createElementNS(XHTML_NS, "div");
+ header.className = "event-header devtools-toolbar";
+ this.container.appendChild(header);
+
+ if (!listener.hide.debugger) {
+ let debuggerIcon = doc.createElementNS(XHTML_NS, "img");
+ debuggerIcon.className = "event-tooltip-debugger-icon";
+ debuggerIcon.setAttribute("src",
+ "chrome://devtools/skin/images/tool-debugger.svg");
+ let openInDebugger = GetStringFromName("eventsTooltip.openInDebugger");
+ debuggerIcon.setAttribute("title", openInDebugger);
+ header.appendChild(debuggerIcon);
+ }
+
+ if (!listener.hide.type) {
+ let eventTypeLabel = doc.createElementNS(XHTML_NS, "span");
+ eventTypeLabel.className = "event-tooltip-event-type";
+ eventTypeLabel.textContent = listener.type;
+ eventTypeLabel.setAttribute("title", listener.type);
+ header.appendChild(eventTypeLabel);
+ }
+
+ if (!listener.hide.filename) {
+ let filename = doc.createElementNS(XHTML_NS, "span");
+ filename.className = "event-tooltip-filename devtools-monospace";
+ filename.textContent = listener.origin;
+ filename.setAttribute("title", listener.origin);
+ header.appendChild(filename);
+ }
+
+ let attributesContainer = doc.createElementNS(XHTML_NS, "div");
+ attributesContainer.className = "event-tooltip-attributes-container";
+ header.appendChild(attributesContainer);
+
+ if (!listener.hide.capturing) {
+ let attributesBox = doc.createElementNS(XHTML_NS, "div");
+ attributesBox.className = "event-tooltip-attributes-box";
+ attributesContainer.appendChild(attributesBox);
+
+ let capturing = doc.createElementNS(XHTML_NS, "span");
+ capturing.className = "event-tooltip-attributes";
+ capturing.textContent = phase;
+ capturing.setAttribute("title", phase);
+ attributesBox.appendChild(capturing);
+ }
+
+ if (listener.tags) {
+ for (let tag of listener.tags.split(",")) {
+ let attributesBox = doc.createElementNS(XHTML_NS, "div");
+ attributesBox.className = "event-tooltip-attributes-box";
+ attributesContainer.appendChild(attributesBox);
+
+ let tagBox = doc.createElementNS(XHTML_NS, "span");
+ tagBox.className = "event-tooltip-attributes";
+ tagBox.textContent = tag;
+ tagBox.setAttribute("title", tag);
+ attributesBox.appendChild(tagBox);
+ }
+ }
+
+ if (!listener.hide.dom0) {
+ let attributesBox = doc.createElementNS(XHTML_NS, "div");
+ attributesBox.className = "event-tooltip-attributes-box";
+ attributesContainer.appendChild(attributesBox);
+
+ let dom0 = doc.createElementNS(XHTML_NS, "span");
+ dom0.className = "event-tooltip-attributes";
+ dom0.textContent = level;
+ dom0.setAttribute("title", level);
+ attributesBox.appendChild(dom0);
+ }
+
+ // Content
+ let content = doc.createElementNS(XHTML_NS, "div");
+ let editor = new Editor(config);
+ this._tooltip.eventEditors.set(content, {
+ editor: editor,
+ handler: listener.handler,
+ searchString: listener.searchString,
+ uri: listener.origin,
+ dom0: listener.DOM0,
+ appended: false
+ });
+
+ content.className = "event-tooltip-content-box";
+ this.container.appendChild(content);
+
+ this._addContentListeners(header);
+ }
+
+ this._tooltip.setContent(this.container, CONTAINER_WIDTH);
+ this._tooltip.on("hidden", this.destroy);
+ },
+
+ _addContentListeners: function (header) {
+ header.addEventListener("click", this._headerClicked);
+ },
+
+ _headerClicked: function (event) {
+ if (event.target.classList.contains("event-tooltip-debugger-icon")) {
+ this._debugClicked(event);
+ event.stopPropagation();
+ return;
+ }
+
+ let doc = this._tooltip.doc;
+ let header = event.currentTarget;
+ let content = header.nextElementSibling;
+
+ if (content.hasAttribute("open")) {
+ content.removeAttribute("open");
+ } else {
+ let contentNodes = doc.querySelectorAll(".event-tooltip-content-box");
+
+ for (let node of contentNodes) {
+ if (node !== content) {
+ node.removeAttribute("open");
+ }
+ }
+
+ content.setAttribute("open", "");
+
+ let eventEditors = this._tooltip.eventEditors.get(content);
+
+ if (eventEditors.appended) {
+ return;
+ }
+
+ let {editor, handler} = eventEditors;
+
+ let iframe = doc.createElementNS(XHTML_NS, "iframe");
+ iframe.setAttribute("style", "width: 100%; height: 100%; border-style: none;");
+
+ editor.appendTo(content, iframe).then(() => {
+ let tidied = beautify.js(handler, { "indent_size": 2 });
+ editor.setText(tidied);
+
+ eventEditors.appended = true;
+
+ let container = header.parentElement.getBoundingClientRect();
+ if (header.getBoundingClientRect().top < container.top) {
+ header.scrollIntoView(true);
+ } else if (content.getBoundingClientRect().bottom > container.bottom) {
+ content.scrollIntoView(false);
+ }
+
+ this._tooltip.emit("event-tooltip-ready");
+ });
+ }
+ },
+
+ _debugClicked: function (event) {
+ let header = event.currentTarget;
+ let content = header.nextElementSibling;
+
+ let {uri, searchString, dom0} = this._tooltip.eventEditors.get(content);
+
+ if (uri && uri !== "?") {
+ // Save a copy of toolbox as it will be set to null when we hide the tooltip.
+ let toolbox = this._toolbox;
+
+ this._tooltip.hide();
+
+ uri = uri.replace(/"/g, "");
+
+ let showSource = ({ DebuggerView }) => {
+ let matches = uri.match(/(.*):(\d+$)/);
+ let line = 1;
+
+ if (matches) {
+ uri = matches[1];
+ line = matches[2];
+ }
+
+ let item = DebuggerView.Sources.getItemForAttachment(a => a.source.url === uri);
+ if (item) {
+ let actor = item.attachment.source.actor;
+ DebuggerView.setEditorLocation(
+ actor, line, {noDebug: true}
+ ).then(() => {
+ if (dom0) {
+ let text = DebuggerView.editor.getText();
+ let index = text.indexOf(searchString);
+ let lastIndex = text.lastIndexOf(searchString);
+
+ // To avoid confusion we only search for DOM0 event handlers when
+ // there is only one possible match in the file.
+ if (index !== -1 && index === lastIndex) {
+ text = text.substr(0, index);
+ let newlineMatches = text.match(/\n/g);
+
+ if (newlineMatches) {
+ DebuggerView.editor.setCursor({
+ line: newlineMatches.length
+ });
+ }
+ }
+ }
+ });
+ }
+ };
+
+ let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger");
+ toolbox.selectTool("jsdebugger").then(({ panelWin: dbg }) => {
+ if (debuggerAlreadyOpen) {
+ showSource(dbg);
+ } else {
+ dbg.once(dbg.EVENTS.SOURCES_ADDED, () => showSource(dbg));
+ }
+ });
+ }
+ },
+
+ destroy: function () {
+ if (this._tooltip) {
+ this._tooltip.off("hidden", this.destroy);
+
+ let boxes = this.container.querySelectorAll(".event-tooltip-content-box");
+
+ for (let box of boxes) {
+ let {editor} = this._tooltip.eventEditors.get(box);
+ editor.destroy();
+ }
+
+ this._tooltip.eventEditors = null;
+ }
+
+ let headerNodes = this.container.querySelectorAll(".event-header");
+
+ for (let node of headerNodes) {
+ node.removeEventListener("click", this._headerClicked);
+ }
+
+ let sourceNodes = this.container.querySelectorAll(".event-tooltip-debugger-icon");
+ for (let node of sourceNodes) {
+ node.removeEventListener("click", this._debugClicked);
+ }
+
+ this._eventListenerInfos = this._toolbox = this._tooltip = null;
+ }
+};
+
+module.exports.setEventTooltip = setEventTooltip;
--- a/devtools/client/shared/widgets/tooltip/moz.build
+++ b/devtools/client/shared/widgets/tooltip/moz.build
@@ -1,10 +1,11 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
DevToolsModules(
+ 'EventTooltipHelper.js',
'ImageTooltipHelper.js',
'TooltipToggle.js',
)
--- a/devtools/client/themes/tooltips.css
+++ b/devtools/client/themes/tooltips.css
@@ -194,77 +194,87 @@
.tooltip-top .tooltip-arrow:before {
margin-top: -12px;
transform: rotate(45deg);
}
/* Tooltip: Events */
#devtools-tooltip-events-container {
- margin: -4px; /* Compensate for the .panel-arrowcontent padding. */
- max-width: 590px;
overflow-y: auto;
}
.event-header {
display: flex;
align-items: center;
cursor: pointer;
+ overflow: hidden;
}
.event-header:first-child {
border-width: 0;
}
.event-header:not(:first-child) {
border-width: 1px 0 0 0;
}
+.devtools-tooltip-events-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
.event-tooltip-event-type,
.event-tooltip-filename,
.event-tooltip-attributes {
margin-inline-start: 0;
flex-shrink: 0;
cursor: pointer;
}
.event-tooltip-event-type {
font-weight: bold;
font-size: 13px;
}
.event-tooltip-filename {
- margin-inline-end: 0;
+ margin: 0 5px;
font-size: 100%;
flex-shrink: 1;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ /* Force ellipsis to be displayed on the left */
+ direction: rtl;
}
.event-tooltip-debugger-icon {
width: 16px;
height: 16px;
margin-inline-end: 4px;
opacity: 0.6;
flex-shrink: 0;
}
.event-tooltip-debugger-icon:hover {
opacity: 1;
}
.event-tooltip-content-box {
display: none;
- height: 100px;
+ height: 54px;
overflow: hidden;
margin-inline-end: 0;
border: 1px solid var(--theme-splitter-color);
border-width: 1px 0 0 0;
}
.event-toolbox-content-box iframe {
height: 100%;
+ border-style: none;
}
.event-tooltip-content-box[open] {
display: block;
}
.event-tooltip-source-container {
margin-top: 5px;
@@ -283,16 +293,17 @@
flex-grow: 1;
justify-content: flex-end;
}
.event-tooltip-attributes-box {
display: flex;
flex-shrink: 0;
align-items: center;
+ height: 14px;
border-radius: 3px;
padding: 2px;
margin-inline-start: 5px;
background-color: var(--theme-body-color-alt);
color: var(--theme-toolbar-background);
}
.event-tooltip-attributes {