Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  ScreenshotsComponentChild.sys.mjs   Sprache: unbekannt

 
/* 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/. */
/* eslint-env mozilla/browser-window */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  ScreenshotsOverlay: "resource:///modules/ScreenshotsOverlayChild.sys.mjs",
});

const SCREENSHOTS_PREVENT_CONTENT_EVENTS_PREF =
  "screenshots.browser.component.preventContentEvents";

export class ScreenshotsComponentChild extends JSWindowActorChild {
  #resizeTask;
  #scrollTask;
  #overlay;
  #preventableEventsAdded = false;

  static OVERLAY_EVENTS = [
    "click",
    "pointerdown",
    "pointermove",
    "pointerup",
    "keyup",
    "keydown",
  ];

  // The following events are only listened to so we can prevent them from
  // reaching the content page. The events in OVERLAY_EVENTS are also prevented.
  static PREVENTABLE_EVENTS = [
    "mousemove",
    "mousedown",
    "mouseup",
    "mouseenter",
    "mouseover",
    "mouseout",
    "mouseleave",
    "touchstart",
    "touchmove",
    "touchend",
    "dblclick",
    "auxclick",
    "keypress",
    "contextmenu",
    "pointerenter",
    "pointerover",
    "pointerout",
    "pointerleave",
  ];

  get overlay() {
    return this.#overlay;
  }

  receiveMessage(message) {
    switch (message.name) {
      case "Screenshots:ShowOverlay":
        return this.startScreenshotsOverlay();
      case "Screenshots:HideOverlay":
        return this.endScreenshotsOverlay(message.data);
      case "Screenshots:isOverlayShowing":
        return this.overlay?.initialized;
      case "Screenshots:getFullPageBounds":
        return this.getFullPageBounds();
      case "Screenshots:getVisibleBounds":
        return this.getVisibleBounds();
      case "Screenshots:getDocumentTitle":
        return this.getDocumentTitle();
      case "Screenshots:GetMethodsUsed":
        return this.getMethodsUsed();
      case "Screenshots:RemoveEventListeners":
        return this.removeEventListeners();
      case "Screenshots:AddEventListeners":
        return this.addEventListeners();
      case "Screenshots:MoveFocusToContent":
        return this.focusOverlay(message.data);
      case "Screenshots:ClearFocus":
        Services.focus.clearFocus(this.contentWindow);
        return null;
    }
    return null;
  }

  handleEvent(event) {
    if (!event.isTrusted) {
      return;
    }

    // Handle overlay events here
    if (
      [
        ...ScreenshotsComponentChild.OVERLAY_EVENTS,
        ...ScreenshotsComponentChild.PREVENTABLE_EVENTS,
        "selectionchange",
      ].includes(event.type)
    ) {
      if (!this.overlay?.initialized) {
        return;
      }

      // Preventing a pointerdown event throws an error in debug builds.
      // See https://searchfox.org/mozilla-central/rev/b41bb321fe4bd7d03926083698ac498ebec0accf/widget/WidgetEventImpl.cpp#566-572
      // Don't prevent the default context menu.
      if (!["contextmenu", "pointerdown"].includes(event.type)) {
        event.preventDefault();
      }

      event.stopImmediatePropagation();
      this.overlay.handleEvent(event);
      return;
    }

    switch (event.type) {
      case "beforeunload":
        this.requestCancelScreenshot("Navigation");
        break;
      case "resize":
        if (!this.#resizeTask && this.overlay?.initialized) {
          this.#resizeTask = new lazy.DeferredTask(() => {
            this.overlay.updateScreenshotsOverlayDimensions("resize");
          }, 16);
        }
        this.#resizeTask.arm();
        break;
      case "scroll":
        if (!this.#scrollTask && this.overlay?.initialized) {
          this.#scrollTask = new lazy.DeferredTask(() => {
            this.overlay.updateScreenshotsOverlayDimensions("scroll");
          }, 16);
        }
        this.#scrollTask.arm();
        break;
      case "Screenshots:Close":
        this.requestCancelScreenshot(event.detail.reason);
        break;
      case "Screenshots:Copy":
        this.requestCopyScreenshot(event.detail.region);
        break;
      case "Screenshots:Download":
        this.requestDownloadScreenshot(event.detail.region);
        break;
      case "Screenshots:OverlaySelection": {
        let { hasSelection, overlayState } = event.detail;
        this.sendOverlaySelection({ hasSelection, overlayState });
        break;
      }
      case "Screenshots:RecordEvent": {
        let { eventName, args } = event.detail;
        Glean.screenshots[eventName].record(args);
        break;
      }
      case "Screenshots:ShowPanel":
        this.sendAsyncMessage("Screenshots:ShowPanel");
        break;
      case "Screenshots:HidePanel":
        this.sendAsyncMessage("Screenshots:HidePanel");
        break;
      case "Screenshots:FocusPanel":
        this.sendAsyncMessage("Screenshots:MoveFocusToParent", event.detail);
        break;
    }
  }

  /**
   * Send a request to cancel the screenshot to the parent process
   */
  requestCancelScreenshot(reason) {
    this.sendAsyncMessage("Screenshots:CancelScreenshot", {
      closeOverlay: false,
      reason,
    });
    this.endScreenshotsOverlay();
  }

  /**
   * Send a request to copy the screenshots
   * @param {Object} region The region dimensions of the screenshot to be copied
   */
  requestCopyScreenshot(region) {
    region.devicePixelRatio = this.contentWindow.devicePixelRatio;
    this.sendAsyncMessage("Screenshots:CopyScreenshot", { region });
    this.endScreenshotsOverlay({ doNotResetMethods: true });
  }

  /**
   * Send a request to download the screenshots
   * @param {Object} region The region dimensions of the screenshot to be downloaded
   */
  requestDownloadScreenshot(region) {
    region.devicePixelRatio = this.contentWindow.devicePixelRatio;
    this.sendAsyncMessage("Screenshots:DownloadScreenshot", {
      title: this.getDocumentTitle(),
      region,
    });
    this.endScreenshotsOverlay({ doNotResetMethods: true });
  }

  getDocumentTitle() {
    return this.document.title;
  }

  sendOverlaySelection(data) {
    this.sendAsyncMessage("Screenshots:OverlaySelection", data);
  }

  getMethodsUsed() {
    let methodsUsed = this.#overlay.methodsUsed;
    this.#overlay.resetMethodsUsed();
    return methodsUsed;
  }

  focusOverlay(direction) {
    this.contentWindow.focus();
    this.#overlay.focus(direction);
  }

  /**
   * Resolves when the document is ready to have an overlay injected into it.
   *
   * @returns {Promise}
   * @resolves {Boolean} true when document is ready or rejects
   */
  documentIsReady() {
    const document = this.document;
    // Some pages take ages to finish loading - if at all.
    // We want to respond to enable the screenshots UI as soon that is possible
    function readyEnough() {
      return (
        document.readyState !== "uninitialized" && document.documentElement
      );
    }

    if (readyEnough()) {
      return Promise.resolve();
    }
    return new Promise((resolve, reject) => {
      function onChange(event) {
        if (event.type === "pagehide") {
          document.removeEventListener("readystatechange", onChange);
          this.contentWindow.removeEventListener("pagehide", onChange);
          reject(new Error("document unloaded before it was ready"));
        } else if (readyEnough()) {
          document.removeEventListener("readystatechange", onChange);
          this.contentWindow.removeEventListener("pagehide", onChange);
          resolve();
        }
      }
      document.addEventListener("readystatechange", onChange);
      this.contentWindow.addEventListener("pagehide", onChange, { once: true });
    });
  }

  addEventListeners() {
    this.contentWindow.addEventListener("beforeunload", this);
    this.contentWindow.addEventListener("resize", this);
    this.contentWindow.addEventListener("scroll", this);
    this.addOverlayEventListeners();
  }

  addOverlayEventListeners() {
    let chromeEventHandler = this.docShell.chromeEventHandler;
    for (let event of ScreenshotsComponentChild.OVERLAY_EVENTS) {
      chromeEventHandler.addEventListener(event, this, true);
    }

    this.document.addEventListener("selectionchange", this);

    if (Services.prefs.getBoolPref(SCREENSHOTS_PREVENT_CONTENT_EVENTS_PREF)) {
      for (let event of ScreenshotsComponentChild.PREVENTABLE_EVENTS) {
        chromeEventHandler.addEventListener(event, this, true);
      }

      this.#preventableEventsAdded = true;
    }
  }

  /**
   * Wait until the document is ready and then show the screenshots overlay
   *
   * @returns {Boolean} true when document is ready and the overlay is shown
   * otherwise false
   */
  async startScreenshotsOverlay() {
    try {
      await this.documentIsReady();
    } catch (ex) {
      console.warn(`ScreenshotsComponentChild: ${ex.message}`);
      return false;
    }
    await this.documentIsReady();
    let overlay =
      this.overlay ||
      (this.#overlay = new lazy.ScreenshotsOverlay(this.document));
    this.addEventListeners();

    overlay.initialize();
    return true;
  }

  removeEventListeners() {
    this.contentWindow.removeEventListener("beforeunload", this);
    this.contentWindow.removeEventListener("resize", this);
    this.contentWindow.removeEventListener("scroll", this);
    this.removeOverlayEventListeners();
  }

  removeOverlayEventListeners() {
    let chromeEventHandler = this.docShell.chromeEventHandler;
    for (let event of ScreenshotsComponentChild.OVERLAY_EVENTS) {
      chromeEventHandler.removeEventListener(event, this, true);
    }

    this.document.removeEventListener("selectionchange", this);

    if (this.#preventableEventsAdded) {
      for (let event of ScreenshotsComponentChild.PREVENTABLE_EVENTS) {
        chromeEventHandler.removeEventListener(event, this, true);
      }
    }

    this.#preventableEventsAdded = false;
  }

  /**
   * Removes event listeners and the screenshots overlay.
   */
  endScreenshotsOverlay(options = {}) {
    this.removeEventListeners();

    this.overlay?.tearDown(options);
    this.#resizeTask?.disarm();
    this.#scrollTask?.disarm();
  }

  didDestroy() {
    this.#resizeTask?.disarm();
    this.#scrollTask?.disarm();
  }

  /**
   * Gets the full page bounds for a full page screenshot.
   *
   * @returns { object }
   *   The device pixel ratio and a DOMRect of the scrollable content bounds.
   *
   *   devicePixelRatio (float):
   *      The device pixel ratio of the screen
   *
   *   rect (object):
   *      top (int):
   *        The scroll top position for the content window.
   *
   *      left (int):
   *        The scroll left position for the content window.
   *
   *      width (int):
   *        The scroll width of the content window.
   *
   *      height (int):
   *        The scroll height of the content window.
   */
  getFullPageBounds() {
    let {
      scrollMinX,
      scrollMinY,
      scrollWidth,
      scrollHeight,
      devicePixelRatio,
    } = this.#overlay.windowDimensions.dimensions;
    let rect = {
      left: scrollMinX,
      top: scrollMinY,
      right: scrollMinX + scrollWidth,
      bottom: scrollMinY + scrollHeight,
      width: scrollWidth,
      height: scrollHeight,
      devicePixelRatio,
    };
    return rect;
  }

  /**
   * Gets the visible page bounds for a visible screenshot.
   *
   * @returns { object }
   *   The device pixel ratio and a DOMRect of the current visible
   *   content bounds.
   *
   *   devicePixelRatio (float):
   *      The device pixel ratio of the screen
   *
   *   rect (object):
   *      top (int):
   *        The top position for the content window.
   *
   *      left (int):
   *        The left position for the content window.
   *
   *      width (int):
   *        The width of the content window.
   *
   *      height (int):
   *        The height of the content window.
   */
  getVisibleBounds() {
    let {
      pageScrollX,
      pageScrollY,
      clientWidth,
      clientHeight,
      devicePixelRatio,
    } = this.#overlay.windowDimensions.dimensions;
    let rect = {
      left: pageScrollX,
      top: pageScrollY,
      right: pageScrollX + clientWidth,
      bottom: pageScrollY + clientHeight,
      width: clientWidth,
      height: clientHeight,
      devicePixelRatio,
    };
    return rect;
  }
}

[ Dauer der Verarbeitung: 0.6 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge