summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/markup/views/element-container.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--devtools/client/inspector/markup/views/element-container.js257
1 files changed, 257 insertions, 0 deletions
diff --git a/devtools/client/inspector/markup/views/element-container.js b/devtools/client/inspector/markup/views/element-container.js
new file mode 100644
index 0000000000..7aee2bd990
--- /dev/null
+++ b/devtools/client/inspector/markup/views/element-container.js
@@ -0,0 +1,257 @@
+/* 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 MarkupContainer = require("resource://devtools/client/inspector/markup/views/markup-container.js");
+const ElementEditor = require("resource://devtools/client/inspector/markup/views/element-editor.js");
+const {
+ ELEMENT_NODE,
+} = require("resource://devtools/shared/dom-node-constants.js");
+const { extend } = require("resource://devtools/shared/extend.js");
+
+loader.lazyRequireGetter(
+ this,
+ "EventTooltip",
+ "resource://devtools/client/shared/widgets/tooltip/EventTooltipHelper.js",
+ true
+);
+loader.lazyRequireGetter(
+ this,
+ ["setImageTooltip", "setBrokenImageTooltip"],
+ "resource://devtools/client/shared/widgets/tooltip/ImageTooltipHelper.js",
+ true
+);
+loader.lazyRequireGetter(
+ this,
+ "clipboardHelper",
+ "resource://devtools/shared/platform/clipboard.js"
+);
+
+const PREVIEW_MAX_DIM_PREF = "devtools.inspector.imagePreviewTooltipSize";
+
+/**
+ * An implementation of MarkupContainer for Elements that can contain
+ * child nodes.
+ * Allows editing of tag name, attributes, expanding / collapsing.
+ *
+ * @param {MarkupView} markupView
+ * The markup view that owns this container.
+ * @param {NodeFront} node
+ * The node to display.
+ */
+function MarkupElementContainer(markupView, node) {
+ MarkupContainer.prototype.initialize.call(
+ this,
+ markupView,
+ node,
+ "elementcontainer"
+ );
+
+ if (node.nodeType === ELEMENT_NODE) {
+ this.editor = new ElementEditor(this, node);
+ } else {
+ throw new Error("Invalid node for MarkupElementContainer");
+ }
+
+ this.tagLine.appendChild(this.editor.elt);
+}
+
+MarkupElementContainer.prototype = extend(MarkupContainer.prototype, {
+ onContainerClick(event) {
+ if (!event.target.hasAttribute("data-event")) {
+ return;
+ }
+
+ this._buildEventTooltipContent(event.target);
+ },
+
+ async _buildEventTooltipContent(target) {
+ const tooltip = this.markup.eventDetailsTooltip;
+ await tooltip.hide();
+
+ const listenerInfo = await this.node.getEventListenerInfo();
+
+ const toolbox = this.markup.toolbox;
+
+ // Create the EventTooltip which will populate the tooltip content.
+ const eventTooltip = new EventTooltip(
+ tooltip,
+ listenerInfo,
+ toolbox,
+ this.node
+ );
+
+ // Add specific styling to the "event" badge when at least one event is disabled.
+ // The eventTooltip will take care of clearing the event listener when it's destroyed.
+ eventTooltip.on(
+ "event-tooltip-listener-toggled",
+ ({ hasDisabledEventListeners }) => {
+ const className = "has-disabled-events";
+ if (hasDisabledEventListeners) {
+ this.editor._eventBadge.classList.add(className);
+ } else {
+ this.editor._eventBadge.classList.remove(className);
+ }
+ }
+ );
+
+ // Disable the image preview tooltip while we display the event details
+ this.markup._disableImagePreviewTooltip();
+ tooltip.once("hidden", () => {
+ eventTooltip.destroy();
+
+ // 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).
+ this.markup.win.setTimeout(() => {
+ if (this.editor._eventBadge) {
+ this.editor._eventBadge.style.pointerEvents = "auto";
+ }
+ }, 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);
+ },
+
+ /**
+ * 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
+ * - data is the data-uri for the image preview.
+ * - size contains information about the original image size and if
+ * the preview has been resized.
+ *
+ * If this element is not previewable or the preview cannot be generated for
+ * some reason, the Promise is rejected.
+ */
+ _getPreview() {
+ if (!this.isPreviewable()) {
+ return Promise.reject("_getPreview called on a non-previewable element.");
+ }
+
+ if (this.tooltipDataPromise) {
+ // A preview request is already pending. Re-use that request.
+ return this.tooltipDataPromise;
+ }
+
+ // Fetch the preview from the server.
+ this.tooltipDataPromise = async function () {
+ const maxDim = Services.prefs.getIntPref(PREVIEW_MAX_DIM_PREF);
+ const preview = await this.node.getImageData(maxDim);
+ const data = await preview.data.string();
+
+ // Clear the pending preview request. We can't reuse the results later as
+ // the preview contents might have changed.
+ this.tooltipDataPromise = null;
+ return { data, size: preview.size };
+ }.bind(this)();
+
+ return this.tooltipDataPromise;
+ },
+
+ /**
+ * Executed by MarkupView._isImagePreviewTarget 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) {
+ // Is this Element previewable.
+ if (!this.isPreviewable()) {
+ return false;
+ }
+
+ // If the Element has an src attribute, the tooltip is shown when hovering
+ // 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;
+ }
+
+ 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);
+ } catch (e) {
+ // Indicate the failure but show the tooltip anyway.
+ setBrokenImageTooltip(tooltip, this.markup.doc);
+ }
+ return true;
+ },
+
+ copyImageDataUri() {
+ // 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);
+ });
+ });
+ },
+
+ setInlineTextChild(inlineTextChild) {
+ this.inlineTextChild = inlineTextChild;
+ this.editor.updateTextEditor();
+ },
+
+ clearInlineTextChild() {
+ this.inlineTextChild = undefined;
+ this.editor.updateTextEditor();
+ },
+
+ /**
+ * Trigger new attribute field for input.
+ */
+ addAttribute() {
+ this.editor.newAttr.editMode();
+ },
+
+ /**
+ * Trigger attribute field for editing.
+ */
+ editAttribute(attrName) {
+ this.editor.attrElements.get(attrName).editMode();
+ },
+
+ /**
+ * Remove attribute from container.
+ * This is an undoable action.
+ */
+ removeAttribute(attrName) {
+ const doMods = this.editor._startModifyingAttributes();
+ const undoMods = this.editor._startModifyingAttributes();
+ this.editor._saveAttribute(attrName, undoMods);
+ doMods.removeAttribute(attrName);
+ this.undo.do(
+ () => {
+ doMods.apply();
+ },
+ () => {
+ undoMods.apply();
+ }
+ );
+ },
+});
+
+module.exports = MarkupElementContainer;