tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

element-container.js (8001B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const MarkupContainer = require("resource://devtools/client/inspector/markup/views/markup-container.js");
      8 const ElementEditor = require("resource://devtools/client/inspector/markup/views/element-editor.js");
      9 const {
     10  ELEMENT_NODE,
     11 } = require("resource://devtools/shared/dom-node-constants.js");
     12 
     13 loader.lazyRequireGetter(
     14  this,
     15  "EventTooltip",
     16  "resource://devtools/client/shared/widgets/tooltip/EventTooltipHelper.js",
     17  true
     18 );
     19 loader.lazyRequireGetter(
     20  this,
     21  ["setImageTooltip", "setBrokenImageTooltip"],
     22  "resource://devtools/client/shared/widgets/tooltip/ImageTooltipHelper.js",
     23  true
     24 );
     25 loader.lazyRequireGetter(
     26  this,
     27  "clipboardHelper",
     28  "resource://devtools/shared/platform/clipboard.js"
     29 );
     30 
     31 const PREVIEW_MAX_DIM_PREF = "devtools.inspector.imagePreviewTooltipSize";
     32 
     33 /**
     34 * An implementation of MarkupContainer for Elements that can contain
     35 * child nodes.
     36 * Allows editing of tag name, attributes, expanding / collapsing.
     37 */
     38 class MarkupElementContainer extends MarkupContainer {
     39  /**
     40   * @param  {MarkupView} markupView
     41   *         The markup view that owns this container.
     42   * @param  {NodeFront} node
     43   *         The node to display.
     44   */
     45  constructor(markupView, node) {
     46    super();
     47    this.initialize(markupView, node, "elementcontainer");
     48 
     49    if (node.nodeType === ELEMENT_NODE) {
     50      this.editor = new ElementEditor(this, node);
     51    } else {
     52      throw new Error("Invalid node for MarkupElementContainer");
     53    }
     54 
     55    this.tagLine.appendChild(this.editor.elt);
     56  }
     57  onContainerClick(event) {
     58    if (!event.target.hasAttribute("data-event")) {
     59      return;
     60    }
     61 
     62    event.target.setAttribute("aria-pressed", "true");
     63    this._buildEventTooltipContent(event.target);
     64  }
     65 
     66  async _buildEventTooltipContent(target) {
     67    const tooltip = this.markup.eventDetailsTooltip;
     68    await tooltip.hide();
     69 
     70    const listenerInfo = await this.node.getEventListenerInfo();
     71 
     72    const toolbox = this.markup.toolbox;
     73 
     74    // Create the EventTooltip which will populate the tooltip content.
     75    const eventTooltip = new EventTooltip(
     76      tooltip,
     77      listenerInfo,
     78      toolbox,
     79      this.node
     80    );
     81 
     82    // Add specific styling to the "event" badge when at least one event is disabled.
     83    // The eventTooltip will take care of clearing the event listener when it's destroyed.
     84    eventTooltip.on(
     85      "event-tooltip-listener-toggled",
     86      ({ hasDisabledEventListeners }) => {
     87        const className = "has-disabled-events";
     88        if (hasDisabledEventListeners) {
     89          this.editor._eventBadge.classList.add(className);
     90        } else {
     91          this.editor._eventBadge.classList.remove(className);
     92        }
     93      }
     94    );
     95 
     96    // Disable the image preview tooltip while we display the event details
     97    this.markup._disableImagePreviewTooltip();
     98    tooltip.once("hidden", () => {
     99      eventTooltip.destroy();
    100 
    101      // Enable the image preview tooltip after closing the event details
    102      this.markup._enableImagePreviewTooltip();
    103 
    104      // Allow clicks on the event badge to display the event popup again
    105      // (but allow the currently queued click event to run first).
    106      this.markup.win.setTimeout(() => {
    107        if (this.editor._eventBadge) {
    108          this.editor._eventBadge.style.pointerEvents = "auto";
    109          this.editor._eventBadge.setAttribute("aria-pressed", "false");
    110        }
    111      }, 0);
    112    });
    113 
    114    // Prevent clicks on the event badge to display the event popup again.
    115    if (this.editor._eventBadge) {
    116      this.editor._eventBadge.style.pointerEvents = "none";
    117    }
    118    tooltip.show(target);
    119    tooltip.focus();
    120  }
    121 
    122  /**
    123   * Generates the an image preview for this Element. The element must be an
    124   * image or canvas (@see isPreviewable).
    125   *
    126   * @return {Promise} that is resolved with an object of form
    127   *         { data, size: { naturalWidth, naturalHeight, resizeRatio } } where
    128   *         - data is the data-uri for the image preview.
    129   *         - size contains information about the original image size and if
    130   *         the preview has been resized.
    131   *
    132   * If this element is not previewable or the preview cannot be generated for
    133   * some reason, the Promise is rejected.
    134   */
    135  _getPreview() {
    136    if (!this.isPreviewable()) {
    137      return Promise.reject("_getPreview called on a non-previewable element.");
    138    }
    139 
    140    if (this.tooltipDataPromise) {
    141      // A preview request is already pending. Re-use that request.
    142      return this.tooltipDataPromise;
    143    }
    144 
    145    // Fetch the preview from the server.
    146    this.tooltipDataPromise = async function () {
    147      const maxDim = Services.prefs.getIntPref(PREVIEW_MAX_DIM_PREF);
    148      const preview = await this.node.getImageData(maxDim);
    149      const data = await preview.data.string();
    150 
    151      // Clear the pending preview request. We can't reuse the results later as
    152      // the preview contents might have changed.
    153      this.tooltipDataPromise = null;
    154      return { data, size: preview.size };
    155    }.bind(this)();
    156 
    157    return this.tooltipDataPromise;
    158  }
    159 
    160  /**
    161   * Executed by MarkupView._isImagePreviewTarget which is itself called when
    162   * the mouse hovers over a target in the markup-view.
    163   * Checks if the target is indeed something we want to have an image tooltip
    164   * preview over and, if so, inserts content into the tooltip.
    165   *
    166   * @return {Promise} that resolves when the tooltip content is ready. Resolves
    167   * true if the tooltip should be displayed, false otherwise.
    168   */
    169  async isImagePreviewTarget(target, tooltip) {
    170    // Is this Element previewable.
    171    if (!this.isPreviewable()) {
    172      return false;
    173    }
    174 
    175    // If the Element has an src attribute, the tooltip is shown when hovering
    176    // over the src url. If not, the tooltip is shown when hovering over the tag
    177    // name.
    178    const src = this.editor.getAttributeElement("src");
    179    const expectedTarget = src ? src.querySelector(".link") : this.editor.tag;
    180    if (target !== expectedTarget) {
    181      return false;
    182    }
    183 
    184    try {
    185      const { data, size } = await this._getPreview();
    186      // The preview is ready.
    187      const options = {
    188        naturalWidth: size.naturalWidth,
    189        naturalHeight: size.naturalHeight,
    190        maxDim: Services.prefs.getIntPref(PREVIEW_MAX_DIM_PREF),
    191      };
    192 
    193      setImageTooltip(tooltip, this.markup.doc, data, options);
    194    } catch (e) {
    195      // Indicate the failure but show the tooltip anyway.
    196      setBrokenImageTooltip(tooltip, this.markup.doc);
    197    }
    198    return true;
    199  }
    200 
    201  copyImageDataUri() {
    202    // We need to send again a request to gettooltipData even if one was sent
    203    // for the tooltip, because we want the full-size image
    204    this.node.getImageData().then(data => {
    205      data.data.string().then(str => {
    206        clipboardHelper.copyString(str);
    207      });
    208    });
    209  }
    210 
    211  setInlineTextChild(inlineTextChild) {
    212    this.inlineTextChild = inlineTextChild;
    213    this.editor.updateTextEditor();
    214  }
    215 
    216  clearInlineTextChild() {
    217    this.inlineTextChild = undefined;
    218    this.editor.updateTextEditor();
    219  }
    220 
    221  /**
    222   * Trigger new attribute field for input.
    223   */
    224  addAttribute() {
    225    this.editor.newAttr.editMode();
    226  }
    227 
    228  /**
    229   * Trigger attribute field for editing.
    230   */
    231  editAttribute(attrName) {
    232    this.editor.attrElements.get(attrName).editMode();
    233  }
    234 
    235  /**
    236   * Remove attribute from container.
    237   * This is an undoable action.
    238   */
    239  removeAttribute(attrName) {
    240    const doMods = this.editor._startModifyingAttributes();
    241    const undoMods = this.editor._startModifyingAttributes();
    242    this.editor._saveAttribute(attrName, undoMods);
    243    doMods.removeAttribute(attrName);
    244    this.undo.do(
    245      () => {
    246        doMods.apply();
    247      },
    248      () => {
    249        undoMods.apply();
    250      }
    251    );
    252  }
    253 }
    254 
    255 module.exports = MarkupElementContainer;