forked from mirrors/gecko-dev
		
	Differential Revision: https://phabricator.services.mozilla.com/D61138 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			818 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			818 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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/. */
 | 
						|
 | 
						|
/* globals log, util, catcher, inlineSelectionCss, callBackground, assertIsTrusted, assertIsBlankDocument, buildSettings blobConverters */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
this.ui = (function() { // eslint-disable-line no-unused-vars
 | 
						|
  const exports = {};
 | 
						|
  const SAVE_BUTTON_HEIGHT = 50;
 | 
						|
 | 
						|
  const { watchFunction } = catcher;
 | 
						|
 | 
						|
  exports.isHeader = function(el) {
 | 
						|
    while (el) {
 | 
						|
      if (el.classList &&
 | 
						|
          (el.classList.contains("myshots-button") ||
 | 
						|
           el.classList.contains("visible") ||
 | 
						|
           el.classList.contains("full-page") ||
 | 
						|
           el.classList.contains("cancel-shot"))) {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
      el = el.parentNode;
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  };
 | 
						|
 | 
						|
  const substitutedCss = inlineSelectionCss.replace(/MOZ_EXTENSION([^"]+)/g, (match, filename) => {
 | 
						|
    return browser.extension.getURL(filename);
 | 
						|
  });
 | 
						|
 | 
						|
  function makeEl(tagName, className) {
 | 
						|
    if (!iframe.document()) {
 | 
						|
      throw new Error("Attempted makeEl before iframe was initialized");
 | 
						|
    }
 | 
						|
    const el = iframe.document().createElement(tagName);
 | 
						|
    if (className) {
 | 
						|
      el.className = className;
 | 
						|
    }
 | 
						|
    return el;
 | 
						|
  }
 | 
						|
 | 
						|
  function onResize() {
 | 
						|
    if (this.sizeTracking.windowDelayer) {
 | 
						|
      clearTimeout(this.sizeTracking.windowDelayer);
 | 
						|
    }
 | 
						|
    this.sizeTracking.windowDelayer = setTimeout(watchFunction(() => {
 | 
						|
      this.updateElementSize(true);
 | 
						|
    }), 50);
 | 
						|
  }
 | 
						|
 | 
						|
  function highContrastCheck(win) {
 | 
						|
    const doc = win.document;
 | 
						|
    const el = doc.createElement("div");
 | 
						|
    el.style.backgroundImage = "url('#')";
 | 
						|
    el.style.display = "none";
 | 
						|
    doc.body.appendChild(el);
 | 
						|
    const computed = win.getComputedStyle(el);
 | 
						|
    doc.body.removeChild(el);
 | 
						|
    // When Windows is in High Contrast mode, Firefox replaces background
 | 
						|
    // image URLs with the string "none".
 | 
						|
    return (computed && computed.backgroundImage === "none");
 | 
						|
  }
 | 
						|
 | 
						|
  const showMyShots = exports.showMyShots = function() {
 | 
						|
    return window.hasAnyShots;
 | 
						|
  };
 | 
						|
 | 
						|
  function initializeIframe() {
 | 
						|
    const el = document.createElement("iframe");
 | 
						|
    el.src = browser.extension.getURL("blank.html");
 | 
						|
    el.style.zIndex = "99999999999";
 | 
						|
    el.style.border = "none";
 | 
						|
    el.style.top = "0";
 | 
						|
    el.style.left = "0";
 | 
						|
    el.style.margin = "0";
 | 
						|
    el.scrolling = "no";
 | 
						|
    el.style.clip = "auto";
 | 
						|
    return el;
 | 
						|
  }
 | 
						|
 | 
						|
  const iframeSelection = exports.iframeSelection = {
 | 
						|
    element: null,
 | 
						|
    addClassName: "",
 | 
						|
    sizeTracking: {
 | 
						|
      timer: null,
 | 
						|
      windowDelayer: null,
 | 
						|
      lastHeight: null,
 | 
						|
      lastWidth: null,
 | 
						|
    },
 | 
						|
    document: null,
 | 
						|
    display(installHandlerOnDocument) {
 | 
						|
      return new Promise((resolve, reject) => {
 | 
						|
        if (!this.element) {
 | 
						|
          this.element = initializeIframe();
 | 
						|
          this.element.id = "firefox-screenshots-selection-iframe";
 | 
						|
          this.element.style.display = "none";
 | 
						|
          this.element.style.setProperty("position", "absolute", "important");
 | 
						|
          this.element.style.setProperty("background-color", "transparent");
 | 
						|
          this.element.setAttribute("role", "dialog");
 | 
						|
          this.updateElementSize();
 | 
						|
          this.element.addEventListener("load", watchFunction(() => {
 | 
						|
            this.document = this.element.contentDocument;
 | 
						|
            assertIsBlankDocument(this.document);
 | 
						|
            // eslint-disable-next-line no-unsanitized/property
 | 
						|
            this.document.documentElement.innerHTML = `
 | 
						|
               <head>
 | 
						|
                <style>${substitutedCss}</style>
 | 
						|
                <title></title>
 | 
						|
               </head>
 | 
						|
               <body></body>`;
 | 
						|
            installHandlerOnDocument(this.document);
 | 
						|
            if (this.addClassName) {
 | 
						|
              this.document.body.className = this.addClassName;
 | 
						|
            }
 | 
						|
            this.document.documentElement.dir = browser.i18n.getMessage("@@bidi_dir");
 | 
						|
            this.document.documentElement.lang = browser.i18n.getMessage("@@ui_locale");
 | 
						|
            resolve();
 | 
						|
          }), {once: true});
 | 
						|
          document.body.appendChild(this.element);
 | 
						|
        } else {
 | 
						|
          resolve();
 | 
						|
        }
 | 
						|
      });
 | 
						|
    },
 | 
						|
 | 
						|
    hide() {
 | 
						|
      this.element.style.display = "none";
 | 
						|
      this.stopSizeWatch();
 | 
						|
    },
 | 
						|
 | 
						|
    unhide() {
 | 
						|
      this.updateElementSize();
 | 
						|
      this.element.style.display = "block";
 | 
						|
      catcher.watchPromise(callBackground("sendEvent", "internal", "unhide-selection-frame"));
 | 
						|
      if (highContrastCheck(this.element.contentWindow)) {
 | 
						|
        this.element.contentDocument.body.classList.add("hcm");
 | 
						|
      }
 | 
						|
      this.initSizeWatch();
 | 
						|
      this.element.focus();
 | 
						|
    },
 | 
						|
 | 
						|
    updateElementSize(force) {
 | 
						|
      // Note: if someone sizes down the page, then the iframe will keep the
 | 
						|
      // document from naturally shrinking.  We use force to temporarily hide
 | 
						|
      // the element so that we can tell if the document shrinks
 | 
						|
      const visible = this.element.style.display !== "none";
 | 
						|
      if (force && visible) {
 | 
						|
        this.element.style.display = "none";
 | 
						|
      }
 | 
						|
      const height = Math.max(
 | 
						|
        document.documentElement.clientHeight,
 | 
						|
        document.body.clientHeight,
 | 
						|
        document.documentElement.scrollHeight,
 | 
						|
        document.body.scrollHeight);
 | 
						|
      if (height !== this.sizeTracking.lastHeight) {
 | 
						|
        this.sizeTracking.lastHeight = height;
 | 
						|
        this.element.style.height = height + "px";
 | 
						|
      }
 | 
						|
      // Do not use window.innerWidth since that includes the width of the
 | 
						|
      // scroll bar.
 | 
						|
      const width = Math.max(
 | 
						|
        document.documentElement.clientWidth,
 | 
						|
        document.body.clientWidth,
 | 
						|
        document.documentElement.scrollWidth,
 | 
						|
        document.body.scrollWidth);
 | 
						|
      if (width !== this.sizeTracking.lastWidth) {
 | 
						|
        this.sizeTracking.lastWidth = width;
 | 
						|
        this.element.style.width = width + "px";
 | 
						|
        // Since this frame has an absolute position relative to the parent
 | 
						|
        // document, if the parent document's body has a relative position and
 | 
						|
        // left and/or top not at 0, then the left and/or top of the parent
 | 
						|
        // document's body is not at (0, 0) of the viewport. That makes the
 | 
						|
        // frame shifted relative to the viewport. These margins negates that.
 | 
						|
        if (window.getComputedStyle(document.body).position === "relative") {
 | 
						|
          const docBoundingRect = document.documentElement.getBoundingClientRect();
 | 
						|
          const bodyBoundingRect = document.body.getBoundingClientRect();
 | 
						|
          this.element.style.marginLeft = `-${bodyBoundingRect.left - docBoundingRect.left}px`;
 | 
						|
          this.element.style.marginTop = `-${bodyBoundingRect.top - docBoundingRect.top}px`;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      if (force && visible) {
 | 
						|
        this.element.style.display = "block";
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    initSizeWatch() {
 | 
						|
      this.stopSizeWatch();
 | 
						|
      this.sizeTracking.timer = setInterval(watchFunction(this.updateElementSize.bind(this)), 2000);
 | 
						|
      window.addEventListener("resize", this.onResize, true);
 | 
						|
    },
 | 
						|
 | 
						|
    stopSizeWatch() {
 | 
						|
      if (this.sizeTracking.timer) {
 | 
						|
        clearTimeout(this.sizeTracking.timer);
 | 
						|
        this.sizeTracking.timer = null;
 | 
						|
      }
 | 
						|
      if (this.sizeTracking.windowDelayer) {
 | 
						|
        clearTimeout(this.sizeTracking.windowDelayer);
 | 
						|
        this.sizeTracking.windowDelayer = null;
 | 
						|
      }
 | 
						|
      this.sizeTracking.lastHeight = this.sizeTracking.lastWidth = null;
 | 
						|
      window.removeEventListener("resize", this.onResize, true);
 | 
						|
    },
 | 
						|
 | 
						|
    getElementFromPoint(x, y) {
 | 
						|
      this.element.style.pointerEvents = "none";
 | 
						|
      let el;
 | 
						|
      try {
 | 
						|
        el = document.elementFromPoint(x, y);
 | 
						|
      } finally {
 | 
						|
        this.element.style.pointerEvents = "";
 | 
						|
      }
 | 
						|
      return el;
 | 
						|
    },
 | 
						|
 | 
						|
    remove() {
 | 
						|
      this.stopSizeWatch();
 | 
						|
      util.removeNode(this.element);
 | 
						|
      this.element = this.document = null;
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  iframeSelection.onResize = watchFunction(assertIsTrusted(onResize.bind(iframeSelection)), true);
 | 
						|
 | 
						|
  const iframePreSelection = exports.iframePreSelection = {
 | 
						|
    element: null,
 | 
						|
    document: null,
 | 
						|
    display(installHandlerOnDocument, standardOverlayCallbacks) {
 | 
						|
      return new Promise((resolve, reject) => {
 | 
						|
        if (!this.element) {
 | 
						|
          this.element = initializeIframe();
 | 
						|
          this.element.id = "firefox-screenshots-preselection-iframe";
 | 
						|
          this.element.style.setProperty("position", "fixed", "important");
 | 
						|
          this.element.style.setProperty("background-color", "transparent");
 | 
						|
          this.element.style.width = "100%";
 | 
						|
          this.element.style.height = "100%";
 | 
						|
          this.element.setAttribute("role", "dialog");
 | 
						|
          this.element.addEventListener("load", watchFunction(() => {
 | 
						|
            this.document = this.element.contentDocument;
 | 
						|
            assertIsBlankDocument(this.document);
 | 
						|
            // eslint-disable-next-line no-unsanitized/property
 | 
						|
            this.document.documentElement.innerHTML = `
 | 
						|
               <head>
 | 
						|
                <link rel="localization" href="browser/screenshots.ftl">
 | 
						|
                <style>${substitutedCss}</style>
 | 
						|
                <title></title>
 | 
						|
               </head>
 | 
						|
               <body>
 | 
						|
                 <div class="preview-overlay precision-cursor">
 | 
						|
                   <div class="fixed-container">
 | 
						|
                     <div class="face-container">
 | 
						|
                       <div class="eye left"><div class="eyeball"></div></div>
 | 
						|
                       <div class="eye right"><div class="eyeball"></div></div>
 | 
						|
                       <div class="face"></div>
 | 
						|
                     </div>
 | 
						|
                     <div class="preview-instructions" data-l10n-id="screenshots-instructions"></div>
 | 
						|
                     <button class="cancel-shot" data-l10n-id="screenshots-cancel-button"></button>
 | 
						|
                     <div class="myshots-all-buttons-container">
 | 
						|
                       ${showMyShots() ? `
 | 
						|
                         <button class="myshots-button" tabindex="3" data-l10n-id="screenshots-my-shots-button"></button>
 | 
						|
                         <div class="spacer"></div>
 | 
						|
                       ` : ""}
 | 
						|
                       <button class="visible" tabindex="2" data-l10n-id="screenshots-save-visible-button"></button>
 | 
						|
                       <button class="full-page" tabindex="1" data-l10n-id="screenshots-save-page-button"></button>
 | 
						|
                     </div>
 | 
						|
                   </div>
 | 
						|
                 </div>
 | 
						|
               </body>`;
 | 
						|
            installHandlerOnDocument(this.document);
 | 
						|
            if (this.addClassName) {
 | 
						|
              this.document.body.className = this.addClassName;
 | 
						|
            }
 | 
						|
            this.document.documentElement.dir = browser.i18n.getMessage("@@bidi_dir");
 | 
						|
            this.document.documentElement.lang = browser.i18n.getMessage("@@ui_locale");
 | 
						|
            const overlay = this.document.querySelector(".preview-overlay");
 | 
						|
            if (showMyShots()) {
 | 
						|
              overlay.querySelector(".myshots-button").addEventListener(
 | 
						|
                "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onOpenMyShots)));
 | 
						|
            }
 | 
						|
            overlay.querySelector(".visible").addEventListener(
 | 
						|
              "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onClickVisible)));
 | 
						|
            overlay.querySelector(".full-page").addEventListener(
 | 
						|
              "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onClickFullPage)));
 | 
						|
            overlay.querySelector(".cancel-shot").addEventListener(
 | 
						|
              "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onClickCancel)));
 | 
						|
 | 
						|
            resolve();
 | 
						|
          }), {once: true});
 | 
						|
          document.body.appendChild(this.element);
 | 
						|
        } else {
 | 
						|
          resolve();
 | 
						|
        }
 | 
						|
      });
 | 
						|
    },
 | 
						|
 | 
						|
    hide() {
 | 
						|
      window.removeEventListener("scroll", watchFunction(assertIsTrusted(this.onScroll)));
 | 
						|
      window.removeEventListener("resize", this.onResize, true);
 | 
						|
      if (this.element) {
 | 
						|
        this.element.style.display = "none";
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    unhide() {
 | 
						|
      window.addEventListener("scroll", watchFunction(assertIsTrusted(this.onScroll)));
 | 
						|
      window.addEventListener("resize", this.onResize, true);
 | 
						|
      this.element.style.display = "block";
 | 
						|
      catcher.watchPromise(callBackground("sendEvent", "internal", "unhide-preselection-frame"));
 | 
						|
      if (highContrastCheck(this.element.contentWindow)) {
 | 
						|
        this.element.contentDocument.body.classList.add("hcm");
 | 
						|
      }
 | 
						|
      this.element.focus();
 | 
						|
    },
 | 
						|
 | 
						|
    onScroll() {
 | 
						|
      exports.HoverBox.hide();
 | 
						|
    },
 | 
						|
 | 
						|
    getElementFromPoint(x, y) {
 | 
						|
      this.element.style.pointerEvents = "none";
 | 
						|
      let el;
 | 
						|
      try {
 | 
						|
        el = document.elementFromPoint(x, y);
 | 
						|
      } finally {
 | 
						|
        this.element.style.pointerEvents = "";
 | 
						|
      }
 | 
						|
      return el;
 | 
						|
    },
 | 
						|
 | 
						|
    remove() {
 | 
						|
      this.hide();
 | 
						|
      util.removeNode(this.element);
 | 
						|
      this.element = null;
 | 
						|
      this.document = null;
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  let msgsPromise = callBackground("getStrings", [
 | 
						|
    "screenshots-cancel-button",
 | 
						|
    "screenshots-copy-button-tooltip",
 | 
						|
    "screenshots-download-button-tooltip",
 | 
						|
    "screenshots-copy-button",
 | 
						|
    "screenshots-download-button",
 | 
						|
  ]);
 | 
						|
 | 
						|
  const iframePreview = exports.iframePreview = {
 | 
						|
    element: null,
 | 
						|
    document: null,
 | 
						|
    display(installHandlerOnDocument, standardOverlayCallbacks) {
 | 
						|
      return new Promise((resolve, reject) => {
 | 
						|
        if (!this.element) {
 | 
						|
          this.element = initializeIframe();
 | 
						|
          this.element.id = "firefox-screenshots-preview-iframe";
 | 
						|
          this.element.style.display = "none";
 | 
						|
          this.element.style.setProperty("position", "fixed", "important");
 | 
						|
          this.element.style.setProperty("background-color", "transparent");
 | 
						|
          this.element.style.height = "100%";
 | 
						|
          this.element.style.width = "100%";
 | 
						|
          this.element.setAttribute("role", "dialog");
 | 
						|
          this.element.onload = watchFunction(() => {
 | 
						|
            msgsPromise.then(([cancelTitle, copyTitle, downloadTitle]) => {
 | 
						|
              this.document = this.element.contentDocument;
 | 
						|
              // eslint-disable-next-line no-unsanitized/property
 | 
						|
              this.document.documentElement.innerHTML = `
 | 
						|
                <head>
 | 
						|
                  <link rel="localization" href="browser/screenshots.ftl">
 | 
						|
                  <style>${substitutedCss}</style>
 | 
						|
                  <title></title>
 | 
						|
                </head>
 | 
						|
                <body>
 | 
						|
                  <div class="preview-overlay">
 | 
						|
                    <div class="preview-image">
 | 
						|
                      <div class="preview-buttons">
 | 
						|
                        <button class="highlight-button-cancel" title="${cancelTitle}">
 | 
						|
                          <img src="${browser.extension.getURL("icons/cancel.svg")}" />
 | 
						|
                        </button>
 | 
						|
                        <button class="highlight-button-copy" title="${copyTitle}">
 | 
						|
                          <img src="${browser.extension.getURL("icons/copy.svg")}" />
 | 
						|
                          <span data-l10n-id="screenshots-copy-button"/>
 | 
						|
                        </button>
 | 
						|
                        <button class="highlight-button-download" title="${downloadTitle}">
 | 
						|
                          <img src="${browser.extension.getURL("icons/download-white.svg")}" />
 | 
						|
                          <span data-l10n-id="screenshots-download-button"/>
 | 
						|
                      </button>
 | 
						|
                    </div>
 | 
						|
                      <div class="preview-image-wrapper"></div>
 | 
						|
                  </div>
 | 
						|
                  <div class="loader" style="display:none">
 | 
						|
                    <div class="loader-inner"></div>
 | 
						|
                  </div>
 | 
						|
                </div>
 | 
						|
              </body>`;
 | 
						|
 | 
						|
              installHandlerOnDocument(this.document);
 | 
						|
              this.document.documentElement.dir = browser.i18n.getMessage("@@bidi_dir");
 | 
						|
              this.document.documentElement.lang = browser.i18n.getMessage("@@ui_locale");
 | 
						|
 | 
						|
              const overlay = this.document.querySelector(".preview-overlay");
 | 
						|
              overlay.querySelector(".highlight-button-copy").addEventListener(
 | 
						|
                "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onCopyPreview)));
 | 
						|
              overlay.querySelector(".highlight-button-download").addEventListener(
 | 
						|
                "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onDownloadPreview)));
 | 
						|
              overlay.querySelector(".highlight-button-cancel").addEventListener(
 | 
						|
                "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.cancel)));
 | 
						|
              resolve();
 | 
						|
            });
 | 
						|
          });
 | 
						|
          document.body.appendChild(this.element);
 | 
						|
        } else {
 | 
						|
          resolve();
 | 
						|
        }
 | 
						|
      });
 | 
						|
    },
 | 
						|
 | 
						|
    hide() {
 | 
						|
      if (this.element) {
 | 
						|
        this.element.style.display = "none";
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    unhide() {
 | 
						|
      this.element.style.display = "block";
 | 
						|
      catcher.watchPromise(callBackground("sendEvent", "internal", "unhide-preview-frame"));
 | 
						|
      this.element.focus();
 | 
						|
    },
 | 
						|
 | 
						|
    showLoader() {
 | 
						|
      this.document.body.querySelector(".preview-image").style.display = "none";
 | 
						|
      this.document.body.querySelector(".loader").style.display = "";
 | 
						|
    },
 | 
						|
 | 
						|
    remove() {
 | 
						|
      this.hide();
 | 
						|
      util.removeNode(this.element);
 | 
						|
      this.element = null;
 | 
						|
      this.document = null;
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  iframePreSelection.onResize = watchFunction(onResize.bind(iframePreSelection), true);
 | 
						|
 | 
						|
  const iframe = exports.iframe = {
 | 
						|
    currentIframe: iframePreSelection,
 | 
						|
    display(installHandlerOnDocument, standardOverlayCallbacks) {
 | 
						|
      return iframeSelection.display(installHandlerOnDocument)
 | 
						|
        .then(() => iframePreSelection.display(installHandlerOnDocument, standardOverlayCallbacks))
 | 
						|
        .then(() => iframePreview.display(installHandlerOnDocument, standardOverlayCallbacks));
 | 
						|
    },
 | 
						|
 | 
						|
    hide() {
 | 
						|
      this.currentIframe.hide();
 | 
						|
    },
 | 
						|
 | 
						|
    unhide() {
 | 
						|
      this.currentIframe.unhide();
 | 
						|
    },
 | 
						|
 | 
						|
    showLoader() {
 | 
						|
      if (this.currentIframe.showLoader) {
 | 
						|
        this.currentIframe.showLoader();
 | 
						|
        this.currentIframe.unhide();
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    getElementFromPoint(x, y) {
 | 
						|
      return this.currentIframe.getElementFromPoint(x, y);
 | 
						|
    },
 | 
						|
 | 
						|
    remove() {
 | 
						|
      iframeSelection.remove();
 | 
						|
      iframePreSelection.remove();
 | 
						|
      iframePreview.remove();
 | 
						|
    },
 | 
						|
 | 
						|
    document() {
 | 
						|
      return this.currentIframe.document;
 | 
						|
    },
 | 
						|
 | 
						|
    useSelection() {
 | 
						|
      if (this.currentIframe === iframePreSelection || this.currentIframe === iframePreview) {
 | 
						|
        this.hide();
 | 
						|
      }
 | 
						|
      this.currentIframe = iframeSelection;
 | 
						|
      this.unhide();
 | 
						|
    },
 | 
						|
 | 
						|
    usePreSelection() {
 | 
						|
      if (this.currentIframe === iframeSelection || this.currentIframe === iframePreview) {
 | 
						|
        this.hide();
 | 
						|
      }
 | 
						|
      this.currentIframe = iframePreSelection;
 | 
						|
      this.unhide();
 | 
						|
    },
 | 
						|
 | 
						|
    usePreview() {
 | 
						|
      if (this.currentIframe === iframeSelection || this.currentIframe === iframePreSelection) {
 | 
						|
        this.hide();
 | 
						|
      }
 | 
						|
      this.currentIframe = iframePreview;
 | 
						|
      this.unhide();
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  const movements = ["topLeft", "top", "topRight", "left", "right", "bottomLeft", "bottom", "bottomRight"];
 | 
						|
 | 
						|
  /** Creates the selection box */
 | 
						|
  exports.Box = {
 | 
						|
 | 
						|
    async display(pos, callbacks) {
 | 
						|
      await this._createEl();
 | 
						|
      if (callbacks !== undefined && callbacks.cancel) {
 | 
						|
        // We use onclick here because we don't want addEventListener
 | 
						|
        // to add multiple event handlers to the same button
 | 
						|
        this.cancel.onclick = watchFunction(assertIsTrusted(callbacks.cancel));
 | 
						|
        this.cancel.style.display = "";
 | 
						|
      } else {
 | 
						|
        this.cancel.style.display = "none";
 | 
						|
      }
 | 
						|
      if (callbacks !== undefined && callbacks.save && this.save) {
 | 
						|
        // We use onclick here because we don't want addEventListener
 | 
						|
        // to add multiple event handlers to the same button
 | 
						|
        this.save.removeAttribute("disabled");
 | 
						|
        this.save.onclick = watchFunction(assertIsTrusted((e) => {
 | 
						|
          this.save.setAttribute("disabled", "true");
 | 
						|
          callbacks.save(e);
 | 
						|
        }));
 | 
						|
        this.save.style.display = "";
 | 
						|
      } else if (this.save) {
 | 
						|
        this.save.style.display = "none";
 | 
						|
      }
 | 
						|
      if (callbacks !== undefined && callbacks.download) {
 | 
						|
        this.download.removeAttribute("disabled");
 | 
						|
        this.download.onclick = watchFunction(assertIsTrusted((e) => {
 | 
						|
          this.download.setAttribute("disabled", true);
 | 
						|
          callbacks.download(e);
 | 
						|
          e.preventDefault();
 | 
						|
          e.stopPropagation();
 | 
						|
          return false;
 | 
						|
        }));
 | 
						|
        this.download.style.display = "";
 | 
						|
      } else {
 | 
						|
        this.download.style.display = "none";
 | 
						|
      }
 | 
						|
      if (callbacks !== undefined && callbacks.copy) {
 | 
						|
        this.copy.removeAttribute("disabled");
 | 
						|
        this.copy.onclick = watchFunction(assertIsTrusted((e) => {
 | 
						|
          this.copy.setAttribute("disabled", true);
 | 
						|
          callbacks.copy(e);
 | 
						|
          e.preventDefault();
 | 
						|
          e.stopPropagation();
 | 
						|
        }));
 | 
						|
        this.copy.style.display = "";
 | 
						|
      } else {
 | 
						|
        this.copy.style.display = "none";
 | 
						|
      }
 | 
						|
 | 
						|
      const winBottom = window.innerHeight;
 | 
						|
      const pageYOffset = window.pageYOffset;
 | 
						|
 | 
						|
      if ((pos.right - pos.left) < 78 || (pos.bottom - pos.top) < 78) {
 | 
						|
        this.el.classList.add("small-selection");
 | 
						|
      } else {
 | 
						|
        this.el.classList.remove("small-selection");
 | 
						|
      }
 | 
						|
 | 
						|
      // if the selection bounding box is w/in SAVE_BUTTON_HEIGHT px of the bottom of
 | 
						|
      // the window, flip controls into the box
 | 
						|
      if (pos.bottom > ((winBottom + pageYOffset) - SAVE_BUTTON_HEIGHT)) {
 | 
						|
        this.el.classList.add("bottom-selection");
 | 
						|
      } else {
 | 
						|
        this.el.classList.remove("bottom-selection");
 | 
						|
      }
 | 
						|
 | 
						|
      if (pos.right < 200) {
 | 
						|
        this.el.classList.add("left-selection");
 | 
						|
      } else {
 | 
						|
        this.el.classList.remove("left-selection");
 | 
						|
      }
 | 
						|
      this.el.style.top = `${pos.top}px`;
 | 
						|
      this.el.style.left = `${pos.left}px`;
 | 
						|
      this.el.style.height = `${pos.bottom - pos.top}px`;
 | 
						|
      this.el.style.width = `${pos.right - pos.left}px`;
 | 
						|
      this.bgTop.style.top = "0px";
 | 
						|
      this.bgTop.style.height = `${pos.top}px`;
 | 
						|
      this.bgTop.style.left = "0px";
 | 
						|
      this.bgTop.style.width = "100%";
 | 
						|
      this.bgBottom.style.top = `${pos.bottom}px`;
 | 
						|
      this.bgBottom.style.height = `calc(100vh - ${pos.bottom}px)`;
 | 
						|
      this.bgBottom.style.left = "0px";
 | 
						|
      this.bgBottom.style.width = "100%";
 | 
						|
      this.bgLeft.style.top = `${pos.top}px`;
 | 
						|
      this.bgLeft.style.height = `${pos.bottom - pos.top}px`;
 | 
						|
      this.bgLeft.style.left = "0px";
 | 
						|
      this.bgLeft.style.width = `${pos.left}px`;
 | 
						|
      this.bgRight.style.top = `${pos.top}px`;
 | 
						|
      this.bgRight.style.height = `${pos.bottom - pos.top}px`;
 | 
						|
      this.bgRight.style.left = `${pos.right}px`;
 | 
						|
      this.bgRight.style.width = `calc(100% - ${pos.right}px)`;
 | 
						|
    },
 | 
						|
 | 
						|
    // used to eventually move the download-only warning
 | 
						|
    // when a user ends scrolling or ends resizing a window
 | 
						|
    delayExecution(delay, cb) {
 | 
						|
      let timer;
 | 
						|
      return function() {
 | 
						|
        if (typeof timer !== "undefined") {
 | 
						|
          clearTimeout(timer);
 | 
						|
        }
 | 
						|
        timer = setTimeout(cb, delay);
 | 
						|
      };
 | 
						|
    },
 | 
						|
 | 
						|
    remove() {
 | 
						|
      for (const name of ["el", "bgTop", "bgLeft", "bgRight", "bgBottom"]) {
 | 
						|
        if (name in this) {
 | 
						|
          util.removeNode(this[name]);
 | 
						|
          this[name] = null;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    async _createEl() {
 | 
						|
      let boxEl = this.el;
 | 
						|
      if (boxEl) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      let [cancelTitle, copyTitle, downloadTitle, copyText, downloadText ] = await msgsPromise;
 | 
						|
      boxEl = makeEl("div", "highlight");
 | 
						|
      const buttons = makeEl("div", "highlight-buttons");
 | 
						|
      const cancel = makeEl("button", "highlight-button-cancel");
 | 
						|
      const cancelImg = makeEl("img");
 | 
						|
      cancelImg.src = browser.extension.getURL("icons/cancel.svg");
 | 
						|
      cancel.title = cancelTitle;
 | 
						|
      cancel.appendChild(cancelImg);
 | 
						|
      buttons.appendChild(cancel);
 | 
						|
 | 
						|
      const copy = makeEl("button", "highlight-button-copy");
 | 
						|
      copy.title = copyTitle;
 | 
						|
      const copyImg = makeEl("img");
 | 
						|
      const copyString = makeEl("span");
 | 
						|
      copyString.textContent = copyText;
 | 
						|
      copyImg.src = browser.extension.getURL("icons/copy.svg");
 | 
						|
      copy.appendChild(copyImg);
 | 
						|
      copy.appendChild(copyString);
 | 
						|
      buttons.appendChild(copy);
 | 
						|
 | 
						|
      const download = makeEl("button", "highlight-button-download");
 | 
						|
      const downloadImg = makeEl("img");
 | 
						|
      downloadImg.src = browser.extension.getURL("icons/download-white.svg");
 | 
						|
      download.appendChild(downloadImg);
 | 
						|
      download.append(downloadText);
 | 
						|
      download.title = downloadTitle;
 | 
						|
      buttons.appendChild(download);
 | 
						|
      this.cancel = cancel;
 | 
						|
      this.download = download;
 | 
						|
      this.copy = copy;
 | 
						|
 | 
						|
      boxEl.appendChild(buttons);
 | 
						|
      for (const name of movements) {
 | 
						|
        const elTarget = makeEl("div", "mover-target direction-" + name);
 | 
						|
        const elMover = makeEl("div", "mover");
 | 
						|
        elTarget.appendChild(elMover);
 | 
						|
        boxEl.appendChild(elTarget);
 | 
						|
      }
 | 
						|
      this.bgTop = makeEl("div", "bghighlight");
 | 
						|
      iframe.document().body.appendChild(this.bgTop);
 | 
						|
      this.bgLeft = makeEl("div", "bghighlight");
 | 
						|
      iframe.document().body.appendChild(this.bgLeft);
 | 
						|
      this.bgRight = makeEl("div", "bghighlight");
 | 
						|
      iframe.document().body.appendChild(this.bgRight);
 | 
						|
      this.bgBottom = makeEl("div", "bghighlight");
 | 
						|
      iframe.document().body.appendChild(this.bgBottom);
 | 
						|
      iframe.document().body.appendChild(boxEl);
 | 
						|
      this.el = boxEl;
 | 
						|
    },
 | 
						|
 | 
						|
    draggerDirection(target) {
 | 
						|
      while (target) {
 | 
						|
        if (target.nodeType === document.ELEMENT_NODE) {
 | 
						|
          if (target.classList.contains("mover-target")) {
 | 
						|
            for (const name of movements) {
 | 
						|
              if (target.classList.contains("direction-" + name)) {
 | 
						|
                return name;
 | 
						|
              }
 | 
						|
            }
 | 
						|
            catcher.unhandled(new Error("Surprising mover element"), {element: target.outerHTML});
 | 
						|
            log.warn("Got mover-target that wasn't a specific direction");
 | 
						|
          }
 | 
						|
        }
 | 
						|
        target = target.parentNode;
 | 
						|
      }
 | 
						|
      return null;
 | 
						|
    },
 | 
						|
 | 
						|
    isSelection(target) {
 | 
						|
      while (target) {
 | 
						|
        if (target.tagName === "BUTTON") {
 | 
						|
          return false;
 | 
						|
        }
 | 
						|
        if (target.nodeType === document.ELEMENT_NODE && target.classList.contains("highlight")) {
 | 
						|
          return true;
 | 
						|
        }
 | 
						|
        target = target.parentNode;
 | 
						|
      }
 | 
						|
      return false;
 | 
						|
    },
 | 
						|
 | 
						|
    isControl(target) {
 | 
						|
      while (target) {
 | 
						|
        if (target.nodeType === document.ELEMENT_NODE && target.classList.contains("highlight-buttons")) {
 | 
						|
          return true;
 | 
						|
        }
 | 
						|
        target = target.parentNode;
 | 
						|
      }
 | 
						|
      return false;
 | 
						|
    },
 | 
						|
 | 
						|
    clearSaveDisabled() {
 | 
						|
      if (!this.save) {
 | 
						|
        // Happens if we try to remove the disabled status after the worker
 | 
						|
        // has been shut down
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      this.save.removeAttribute("disabled");
 | 
						|
    },
 | 
						|
 | 
						|
    el: null,
 | 
						|
    boxTopEl: null,
 | 
						|
    boxLeftEl: null,
 | 
						|
    boxRightEl: null,
 | 
						|
    boxBottomEl: null,
 | 
						|
  };
 | 
						|
 | 
						|
  exports.HoverBox = {
 | 
						|
 | 
						|
    el: null,
 | 
						|
 | 
						|
    display(rect) {
 | 
						|
      if (!this.el) {
 | 
						|
        this.el = makeEl("div", "hover-highlight");
 | 
						|
        iframe.document().body.appendChild(this.el);
 | 
						|
      }
 | 
						|
      this.el.style.display = "";
 | 
						|
      this.el.style.top = (rect.top - 1) + "px";
 | 
						|
      this.el.style.left = (rect.left - 1) + "px";
 | 
						|
      this.el.style.width = (rect.right - rect.left + 2) + "px";
 | 
						|
      this.el.style.height = (rect.bottom - rect.top + 2) + "px";
 | 
						|
    },
 | 
						|
 | 
						|
    hide() {
 | 
						|
      if (this.el) {
 | 
						|
        this.el.style.display = "none";
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    remove() {
 | 
						|
      util.removeNode(this.el);
 | 
						|
      this.el = null;
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  exports.PixelDimensions = {
 | 
						|
    el: null,
 | 
						|
    xEl: null,
 | 
						|
    yEl: null,
 | 
						|
    display(xPos, yPos, x, y) {
 | 
						|
      if (!this.el) {
 | 
						|
        this.el = makeEl("div", "pixel-dimensions");
 | 
						|
        this.xEl = makeEl("div");
 | 
						|
        this.el.appendChild(this.xEl);
 | 
						|
        this.yEl = makeEl("div");
 | 
						|
        this.el.appendChild(this.yEl);
 | 
						|
        iframe.document().body.appendChild(this.el);
 | 
						|
      }
 | 
						|
      this.xEl.textContent = Math.round(x);
 | 
						|
      this.yEl.textContent = Math.round(y);
 | 
						|
      this.el.style.top = (yPos + 12) + "px";
 | 
						|
      this.el.style.left = (xPos + 12) + "px";
 | 
						|
    },
 | 
						|
    remove() {
 | 
						|
      util.removeNode(this.el);
 | 
						|
      this.el = this.xEl = this.yEl = null;
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  exports.Preview = {
 | 
						|
    display(dataUrl) {
 | 
						|
      const img = makeEl("IMG");
 | 
						|
      const imgBlob = blobConverters.dataUrlToBlob(dataUrl);
 | 
						|
      img.src = URL.createObjectURL(imgBlob);
 | 
						|
      iframe.document().querySelector(".preview-image-wrapper").appendChild(img);
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  /** Removes every UI this module creates */
 | 
						|
  exports.remove = function() {
 | 
						|
    for (const name in exports) {
 | 
						|
      if (name.startsWith("iframe")) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      if (typeof exports[name] === "object" && exports[name].remove) {
 | 
						|
        exports[name].remove();
 | 
						|
      }
 | 
						|
    }
 | 
						|
    exports.iframe.remove();
 | 
						|
  };
 | 
						|
 | 
						|
  exports.triggerDownload = function(url, filename) {
 | 
						|
    return catcher.watchPromise(callBackground("downloadShot", {url, filename}));
 | 
						|
  };
 | 
						|
 | 
						|
  exports.unload = exports.remove;
 | 
						|
 | 
						|
  return exports;
 | 
						|
})();
 | 
						|
null;
 |