зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1266450 - part6: migrate EventDetails tooltip;r=bgrins
For now this is a 1 to 1 migration of the existing Tooltip helper method from XUL to HTML. MozReview-Commit-ID: 9YiJLgibV9h --HG-- extra : rebase_source : af428055060a105d270d70b1e4694717e0869b2b extra : source : d03cca0c048c9ba1f3062519650e37ae986d4bc7
This commit is contained in:
Родитель
3615d9843b
Коммит
0f7dbc517b
|
@ -35,11 +35,10 @@ const {editableField, InplaceEditor} =
|
||||||
const {HTMLEditor} = require("devtools/client/inspector/markup/html-editor");
|
const {HTMLEditor} = require("devtools/client/inspector/markup/html-editor");
|
||||||
const promise = require("promise");
|
const promise = require("promise");
|
||||||
const Services = require("Services");
|
const Services = require("Services");
|
||||||
const {Tooltip} = require("devtools/client/shared/widgets/Tooltip");
|
|
||||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||||
const {setImageTooltip, setBrokenImageTooltip} =
|
const {setImageTooltip, setBrokenImageTooltip} =
|
||||||
require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
|
require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
|
||||||
|
const {setEventTooltip} = require("devtools/client/shared/widgets/tooltip/EventTooltipHelper");
|
||||||
const EventEmitter = require("devtools/shared/event-emitter");
|
const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
const Heritage = require("sdk/core/heritage");
|
const Heritage = require("sdk/core/heritage");
|
||||||
const {parseAttribute} =
|
const {parseAttribute} =
|
||||||
|
@ -174,7 +173,8 @@ MarkupView.prototype = {
|
||||||
_selectedContainer: null,
|
_selectedContainer: null,
|
||||||
|
|
||||||
_initTooltips: function () {
|
_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,
|
this.imagePreviewTooltip = new HTMLTooltip(this._inspector.toolbox,
|
||||||
{type: "arrow"});
|
{type: "arrow"});
|
||||||
this._enableImagePreviewTooltip();
|
this._enableImagePreviewTooltip();
|
||||||
|
@ -2615,11 +2615,8 @@ MarkupElementContainer.prototype = Heritage.extend(MarkupContainer.prototype, {
|
||||||
tooltip.hide(target);
|
tooltip.hide(target);
|
||||||
|
|
||||||
this.node.getEventListenerInfo().then(listenerInfo => {
|
this.node.getEventListenerInfo().then(listenerInfo => {
|
||||||
tooltip.setEventContent({
|
let toolbox = this.markup._inspector.toolbox;
|
||||||
eventListenerInfos: listenerInfo,
|
setEventTooltip(tooltip, listenerInfo, toolbox);
|
||||||
toolbox: this.markup._inspector.toolbox
|
|
||||||
});
|
|
||||||
|
|
||||||
// Disable the image preview tooltip while we display the event details
|
// Disable the image preview tooltip while we display the event details
|
||||||
this.markup._disableImagePreviewTooltip();
|
this.markup._disableImagePreviewTooltip();
|
||||||
tooltip.once("hidden", () => {
|
tooltip.once("hidden", () => {
|
||||||
|
|
|
@ -43,7 +43,7 @@ add_task(function* () {
|
||||||
yield tooltip.once("shown");
|
yield tooltip.once("shown");
|
||||||
info("EventTooltip visible.");
|
info("EventTooltip visible.");
|
||||||
|
|
||||||
let container = tooltip.content;
|
let container = tooltip.panel;
|
||||||
let containerRect = container.getBoundingClientRect();
|
let containerRect = container.getBoundingClientRect();
|
||||||
let headers = container.querySelectorAll(".event-header");
|
let headers = container.querySelectorAll(".event-header");
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ function* checkEventsForNode(test, inspector, testActor) {
|
||||||
info("tooltip shown");
|
info("tooltip shown");
|
||||||
|
|
||||||
// Check values
|
// Check values
|
||||||
let headers = tooltip.content.querySelectorAll(".event-header");
|
let headers = tooltip.panel.querySelectorAll(".event-header");
|
||||||
let nodeFront = container.node;
|
let nodeFront = container.node;
|
||||||
let cssSelector = nodeFront.nodeName + "#" + nodeFront.id;
|
let cssSelector = nodeFront.nodeName + "#" + nodeFront.id;
|
||||||
|
|
||||||
|
@ -83,19 +83,22 @@ function* checkEventsForNode(test, inspector, testActor) {
|
||||||
let attributes = header.querySelectorAll(".event-tooltip-attributes");
|
let attributes = header.querySelectorAll(".event-tooltip-attributes");
|
||||||
let contentBox = header.nextElementSibling;
|
let contentBox = header.nextElementSibling;
|
||||||
|
|
||||||
is(type.getAttribute("value"), expected[i].type,
|
is(type.textContent, expected[i].type,
|
||||||
"type matches for " + cssSelector);
|
"type matches for " + cssSelector);
|
||||||
is(filename.getAttribute("value"), expected[i].filename,
|
is(filename.textContent, expected[i].filename,
|
||||||
"filename matches for " + cssSelector);
|
"filename matches for " + cssSelector);
|
||||||
|
|
||||||
is(attributes.length, expected[i].attributes.length,
|
is(attributes.length, expected[i].attributes.length,
|
||||||
"we have the correct number of attributes");
|
"we have the correct number of attributes");
|
||||||
|
|
||||||
for (let j = 0; j < expected[i].attributes.length; j++) {
|
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);
|
"attribute[" + j + "] matches for " + cssSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure the header is not hidden by scrollbars before clicking.
|
||||||
|
header.scrollIntoView();
|
||||||
|
|
||||||
EventUtils.synthesizeMouseAtCenter(header, {}, type.ownerGlobal);
|
EventUtils.synthesizeMouseAtCenter(header, {}, type.ownerGlobal);
|
||||||
yield tooltip.once("event-tooltip-ready");
|
yield tooltip.once("event-tooltip-ready");
|
||||||
|
|
||||||
|
|
|
@ -110,14 +110,15 @@ HTMLTooltip.prototype = {
|
||||||
* The tooltip content, should be a HTML element.
|
* The tooltip content, should be a HTML element.
|
||||||
* @param {Number} width
|
* @param {Number} width
|
||||||
* Preferred width for the tooltip container
|
* Preferred width for the tooltip container
|
||||||
* @param {Number} height
|
* @param {Number} height (optional)
|
||||||
* Preferred height for the tooltip container. If the content height is
|
* Preferred height for the tooltip container. If the content height is
|
||||||
* smaller than the container's height, the tooltip will automatically
|
* 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
|
* @return {Promise} a promise that will resolve when the content has been
|
||||||
* added in the tooltip container.
|
* 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 themeHeight = EXTRA_HEIGHT[this.type] + 2 * EXTRA_BORDER[this.type];
|
||||||
let themeWidth = 2 * EXTRA_BORDER[this.type];
|
let themeWidth = 2 * EXTRA_BORDER[this.type];
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
const {colorUtils} = require("devtools/client/shared/css-color");
|
const {colorUtils} = require("devtools/client/shared/css-color");
|
||||||
const Heritage = require("sdk/core/heritage");
|
const Heritage = require("sdk/core/heritage");
|
||||||
const {Eyedropper} = require("devtools/client/eyedropper/eyedropper");
|
const {Eyedropper} = require("devtools/client/eyedropper/eyedropper");
|
||||||
const Editor = require("devtools/client/sourceeditor/editor");
|
|
||||||
const Services = require("Services");
|
const Services = require("Services");
|
||||||
|
|
||||||
loader.lazyRequireGetter(this, "beautify", "devtools/shared/jsbeautify/beautify");
|
loader.lazyRequireGetter(this, "beautify", "devtools/shared/jsbeautify/beautify");
|
||||||
|
@ -434,19 +433,6 @@ Tooltip.prototype = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Fill the tooltip with a variables view, inspecting an object via its
|
||||||
* corresponding object actor, as specified in the remote debugging protocol.
|
* corresponding object actor, as specified in the remote debugging protocol.
|
||||||
|
@ -1066,298 +1052,6 @@ Heritage.extend(SwatchBasedEditorTooltip.prototype, {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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
|
* The swatch cubic-bezier tooltip class is a specific class meant to be used
|
||||||
* along with rule-view's generated cubic-bezier swatches.
|
* along with rule-view's generated cubic-bezier swatches.
|
||||||
|
|
|
@ -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;
|
|
@ -5,6 +5,7 @@
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
DevToolsModules(
|
DevToolsModules(
|
||||||
|
'EventTooltipHelper.js',
|
||||||
'ImageTooltipHelper.js',
|
'ImageTooltipHelper.js',
|
||||||
'TooltipToggle.js',
|
'TooltipToggle.js',
|
||||||
)
|
)
|
||||||
|
|
|
@ -199,8 +199,6 @@
|
||||||
/* Tooltip: Events */
|
/* Tooltip: Events */
|
||||||
|
|
||||||
#devtools-tooltip-events-container {
|
#devtools-tooltip-events-container {
|
||||||
margin: -4px; /* Compensate for the .panel-arrowcontent padding. */
|
|
||||||
max-width: 590px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,6 +206,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-header:first-child {
|
.event-header:first-child {
|
||||||
|
@ -218,6 +217,11 @@
|
||||||
border-width: 1px 0 0 0;
|
border-width: 1px 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.devtools-tooltip-events-container {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.event-tooltip-event-type,
|
.event-tooltip-event-type,
|
||||||
.event-tooltip-filename,
|
.event-tooltip-filename,
|
||||||
.event-tooltip-attributes {
|
.event-tooltip-attributes {
|
||||||
|
@ -232,9 +236,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-tooltip-filename {
|
.event-tooltip-filename {
|
||||||
margin-inline-end: 0;
|
margin: 0 5px;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
flex-shrink: 1;
|
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 {
|
.event-tooltip-debugger-icon {
|
||||||
|
@ -251,7 +260,7 @@
|
||||||
|
|
||||||
.event-tooltip-content-box {
|
.event-tooltip-content-box {
|
||||||
display: none;
|
display: none;
|
||||||
height: 100px;
|
height: 54px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-inline-end: 0;
|
margin-inline-end: 0;
|
||||||
border: 1px solid var(--theme-splitter-color);
|
border: 1px solid var(--theme-splitter-color);
|
||||||
|
@ -260,6 +269,7 @@
|
||||||
|
|
||||||
.event-toolbox-content-box iframe {
|
.event-toolbox-content-box iframe {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-tooltip-content-box[open] {
|
.event-tooltip-content-box[open] {
|
||||||
|
@ -288,6 +298,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
height: 14px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
margin-inline-start: 5px;
|
margin-inline-start: 5px;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче