%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /snap/firefox/current/usr/lib/firefox/browser/features/
Upload File :
Create Path :
Current File : //snap/firefox/current/usr/lib/firefox/browser/features/screenshots@mozilla.org.xpi

]PK
!<pHB�����Qexperiments/screenshots/api.jsPK
!<+g������]assertIsBlankDocument.jsPK
!<��|jhh�� assertIsTrusted.jsPK
!<B��zz���background/analytics.jsPK
!<	�������gbackground/communication.jsPK
!<��:�����$background/deviceInfo.jsPK
!<�BI�WW���)background/main.jsPK
!<�'<��
�
��Fbackground/selectorLoader.jsPK
!<Ȋ�:����-Tbackground/senderror.jsPK
!<V0<�
�
��-dbackground/startBackground.jsPK
!<�X�

��^rbackground/takeshot.jsPK
!<9�0A��
���|blank.htmlPK
!<yl����}blobConverters.jsPK
!<��L<P<P����build/inlineSelectionCss.jsPK
!<����e
e
���build/selection.jsPK
!<�S��`�`
����build/shot.jsPK
!<��pzz���?build/thumbnailGenerator.jsPK
!<{��
���Xcatcher.jsPK
!<�A����zaclipboard.jsPK
!<퉇J���idomainFromUrl.jsPK
!<��Lq��#���mexperiments/screenshots/schema.jsonPK
!<RLY���qlog.jsPK
!<��{��
���umanifest.jsonPK
!<�){9	���{moz.buildPK
!<4���RR��͂randomString.jsPK
!<
2&^~~��L�selector/callBackground.jsPK
!<���UU���selector/documentMetadata.jsPK
!<�|�H����selector/shooter.jsPK
!<���qq���selector/ui.jsPK
!<�3�`q`q��)selector/uicontrol.jsPK
!<|ҵ�������selector/util.jsPK
!<b���@@
��їsitehelper.jsPK  7PK
!<pHB���experiments/screenshots/api.js/* 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 browser, AppConstants, Services, ExtensionAPI, ExtensionCommon */

"use strict";

const TOPIC = "menuitem-screenshot-extension";

this.screenshots = class extends ExtensionAPI {
  getAPI(context) {
    let EventManager = ExtensionCommon.EventManager;

    return {
      experiments: {
        screenshots: {
          // If you are checking for 'nightly', also check for 'nightly-try'.
          //
          // Otherwise, just use the standard builds, but be aware of the many
          // non-standard options that also exist (as of August 2018).
          //
          // Standard builds:
          //   'esr' - ESR channel
          //   'release' - release channel
          //   'beta' - beta channel
          //   'nightly' - nightly channel
          // Non-standard / deprecated builds:
          //   'aurora' - deprecated aurora channel (still observed in dxr)
          //   'default' - local builds from source
          //   'nightly-try' - nightly Try builds (QA may occasionally need to test with these)
          getUpdateChannel() {
            return AppConstants.MOZ_UPDATE_CHANNEL;
          },
          isHistoryEnabled() {
            return Services.prefs.getBoolPref("places.history.enabled", true);
          },
          onScreenshotCommand: new EventManager({
            context,
            name: "experiments.screenshots.onScreenshotCommand",
            register: fire => {
              let observer = (subject, topic, data) => {
                let type = data;
                fire.sync(type);
              };
              Services.obs.addObserver(observer, TOPIC);
              return () => {
                Services.obs.removeObserver(observer, TOPIC);
              };
            },
          }).api(),
        },
      },
    };
  }
};
PK
!<+g����assertIsBlankDocument.js/* 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 browser */

/** For use inside an iframe onload function, throws an Error if iframe src is not blank.html

    Should be applied *inside* catcher.watchFunction
*/
this.assertIsBlankDocument = function assertIsBlankDocument(doc) {
  if (doc.documentURI !== browser.runtime.getURL("blank.html")) {
    const exc = new Error("iframe URL does not match expected blank.html");
    exc.foundURL = doc.documentURI;
    throw exc;
  }
};
null;
PK
!<��|jhhassertIsTrusted.js/* 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/. */

/** For use with addEventListener, assures that any events have event.isTrusted set to true
      https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted
    Should be applied *inside* catcher.watchFunction
*/
this.assertIsTrusted = function assertIsTrusted(handlerFunction) {
  return function (event) {
    if (!event) {
      const exc = new Error("assertIsTrusted did not get an event");
      exc.noPopup = true;
      throw exc;
    }
    if (!event.isTrusted) {
      const exc = new Error(`Received untrusted event (type: ${event.type})`);
      exc.noPopup = true;
      throw exc;
    }
    return handlerFunction.call(this, event);
  };
};
null;
PK
!<B��zzbackground/analytics.js/* 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 main, browser, catcher, log */

"use strict";

this.analytics = (function () {
  const exports = {};

  let telemetryEnabled;

  exports.incrementCount = function (scalar) {
    const allowedScalars = [
      "download",
      "upload",
      "copy",
      "visible",
      "full_page",
      "custom",
      "element",
    ];
    if (!allowedScalars.includes(scalar)) {
      const err = `incrementCount passed an unrecognized scalar ${scalar}`;
      log.warn(err);
      return Promise.resolve();
    }
    return browser.telemetry
      .scalarAdd(`screenshots.${scalar}`, 1)
      .catch(err => {
        log.warn(`incrementCount failed with error: ${err}`);
      });
  };

  exports.refreshTelemetryPref = function () {
    return browser.telemetry.canUpload().then(
      result => {
        telemetryEnabled = result;
      },
      error => {
        // If there's an error reading the pref, we should assume that we shouldn't send data
        telemetryEnabled = false;
        throw error;
      }
    );
  };

  exports.isTelemetryEnabled = function () {
    catcher.watchPromise(exports.refreshTelemetryPref());
    return telemetryEnabled;
  };

  return exports;
})();
PK
!<	�����background/communication.js/* 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 catcher, log */

"use strict";

this.communication = (function () {
  const exports = {};

  const registeredFunctions = {};

  exports.onMessage = catcher.watchFunction((req, sender, sendResponse) => {
    if (!(req.funcName in registeredFunctions)) {
      log.error(`Received unknown internal message type ${req.funcName}`);
      sendResponse({ type: "error", name: "Unknown message type" });
      return;
    }
    if (!Array.isArray(req.args)) {
      log.error("Received message with no .args list");
      sendResponse({ type: "error", name: "No .args" });
      return;
    }
    const func = registeredFunctions[req.funcName];
    let result;
    try {
      req.args.unshift(sender);
      result = func.apply(null, req.args);
    } catch (e) {
      log.error(`Error in ${req.funcName}:`, e, e.stack);
      // FIXME: should consider using makeError from catcher here:
      sendResponse({
        type: "error",
        message: e + "",
        errorCode: e.errorCode,
        popupMessage: e.popupMessage,
      });
      return;
    }
    if (result && result.then) {
      result
        .then(concreteResult => {
          sendResponse({ type: "success", value: concreteResult });
        })
        .catch(errorResult => {
          log.error(
            `Promise error in ${req.funcName}:`,
            errorResult,
            errorResult && errorResult.stack
          );
          sendResponse({
            type: "error",
            message: errorResult + "",
            errorCode: errorResult.errorCode,
            popupMessage: errorResult.popupMessage,
          });
        });
      return;
    }
    sendResponse({ type: "success", value: result });
  });

  exports.register = function (name, func) {
    registeredFunctions[name] = func;
  };

  return exports;
})();
PK
!<��:��background/deviceInfo.js/* 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 catcher, browser, navigator */

"use strict";

this.deviceInfo = (function () {
  const manifest = browser.runtime.getManifest();

  let platformInfo = {};
  catcher.watchPromise(
    browser.runtime.getPlatformInfo().then(info => {
      platformInfo = info;
    })
  );

  return function deviceInfo() {
    let match = navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9.]{1,1000})/);
    const chromeVersion = match ? match[1] : null;
    match = navigator.userAgent.match(/Firefox\/([0-9.]{1,1000})/);
    const firefoxVersion = match ? match[1] : null;
    const appName = chromeVersion ? "chrome" : "firefox";

    return {
      addonVersion: manifest.version,
      platform: platformInfo.os,
      architecture: platformInfo.arch,
      version: firefoxVersion || chromeVersion,
      // These don't seem to apply to Chrome:
      // build: system.build,
      // platformVersion: system.platformVersion,
      userAgent: navigator.userAgent,
      appVendor: appName,
      appName,
    };
  };
})();
PK
!<�BI�WWbackground/main.js/* 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 browser, getStrings, selectorLoader, analytics, communication, catcher, log, senderror, startBackground, blobConverters, startSelectionWithOnboarding */

"use strict";

this.main = (function () {
  const exports = {};

  const { incrementCount } = analytics;

  const manifest = browser.runtime.getManifest();
  let backend;

  exports.setBackend = function (newBackend) {
    backend = newBackend;
    backend = backend.replace(/\/*$/, "");
  };

  exports.getBackend = function () {
    return backend;
  };

  communication.register("getBackend", () => {
    return backend;
  });

  for (const permission of manifest.permissions) {
    if (/^https?:\/\//.test(permission)) {
      exports.setBackend(permission);
      break;
    }
  }

  function toggleSelector(tab) {
    return analytics
      .refreshTelemetryPref()
      .then(() => selectorLoader.toggle(tab.id))
      .catch(error => {
        if (
          error.message &&
          /Missing host permission for the tab/.test(error.message)
        ) {
          error.noReport = true;
        }
        error.popupMessage = "UNSHOOTABLE_PAGE";
        throw error;
      });
  }

  // This is called by startBackground.js, where is registered as a click
  // handler for the webextension page action.
  exports.onClicked = catcher.watchFunction(tab => {
    _startShotFlow(tab, "toolbar-button");
  });

  exports.onClickedContextMenu = catcher.watchFunction(tab => {
    _startShotFlow(tab, "context-menu");
  });

  exports.onShortcut = catcher.watchFunction(tab => {
    _startShotFlow(tab, "keyboard-shortcut");
  });

  const _startShotFlow = tab => {
    if (!tab) {
      // Not in a page/tab context, ignore
      return;
    }
    if (!urlEnabled(tab.url)) {
      senderror.showError({
        popupMessage: "UNSHOOTABLE_PAGE",
      });
      return;
    }

    catcher.watchPromise(
      toggleSelector(tab).catch(error => {
        throw error;
      })
    );
  };

  function urlEnabled(url) {
    // Allow screenshots on urls related to web pages in reader mode.
    if (url && url.startsWith("about:reader?url=")) {
      return true;
    }
    if (
      isShotOrMyShotPage(url) ||
      /^(?:about|data|moz-extension):/i.test(url) ||
      isBlacklistedUrl(url)
    ) {
      return false;
    }
    return true;
  }

  function isShotOrMyShotPage(url) {
    // It's okay to take a shot of any pages except shot pages and My Shots
    if (!url.startsWith(backend)) {
      return false;
    }
    const path = url
      .substr(backend.length)
      .replace(/^\/*/, "")
      .replace(/[?#].*/, "");
    if (path === "shots") {
      return true;
    }
    if (/^[^/]{1,4000}\/[^/]{1,4000}$/.test(path)) {
      // Blocks {:id}/{:domain}, but not /, /privacy, etc
      return true;
    }
    return false;
  }

  function isBlacklistedUrl(url) {
    // These specific domains are not allowed for general WebExtension permission reasons
    // Discussion: https://bugzilla.mozilla.org/show_bug.cgi?id=1310082
    // List of domains copied from: https://searchfox.org/mozilla-central/source/browser/app/permissions#18-19
    // Note we disable it here to be informative, the security check is done in WebExtension code
    const badDomains = ["testpilot.firefox.com"];
    let domain = url.replace(/^https?:\/\//i, "");
    domain = domain.replace(/\/.*/, "").replace(/:.*/, "");
    domain = domain.toLowerCase();
    return badDomains.includes(domain);
  }

  communication.register("getStrings", (sender, ids) => {
    return getStrings(ids.map(id => ({ id })));
  });

  communication.register("captureTelemetry", (sender, ...args) => {
    catcher.watchPromise(incrementCount(...args));
  });

  communication.register("openShot", async (sender, { copied }) => {
    if (copied) {
      const id = crypto.randomUUID();
      const [title, message] = await getStrings([
        { id: "screenshots-notification-link-copied-title" },
        { id: "screenshots-notification-link-copied-details" },
      ]);
      return browser.notifications.create(id, {
        type: "basic",
        iconUrl: "chrome://browser/content/screenshots/copied-notification.svg",
        title,
        message,
      });
    }
    return null;
  });

  communication.register("copyShotToClipboard", async (sender, blob) => {
    let buffer = await blobConverters.blobToArray(blob);
    await browser.clipboard.setImageData(buffer, blob.type.split("/", 2)[1]);

    const [title, message] = await getStrings([
      { id: "screenshots-notification-image-copied-title" },
      { id: "screenshots-notification-image-copied-details" },
    ]);

    catcher.watchPromise(incrementCount("copy"));
    return browser.notifications.create({
      type: "basic",
      iconUrl: "chrome://browser/content/screenshots/copied-notification.svg",
      title,
      message,
    });
  });

  communication.register("downloadShot", (sender, info) => {
    // 'data:' urls don't work directly, let's use a Blob
    // see http://stackoverflow.com/questions/40269862/save-data-uri-as-file-using-downloads-download-api
    const blob = blobConverters.dataUrlToBlob(info.url);
    const url = URL.createObjectURL(blob);
    let downloadId;
    const onChangedCallback = catcher.watchFunction(function (change) {
      if (!downloadId || downloadId !== change.id) {
        return;
      }
      if (change.state && change.state.current !== "in_progress") {
        URL.revokeObjectURL(url);
        browser.downloads.onChanged.removeListener(onChangedCallback);
      }
    });
    browser.downloads.onChanged.addListener(onChangedCallback);
    catcher.watchPromise(incrementCount("download"));
    return browser.windows.getLastFocused().then(windowInfo => {
      return browser.downloads
        .download({
          url,
          incognito: windowInfo.incognito,
          filename: info.filename,
        })
        .catch(error => {
          // We are not logging error message when user cancels download
          if (error && error.message && !error.message.includes("canceled")) {
            log.error(error.message);
          }
        })
        .then(id => {
          downloadId = id;
        });
    });
  });

  communication.register("abortStartShot", () => {
    // Note, we only show the error but don't report it, as we know that we can't
    // take shots of these pages:
    senderror.showError({
      popupMessage: "UNSHOOTABLE_PAGE",
    });
  });

  // A Screenshots page wants us to start/force onboarding
  communication.register("requestOnboarding", sender => {
    return startSelectionWithOnboarding(sender.tab);
  });

  communication.register("getPlatformOs", () => {
    return catcher.watchPromise(
      browser.runtime.getPlatformInfo().then(platformInfo => {
        return platformInfo.os;
      })
    );
  });

  // This allows the web site show notifications through sitehelper.js
  communication.register("showNotification", (sender, notification) => {
    return browser.notifications.create(notification);
  });

  return exports;
})();
PK
!<�'<��
�
background/selectorLoader.js/* 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 browser, catcher, communication, log, main */

"use strict";

// eslint-disable-next-line no-var
var global = this;

this.selectorLoader = (function () {
  const exports = {};

  // These modules are loaded in order, first standardScripts and then selectorScripts
  // The order is important due to dependencies
  const standardScripts = [
    "log.js",
    "catcher.js",
    "assertIsTrusted.js",
    "assertIsBlankDocument.js",
    "blobConverters.js",
    "background/selectorLoader.js",
    "selector/callBackground.js",
    "selector/util.js",
  ];

  const selectorScripts = [
    "clipboard.js",
    "build/selection.js",
    "build/shot.js",
    "randomString.js",
    "domainFromUrl.js",
    "build/inlineSelectionCss.js",
    "selector/documentMetadata.js",
    "selector/ui.js",
    "selector/shooter.js",
    "selector/uicontrol.js",
  ];

  exports.unloadIfLoaded = function (tabId) {
    return browser.tabs
      .executeScript(tabId, {
        code: "this.selectorLoader && this.selectorLoader.unloadModules()",
        runAt: "document_start",
      })
      .then(result => {
        return result && result[0];
      });
  };

  exports.testIfLoaded = function (tabId) {
    if (loadingTabs.has(tabId)) {
      return true;
    }
    return browser.tabs
      .executeScript(tabId, {
        code: "!!this.selectorLoader",
        runAt: "document_start",
      })
      .then(result => {
        return result && result[0];
      });
  };

  const loadingTabs = new Set();

  exports.loadModules = function (tabId) {
    loadingTabs.add(tabId);
    catcher.watchPromise(
      executeModules(tabId, standardScripts.concat(selectorScripts)).then(
        () => {
          loadingTabs.delete(tabId);
        }
      )
    );
  };

  function executeModules(tabId, scripts) {
    let lastPromise = Promise.resolve(null);
    scripts.forEach(file => {
      lastPromise = lastPromise.then(() => {
        return browser.tabs
          .executeScript(tabId, {
            file,
            runAt: "document_start",
          })
          .catch(error => {
            log.error("error in script:", file, error);
            error.scriptName = file;
            throw error;
          });
      });
    });
    return lastPromise.then(
      () => {
        log.debug("finished loading scripts:", scripts.join(" "));
      },
      error => {
        exports.unloadIfLoaded(tabId);
        catcher.unhandled(error);
        throw error;
      }
    );
  }

  exports.unloadModules = function () {
    const watchFunction = catcher.watchFunction;
    const allScripts = standardScripts.concat(selectorScripts);
    const moduleNames = allScripts.map(filename =>
      filename.replace(/^.*\//, "").replace(/\.js$/, "")
    );
    moduleNames.reverse();
    for (const moduleName of moduleNames) {
      const moduleObj = global[moduleName];
      if (moduleObj && moduleObj.unload) {
        try {
          watchFunction(moduleObj.unload)();
        } catch (e) {
          // ignore (watchFunction handles it)
        }
      }
      delete global[moduleName];
    }
    return true;
  };

  exports.toggle = function (tabId) {
    return exports.unloadIfLoaded(tabId).then(wasLoaded => {
      if (!wasLoaded) {
        exports.loadModules(tabId);
      }
      return !wasLoaded;
    });
  };

  return exports;
})();
null;
PK
!<Ȋ�:��background/senderror.js/* 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 startBackground, analytics, communication, catcher, log, browser, getStrings */

"use strict";

this.senderror = (function () {
  const exports = {};

  // Do not show an error more than every ERROR_TIME_LIMIT milliseconds:
  const ERROR_TIME_LIMIT = 3000;

  const messages = {
    REQUEST_ERROR: {
      titleKey: "screenshots-request-error-title",
      infoKey: "screenshots-request-error-details",
    },
    CONNECTION_ERROR: {
      titleKey: "screenshots-connection-error-title",
      infoKey: "screenshots-connection-error-details",
    },
    LOGIN_ERROR: {
      titleKey: "screenshots-request-error-title",
      infoKey: "screenshots-login-error-details",
    },
    LOGIN_CONNECTION_ERROR: {
      titleKey: "screenshots-connection-error-title",
      infoKey: "screenshots-connection-error-details",
    },
    UNSHOOTABLE_PAGE: {
      titleKey: "screenshots-unshootable-page-error-title",
      infoKey: "screenshots-unshootable-page-error-details",
    },
    EMPTY_SELECTION: {
      titleKey: "screenshots-empty-selection-error-title",
    },
    PRIVATE_WINDOW: {
      titleKey: "screenshots-private-window-error-title",
      infoKey: "screenshots-private-window-error-details",
    },
    generic: {
      titleKey: "screenshots-generic-error-title",
      infoKey: "screenshots-generic-error-details",
      showMessage: true,
    },
  };

  communication.register("reportError", (sender, error) => {
    catcher.unhandled(error);
  });

  let lastErrorTime;

  exports.showError = async function (error) {
    if (lastErrorTime && Date.now() - lastErrorTime < ERROR_TIME_LIMIT) {
      return;
    }
    lastErrorTime = Date.now();
    const id = crypto.randomUUID();
    let popupMessage = error.popupMessage || "generic";
    if (!messages[popupMessage]) {
      popupMessage = "generic";
    }

    let item = messages[popupMessage];
    if (!("title" in item)) {
      let keys = [{ id: item.titleKey }];
      if ("infoKey" in item) {
        keys.push({ id: item.infoKey });
      }

      [item.title, item.info] = await getStrings(keys);
    }

    let title = item.title;
    let message = item.info || "";
    const showMessage = item.showMessage;
    if (error.message && showMessage) {
      if (message) {
        message += "\n" + error.message;
      } else {
        message = error.message;
      }
    }
    if (Date.now() - startBackground.startTime > 5 * 1000) {
      browser.notifications.create(id, {
        type: "basic",
        // FIXME: need iconUrl for an image, see #2239
        title,
        message,
      });
    }
  };

  exports.reportError = function (e) {
    if (!analytics.isTelemetryEnabled()) {
      log.error("Telemetry disabled. Not sending critical error:", e);
      return;
    }
    const exception = new Error(e.message);
    exception.stack = e.multilineStack || e.stack || undefined;

    // To improve Sentry reporting & grouping, replace the
    // moz-extension://$uuid base URL with a generic resource:// URL.
    if (exception.stack) {
      exception.stack = exception.stack.replace(
        /moz-extension:\/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g,
        "resource://screenshots-addon"
      );
    }
    const rest = {};
    for (const attr in e) {
      if (
        ![
          "name",
          "message",
          "stack",
          "multilineStack",
          "popupMessage",
          "version",
          "sentryPublicDSN",
          "help",
          "fromMakeError",
        ].includes(attr)
      ) {
        rest[attr] = e[attr];
      }
    }
    rest.stack = exception.stack;
  };

  catcher.registerHandler(errorObj => {
    if (!errorObj.noPopup) {
      exports.showError(errorObj);
    }
    if (!errorObj.noReport) {
      exports.reportError(errorObj);
    }
  });

  return exports;
})();
PK
!<V0<�
�
background/startBackground.js/* 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 browser, main, communication, manifest */

/* This file handles:
     clicks on the WebExtension page action
     browser.contextMenus.onClicked
     browser.runtime.onMessage
   and loads the rest of the background page in response to those events, forwarding
   the events to main.onClicked, main.onClickedContextMenu, or communication.onMessage
*/

const startTime = Date.now();

// Set up to be able to use fluent:
(function () {
  let link = document.createElement("link");
  link.setAttribute("rel", "localization");
  link.setAttribute("href", "browser/screenshots.ftl");
  document.head.appendChild(link);

  link = document.createElement("link");
  link.setAttribute("rel", "localization");
  link.setAttribute("href", "toolkit/branding/brandings.ftl");
  document.head.appendChild(link);
})();

this.getStrings = async function (ids) {
  if (document.readyState != "complete") {
    await new Promise(resolve =>
      window.addEventListener("load", resolve, { once: true })
    );
  }
  await document.l10n.ready;
  return document.l10n.formatValues(ids);
};

let zoomFactor = 1;
this.getZoomFactor = function () {
  return zoomFactor;
};

this.startBackground = (function () {
  const exports = { startTime };

  const backgroundScripts = [
    "log.js",
    "catcher.js",
    "blobConverters.js",
    "background/selectorLoader.js",
    "background/communication.js",
    "background/senderror.js",
    "build/shot.js",
    "build/thumbnailGenerator.js",
    "background/analytics.js",
    "background/deviceInfo.js",
    "background/takeshot.js",
    "background/main.js",
  ];

  browser.experiments.screenshots.onScreenshotCommand.addListener(
    async type => {
      try {
        let [[tab]] = await Promise.all([
          browser.tabs.query({ currentWindow: true, active: true }),
          loadIfNecessary(),
        ]);
        zoomFactor = await browser.tabs.getZoom(tab.id);
        if (type === "contextMenu") {
          main.onClickedContextMenu(tab);
        } else if (type === "toolbar" || type === "quickaction") {
          main.onClicked(tab);
        } else if (type === "shortcut") {
          main.onShortcut(tab);
        }
      } catch (error) {
        console.error("Error loading Screenshots:", error);
      }
    }
  );

  browser.runtime.onMessage.addListener((req, sender, sendResponse) => {
    loadIfNecessary()
      .then(() => {
        return communication.onMessage(req, sender, sendResponse);
      })
      .catch(error => {
        console.error("Error loading Screenshots:", error);
      });
    return true;
  });

  let loadedPromise;

  function loadIfNecessary() {
    if (loadedPromise) {
      return loadedPromise;
    }
    loadedPromise = Promise.resolve();
    backgroundScripts.forEach(script => {
      loadedPromise = loadedPromise.then(() => {
        return new Promise((resolve, reject) => {
          const tag = document.createElement("script");
          tag.src = browser.runtime.getURL(script);
          tag.onload = () => {
            resolve();
          };
          tag.onerror = error => {
            const exc = new Error(`Error loading script: ${error.message}`);
            exc.scriptName = script;
            reject(exc);
          };
          document.head.appendChild(tag);
        });
      });
    });
    return loadedPromise;
  }

  return exports;
})();
PK
!<�X�

background/takeshot.js/* 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 browser, communication, getZoomFactor, shot, main, catcher, analytics, blobConverters, thumbnailGenerator */

"use strict";

this.takeshot = (function () {
  const exports = {};
  const MAX_CANVAS_DIMENSION = 32766;

  communication.register(
    "screenshotPage",
    (sender, selectedPos, screenshotType, devicePixelRatio) => {
      return screenshotPage(selectedPos, screenshotType, devicePixelRatio);
    }
  );

  communication.register("getZoomFactor", () => {
    return getZoomFactor();
  });

  function screenshotPage(pos, screenshotType, devicePixelRatio) {
    pos.width = Math.min(pos.right - pos.left, MAX_CANVAS_DIMENSION);
    pos.height = Math.min(pos.bottom - pos.top, MAX_CANVAS_DIMENSION);

    // If we are printing the full page or a truncated full page,
    // we must pass in this rectangle to preview the entire image
    let options = { format: "png" };
    if (
      screenshotType === "fullPage" ||
      screenshotType === "fullPageTruncated"
    ) {
      let rectangle = {
        x: 0,
        y: 0,
        width: pos.width,
        height: pos.height,
      };
      options.rect = rectangle;
      options.resetScrollPosition = true;
    } else if (screenshotType != "visible") {
      let rectangle = {
        x: pos.left,
        y: pos.top,
        width: pos.width,
        height: pos.height,
      };
      options.rect = rectangle;
    }

    return catcher.watchPromise(
      browser.tabs.captureTab(null, options).then(dataUrl => {
        const image = new Image();
        image.src = dataUrl;
        return new Promise(resolve => {
          image.onload = catcher.watchFunction(() => {
            const xScale = devicePixelRatio;
            const yScale = devicePixelRatio;
            const canvas = document.createElement("canvas");
            canvas.height = pos.height * yScale;
            canvas.width = pos.width * xScale;
            const context = canvas.getContext("2d");
            context.drawImage(
              image,
              0,
              0,
              pos.width * xScale,
              pos.height * yScale,
              0,
              0,
              pos.width * xScale,
              pos.height * yScale
            );
            const result = canvas.toDataURL();
            resolve(result);
          });
        });
      })
    );
  }

  return exports;
})();
PK
!<9�0A��
blank.html<!DOCTYPE html>

<!-- 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/. -->

<meta charset="utf-8" />
PK
!<yl�blobConverters.js/* 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/. */

this.blobConverters = (function () {
  const exports = {};

  exports.dataUrlToBlob = function (url) {
    const binary = atob(url.split(",", 2)[1]);
    let contentType = exports.getTypeFromDataUrl(url);
    if (contentType !== "image/png" && contentType !== "image/jpeg") {
      contentType = "image/png";
    }
    const data = Uint8Array.from(binary, char => char.charCodeAt(0));
    const blob = new Blob([data], { type: contentType });
    return blob;
  };

  exports.getTypeFromDataUrl = function (url) {
    let contentType = url.split(",", 1)[0];
    contentType = contentType.split(";", 1)[0];
    contentType = contentType.split(":", 2)[1];
    return contentType;
  };

  exports.blobToArray = function (blob) {
    return new Promise(resolve => {
      const reader = new FileReader();
      reader.addEventListener("loadend", function () {
        resolve(reader.result);
      });
      reader.readAsArrayBuffer(blob);
    });
  };

  exports.blobToDataUrl = function (blob) {
    return new Promise(resolve => {
      const reader = new FileReader();
      reader.addEventListener("loadend", function () {
        resolve(reader.result);
      });
      reader.readAsDataURL(blob);
    });
  };

  return exports;
})();
null;
PK
!<��L<P<Pbuild/inlineSelectionCss.js/* 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/. */

/* Created from build/server/static/css/inline-selection.css */
window.inlineSelectionCss = `
.button, .highlight-button-cancel, .highlight-button-download, .highlight-button-copy {
  display: flex;
  align-items: center;
  justify-content: center;
  column-gap: 8px;
  border: 0;
  border-radius: 3px;
  cursor: pointer;
  font-size: 16px;
  font-weight: 400;
  height: 40px;
  min-width: 40px;
  outline: none;
  padding: 0 10px;
  position: relative;
  text-align: center;
  text-decoration: none;
  transition: background 150ms cubic-bezier(0.07, 0.95, 0, 1), border 150ms cubic-bezier(0.07, 0.95, 0, 1);
  user-select: none;
  white-space: nowrap; }
  .button.hidden, .hidden.highlight-button-cancel, .hidden.highlight-button-download, .hidden.highlight-button-copy {
    display: none; }
  .button.small, .small.highlight-button-cancel, .small.highlight-button-download, .small.highlight-button-copy {
    height: 32px;
    line-height: 32px;
    padding: 0 8px; }
  .button.active, .active.highlight-button-cancel, .active.highlight-button-download, .active.highlight-button-copy {
    background-color: #dedede; }
  .button.tiny, .tiny.highlight-button-cancel, .tiny.highlight-button-download, .tiny.highlight-button-copy {
    font-size: 14px;
    height: 26px;
    border: 1px solid #c7c7c7; }
    .button.tiny:hover, .tiny.highlight-button-cancel:hover, .tiny.highlight-button-download:hover, .tiny.highlight-button-copy:hover, .button.tiny:focus, .tiny.highlight-button-cancel:focus, .tiny.highlight-button-download:focus, .tiny.highlight-button-copy:focus {
      background: #ededf0;
      border-color: #989898; }
    .button.tiny:active, .tiny.highlight-button-cancel:active, .tiny.highlight-button-download:active, .tiny.highlight-button-copy:active {
      background: #dedede;
      border-color: #989898; }
  .button.block-button, .block-button.highlight-button-cancel, .block-button.highlight-button-download, .block-button.highlight-button-copy {
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    border: 0;
    border-inline-end: 1px solid #c7c7c7;
    box-shadow: 0;
    border-radius: 0;
    flex-shrink: 0;
    font-size: 20px;
    height: 100px;
    line-height: 100%;
    overflow: hidden; }
    @media (max-width: 719px) {
      .button.block-button, .block-button.highlight-button-cancel, .block-button.highlight-button-download, .block-button.highlight-button-copy {
        justify-content: flex-start;
        font-size: 16px;
        height: 72px;
        margin-inline-end: 10px;
        padding: 0 5px; } }
    .button.block-button:hover, .block-button.highlight-button-cancel:hover, .block-button.highlight-button-download:hover, .block-button.highlight-button-copy:hover {
      background: #ededf0; }
    .button.block-button:active, .block-button.highlight-button-cancel:active, .block-button.highlight-button-download:active, .block-button.highlight-button-copy:active {
      background: #dedede; }
  .button.download, .download.highlight-button-cancel, .download.highlight-button-download, .download.highlight-button-copy, .button.edit, .edit.highlight-button-cancel, .edit.highlight-button-download, .edit.highlight-button-copy, .button.trash, .trash.highlight-button-cancel, .trash.highlight-button-download, .trash.highlight-button-copy, .button.share, .share.highlight-button-cancel, .share.highlight-button-download, .share.highlight-button-copy, .button.flag, .flag.highlight-button-cancel, .flag.highlight-button-download, .flag.highlight-button-copy {
    background-repeat: no-repeat;
    background-size: 50%;
    background-position: center;
    margin-inline-end: 10px;
    transition: background-color 150ms cubic-bezier(0.07, 0.95, 0, 1); }
  .button.download, .download.highlight-button-cancel, .download.highlight-button-download, .download.highlight-button-copy {
    background-image: url("chrome://browser/content/screenshots/download.svg"); }
    .button.download:hover, .download.highlight-button-cancel:hover, .download.highlight-button-download:hover, .download.highlight-button-copy:hover {
      background-color: #ededf0; }
    .button.download:active, .download.highlight-button-cancel:active, .download.highlight-button-download:active, .download.highlight-button-copy:active {
      background-color: #dedede; }
  .button.share, .share.highlight-button-cancel, .share.highlight-button-download, .share.highlight-button-copy {
    background-image: url("../img/icon-share.svg"); }
    .button.share:hover, .share.highlight-button-cancel:hover, .share.highlight-button-download:hover, .share.highlight-button-copy:hover {
      background-color: #ededf0; }
    .button.share.active, .share.active.highlight-button-cancel, .share.active.highlight-button-download, .share.active.highlight-button-copy, .button.share:active, .share.highlight-button-cancel:active, .share.highlight-button-download:active, .share.highlight-button-copy:active {
      background-color: #dedede; }
  .button.share.newicon, .share.newicon.highlight-button-cancel, .share.newicon.highlight-button-download, .share.newicon.highlight-button-copy {
    background-image: url("../img/icon-share-alternate.svg"); }
  .button.trash, .trash.highlight-button-cancel, .trash.highlight-button-download, .trash.highlight-button-copy {
    background-image: url("../img/icon-trash.svg"); }
    .button.trash:hover, .trash.highlight-button-cancel:hover, .trash.highlight-button-download:hover, .trash.highlight-button-copy:hover {
      background-color: #ededf0; }
    .button.trash:active, .trash.highlight-button-cancel:active, .trash.highlight-button-download:active, .trash.highlight-button-copy:active {
      background-color: #dedede; }
  .button.edit, .edit.highlight-button-cancel, .edit.highlight-button-download, .edit.highlight-button-copy {
    background-image: url("../img/icon-edit.svg"); }
    .button.edit:hover, .edit.highlight-button-cancel:hover, .edit.highlight-button-download:hover, .edit.highlight-button-copy:hover {
      background-color: #ededf0; }
    .button.edit:active, .edit.highlight-button-cancel:active, .edit.highlight-button-download:active, .edit.highlight-button-copy:active {
      background-color: #dedede; }

.app-body {
  background: #f9f9fa;
  color: #38383d; }
  .app-body a {
    color: #0a84ff; }

.highlight-color-scheme {
  background: #0a84ff;
  color: #fff; }
  .highlight-color-scheme a {
    color: #fff;
    text-decoration: underline; }

.alt-color-scheme {
  background: #38383d;
  color: #f9f9fa; }
  .alt-color-scheme h1 {
    color: #6f7fb6; }
  .alt-color-scheme a {
    color: #e1e1e6;
    text-decoration: underline; }

.button.primary, .primary.highlight-button-cancel, .highlight-button-download, .primary.highlight-button-copy {
  background-color: #0a84ff;
  color: #fff; }
  .button.primary:hover, .primary.highlight-button-cancel:hover, .highlight-button-download:hover, .primary.highlight-button-copy:hover, .button.primary:focus, .primary.highlight-button-cancel:focus, .highlight-button-download:focus, .primary.highlight-button-copy:focus {
    background-color: #0072e5; }
  .button.primary:active, .primary.highlight-button-cancel:active, .highlight-button-download:active, .primary.highlight-button-copy:active {
    background-color: #0065cc; }

.button.secondary, .highlight-button-cancel, .secondary.highlight-button-download, .highlight-button-copy {
  background-color: #f9f9fa;
  color: #38383d; }
  .button.secondary:hover, .highlight-button-cancel:hover, .secondary.highlight-button-download:hover, .highlight-button-copy:hover {
    background-color: #ededf0; }
  .button.secondary:active, .highlight-button-cancel:active, .secondary.highlight-button-download:active, .highlight-button-copy:active {
    background-color: #dedede; }

.button.transparent, .transparent.highlight-button-cancel, .transparent.highlight-button-download, .transparent.highlight-button-copy {
  background-color: transparent;
  color: #38383d; }
  .button.transparent:hover, .transparent.highlight-button-cancel:hover, .transparent.highlight-button-download:hover, .transparent.highlight-button-copy:hover {
    background-color: #ededf0; }
  .button.transparent:focus, .transparent.highlight-button-cancel:focus, .transparent.highlight-button-download:focus, .transparent.highlight-button-copy:focus, .button.transparent:active, .transparent.highlight-button-cancel:active, .transparent.highlight-button-download:active, .transparent.highlight-button-copy:active {
    background-color: #dedede; }

.button.warning, .warning.highlight-button-cancel, .warning.highlight-button-download, .warning.highlight-button-copy {
  color: #fff;
  background: #d92215; }
  .button.warning:hover, .warning.highlight-button-cancel:hover, .warning.highlight-button-download:hover, .warning.highlight-button-copy:hover, .button.warning:focus, .warning.highlight-button-cancel:focus, .warning.highlight-button-download:focus, .warning.highlight-button-copy:focus {
    background: #b81d12; }
  .button.warning:active, .warning.highlight-button-cancel:active, .warning.highlight-button-download:active, .warning.highlight-button-copy:active {
    background: #a11910; }

.subtitle-link {
  color: #0a84ff; }

.loader {
  background: rgba(12, 12, 13, 0.2);
  border-radius: 2px;
  height: 4px;
  overflow: hidden;
  position: relative;
  width: 200px; }

.loader-inner {
  animation: bounce infinite alternate 1250ms cubic-bezier(0.7, 0, 0.3, 1);
  background: #45a1ff;
  border-radius: 2px;
  height: 4px;
  transform: translateX(-40px);
  width: 50px; }

@keyframes bounce {
  0% {
    transform: translateX(-40px); }
  100% {
    transform: translate(190px); } }

@keyframes fade-in {
  0% {
    opacity: 0; }
  100% {
    opacity: 1; } }

@keyframes pop {
  0% {
    transform: scale(1); }
  97% {
    transform: scale(1.04); }
  100% {
    transform: scale(1); } }

@keyframes pulse {
  0% {
    opacity: 0.3;
    transform: scale(1); }
  70% {
    opacity: 0.25;
    transform: scale(1.04); }
  100% {
    opacity: 0.3;
    transform: scale(1); } }

@keyframes slide-left {
  0% {
    opacity: 0;
    transform: translate3d(160px, 0, 0); }
  100% {
    opacity: 1;
    transform: translate3d(0, 0, 0); } }

@keyframes bounce-in {
  0% {
    opacity: 0;
    transform: scale(1); }
  60% {
    opacity: 1;
    transform: scale(1.02); }
  100% {
    transform: scale(1); } }

.mover-target {
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
  position: absolute;
  z-index: 5; }

.highlight,
.mover-target {
  background-color: transparent;
  background-image: none; }

.mover-target,
.bghighlight {
  border: 0; }

.hover-highlight {
  animation: fade-in 125ms forwards cubic-bezier(0.07, 0.95, 0, 1);
  background: rgba(255, 255, 255, 0.2);
  border-radius: 1px;
  pointer-events: none;
  position: absolute;
  z-index: 10000000000; }
  .hover-highlight::before {
    border: 2px dashed rgba(255, 255, 255, 0.4);
    bottom: 0;
    content: "";
    inset-inline-start: 0;
    position: absolute;
    inset-inline-end: 0;
    top: 0; }
    /* When prefers contrast is fully supported, we should change these quereies to cover both high and low prefers contrast cases */
    @media (forced-colors: active) {
      .hover-highlight {
        background-color: white;
        opacity: 0.2; } }

.mover-target.direction-topLeft {
  cursor: nwse-resize;
  height: 60px;
  left: -30px;
  top: -30px;
  width: 60px; }

.mover-target.direction-top {
  cursor: ns-resize;
  height: 60px;
  inset-inline-start: 0;
  top: -30px;
  width: 100%;
  z-index: 4; }

.mover-target.direction-topRight {
  cursor: nesw-resize;
  height: 60px;
  right: -30px;
  top: -30px;
  width: 60px; }

.mover-target.direction-left {
  cursor: ew-resize;
  height: 100%;
  left: -30px;
  top: 0;
  width: 60px;
  z-index: 4; }

.mover-target.direction-right {
  cursor: ew-resize;
  height: 100%;
  right: -30px;
  top: 0;
  width: 60px;
  z-index: 4; }

.mover-target.direction-bottomLeft {
  bottom: -30px;
  cursor: nesw-resize;
  height: 60px;
  left: -30px;
  width: 60px; }

.mover-target.direction-bottom {
  bottom: -30px;
  cursor: ns-resize;
  height: 60px;
  inset-inline-start: 0;
  width: 100%;
  z-index: 4; }

.mover-target.direction-bottomRight {
  bottom: -30px;
  cursor: nwse-resize;
  height: 60px;
  right: -30px;
  width: 60px; }

.mover-target:hover .mover {
  transform: scale(1.05); }

.mover {
  background-color: #fff;
  border-radius: 50%;
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
  height: 16px;
  opacity: 1;
  position: relative;
  transition: transform 125ms cubic-bezier(0.07, 0.95, 0, 1);
  width: 16px; }
  .small-selection .mover {
    height: 10px;
    width: 10px; }

.direction-topLeft .mover,
.direction-left .mover,
.direction-bottomLeft .mover {
  left: -1px; }

.direction-topLeft .mover,
.direction-top .mover,
.direction-topRight .mover {
  top: -1px; }

.direction-topRight .mover,
.direction-right .mover,
.direction-bottomRight .mover {
  right: -1px; }

.direction-bottomRight .mover,
.direction-bottom .mover,
.direction-bottomLeft .mover {
  bottom: -1px; }

.bghighlight {
  background-color: rgba(0, 0, 0, 0.7);
  position: absolute;
  z-index: 9999999999; }
  /* When prefers contrast is fully supported, we should change these quereies to cover both high and low prefers contrast cases */
  @media (forced-colors: active) {
    .bghighlight {
      background-color: black;
      opacity: 0.7; } }

.preview-overlay {
  align-items: center;
  background-color: rgba(0, 0, 0, 0.7);
  display: flex;
  height: 100%;
  justify-content: center;
  inset-inline-start: 0;
  margin: 0;
  padding: 0;
  position: fixed;
  top: 0;
  width: 100%;
  z-index: 9999999999; }
  /* When prefers contrast is fully supported, we should change these quereies to cover both high and low prefers contrast cases */
  @media (forced-colors: active) {
    .preview-overlay {
      background-color: black;
      opacity: 0.7; } }

.precision-cursor {
  cursor: crosshair; }

.highlight {
  border-radius: 1px;
  border: 2px dashed rgba(255, 255, 255, 0.8);
  box-sizing: border-box;
  cursor: move;
  position: absolute;
  z-index: 9999999999; }
  /* When prefers contrast is fully supported, we should change these quereies to cover both high and low prefers contrast cases */
  @media (forced-colors: active) {
    .highlight {
      border: 2px dashed white;
      opacity: 1.0; } }

.highlight-buttons {
  display: flex;
  align-items: center;
  justify-content: center;
  bottom: -58px;
  position: absolute;
  inset-inline-end: 5px;
  z-index: 6; }
  .bottom-selection .highlight-buttons {
    bottom: 5px; }
  .left-selection .highlight-buttons {
    inset-inline-end: auto;
    inset-inline-start: 5px; }
  .highlight-buttons > button {
    box-shadow: 0 0 0 1px rgba(12, 12, 13, 0.1), 0 2px 8px rgba(12, 12, 13, 0.1); }

.highlight-button-cancel {
  margin: 5px;
  width: 40px; }

.highlight-button-download {
  margin: 5px;
  width: auto;
  font-size: 18px; }

.highlight-button-download img {
    height: 16px;
    width: 16px;
}

.highlight-button-download:-moz-locale-dir(rtl) {
  flex-direction: reverse;
}

.highlight-button-download img:-moz-locale-dir(ltr) {
  padding-inline-end: 8px;
}

.highlight-button-download img:-moz-locale-dir(rtl) {
  padding-inline-start: 8px;
}

.highlight-button-copy {
  margin: 5px;
  width: auto; }

.highlight-button-copy img {
    height: 16px;
    width: 16px;
}

.highlight-button-copy:-moz-locale-dir(rtl) {
  flex-direction: reverse;
}

.highlight-button-copy img:-moz-locale-dir(ltr) {
  padding-inline-end: 8px;
}

.highlight-button-copy img:-moz-locale-dir(rtl) {
  padding-inline-start: 8px;
}

.pixel-dimensions {
  position: absolute;
  pointer-events: none;
  font-weight: bold;
  font-family: system-ui;
  font-size: 70%;
  color: #000;
  text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff; }

.preview-buttons {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding-inline-end: 4px;
  inset-inline-end: 0;
  width: 100%;
  position: absolute;
  height: 60px;
  border-radius: 4px 4px 0 0;
  background: rgba(249, 249, 250, 0.8);
  top: 0;
  border: 1px solid rgba(249, 249, 250, 0.2);
  border-bottom: 0;
  box-sizing: border-box; }

.preview-image {
  display: flex;
  align-items: center;
  flex-direction: column;
  justify-content: center;
  margin: 24px auto;
  position: relative;
  max-width: 80%;
  max-height: 95%;
  text-align: center;
  animation-delay: 50ms;
  display: flex; }

.preview-image-wrapper {
  background: rgba(249, 249, 250, 0.8);
  border-radius: 0 0 4px 4px;
  display: block;
  height: auto;
  max-width: 100%;
  min-width: 320px;
  overflow-y: scroll;
  padding: 0 60px;
  margin-top: 60px;
  border: 1px solid rgba(249, 249, 250, 0.2);
  border-top: 0; }

.preview-image-wrapper > img {
  box-shadow: 0 0 0 1px rgba(12, 12, 13, 0.1), 0 2px 8px rgba(12, 12, 13, 0.1);
  height: auto;
  margin-bottom: 60px;
  max-width: 100%;
  width: 100%; }

.fixed-container {
  align-items: center;
  display: flex;
  flex-direction: column;
  height: 100vh;
  justify-content: center;
  inset-inline-start: 0;
  margin: 0;
  padding: 0;
  pointer-events: none;
  position: fixed;
  top: 0;
  width: 100%; }

.face-container {
  position: relative;
  width: 64px;
  height: 64px; }

.face {
  width: 62.4px;
  height: 62.4px;
  display: block;
  background-image: url("chrome://browser/content/screenshots/icon-welcome-face-without-eyes.svg"); }

.eye {
  background-color: #fff;
  width: 10.8px;
  height: 14.6px;
  position: absolute;
  border-radius: 100%;
  overflow: hidden;
  inset-inline-start: 16.4px;
  top: 19.8px; }

.eyeball {
  position: absolute;
  width: 6px;
  height: 6px;
  background-color: #000;
  border-radius: 50%;
  inset-inline-start: 2.4px;
  top: 4.3px;
  z-index: 10; }

.left {
  margin-inline-start: 0; }

.right {
  margin-inline-start: 20px; }

.preview-instructions {
  display: flex;
  align-items: center;
  justify-content: center;
  animation: pulse 125mm cubic-bezier(0.07, 0.95, 0, 1);
  color: #fff;
  font-family: system-ui;
  font-size: 24px;
  line-height: 32px;
  text-align: center;
  padding-top: 20px;
  width: 400px;
  user-select: none; }

.cancel-shot {
  background-color: transparent;
  cursor: pointer;
  outline: none;
  border-radius: 3px;
  border: 1px #9b9b9b solid;
  color: #fff;
  cursor: pointer;
  font-family: system-ui;
  font-size: 16px;
  margin-top: 40px;
  padding: 10px 25px;
  pointer-events: all; }

.all-buttons-container {
  display: flex;
  flex-direction: row-reverse;
  background: #f5f5f5;
  border-radius: 2px;
  box-sizing: border-box;
  height: 80px;
  padding: 8px;
  position: absolute;
  inset-inline-end: 8px;
  top: 8px;
  box-shadow: 0 0 0 1px rgba(12, 12, 13, 0.1), 0 2px 8px rgba(12, 12, 13, 0.1); }
  .all-buttons-container .spacer {
    background-color: #c9c9c9;
    flex: 0 0 1px;
    height: 80px;
    margin: 0 10px;
    position: relative;
    top: -8px; }
  .all-buttons-container button {
    display: flex;
    align-items: center;
    flex-direction: column;
    justify-content: flex-end;
    color: #3e3d40;
    background-color: #f5f5f5;
    background-position: center top;
    background-repeat: no-repeat;
    background-size: 46px 46px;
    border: 1px solid transparent;
    cursor: pointer;
    height: 100%;
    min-width: 90px;
    padding: 46px 5px 5px;
    pointer-events: all;
    transition: border 150ms cubic-bezier(0.07, 0.95, 0, 1), background-color 150ms cubic-bezier(0.07, 0.95, 0, 1);
    white-space: nowrap; }
    .all-buttons-container button:hover {
      background-color: #ebebeb;
      border: 1px solid #c7c7c7; }
    .all-buttons-container button:active {
      background-color: #dedede;
      border: 1px solid #989898; }
  .all-buttons-container .full-page {
    background-image: url("chrome://browser/content/screenshots/menu-fullpage.svg"); }
  .all-buttons-container .visible {
    background-image: url("chrome://browser/content/screenshots/menu-visible.svg"); }

@keyframes pulse {
  0% {
    transform: scale(1); }
  50% {
    transform: scale(1.06); }
  100% {
    transform: scale(1); } }

@keyframes fade-in {
  0% {
    opacity: 0; }
  100% {
    opacity: 1; } }

`;
null;
PK
!<����e
e
build/selection.js/* 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/. */

this.selection = (function () {
  let exports = {};
  class Selection {
    constructor(x1, y1, x2, y2) {
      this.x1 = x1;
      this.y1 = y1;
      this.x2 = x2;
      this.y2 = y2;
    }

    get top() {
      return Math.min(this.y1, this.y2);
    }
    set top(val) {
      if (this.y1 < this.y2) {
        this.y1 = val;
      } else {
        this.y2 = val;
      }
    }

    get bottom() {
      return Math.max(this.y1, this.y2);
    }
    set bottom(val) {
      if (this.y1 > this.y2) {
        this.y1 = val;
      } else {
        this.y2 = val;
      }
    }

    get left() {
      return Math.min(this.x1, this.x2);
    }
    set left(val) {
      if (this.x1 < this.x2) {
        this.x1 = val;
      } else {
        this.x2 = val;
      }
    }

    get right() {
      return Math.max(this.x1, this.x2);
    }
    set right(val) {
      if (this.x1 > this.x2) {
        this.x1 = val;
      } else {
        this.x2 = val;
      }
    }

    get width() {
      return Math.abs(this.x2 - this.x1);
    }
    get height() {
      return Math.abs(this.y2 - this.y1);
    }

    rect() {
      return {
        top: Math.floor(this.top),
        left: Math.floor(this.left),
        bottom: Math.floor(this.bottom),
        right: Math.floor(this.right),
      };
    }

    union(other) {
      return new Selection(
        Math.min(this.left, other.left),
        Math.min(this.top, other.top),
        Math.max(this.right, other.right),
        Math.max(this.bottom, other.bottom)
      );
    }

    /** Sort x1/x2 and y1/y2 so x1<x2, y1<y2 */
    sortCoords() {
      if (this.x1 > this.x2) {
        [this.x1, this.x2] = [this.x2, this.x1];
      }
      if (this.y1 > this.y2) {
        [this.y1, this.y2] = [this.y2, this.y1];
      }
    }

    clone() {
      return new Selection(this.x1, this.y1, this.x2, this.y2);
    }

    toJSON() {
      return {
        left: this.left,
        right: this.right,
        top: this.top,
        bottom: this.bottom,
      };
    }

    static getBoundingClientRect(el) {
      if (!el.getBoundingClientRect) {
        // Typically the <html> element or somesuch
        return null;
      }
      const rect = el.getBoundingClientRect();
      if (!rect) {
        return null;
      }
      return new Selection(rect.left, rect.top, rect.right, rect.bottom);
    }
  }

  if (typeof exports !== "undefined") {
    exports.Selection = Selection;
  }

  return exports;
})();
null;
PK
!<�S��`�`
build/shot.js/* 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 process, require */

this.shot = (function () {
  let exports = {}; // Note: in this library we can't use any "system" dependencies because this can be used from multiple
  // environments

  const isNode =
    typeof process !== "undefined" &&
    Object.prototype.toString.call(process) === "[object process]";
  const URL = (isNode && require("url").URL) || window.URL;

  /** Throws an error if the condition isn't true.  Any extra arguments after the condition
    are used as console.error() arguments. */
  function assert(condition, ...args) {
    if (condition) {
      return;
    }
    console.error("Failed assertion", ...args);
    throw new Error(`Failed assertion: ${args.join(" ")}`);
  }

  /** True if `url` is a valid URL */
  function isUrl(url) {
    try {
      const parsed = new URL(url);

      if (parsed.protocol === "view-source:") {
        return isUrl(url.substr("view-source:".length));
      }

      return true;
    } catch (e) {
      return false;
    }
  }

  function isValidClipImageUrl(url) {
    return isUrl(url) && !(url.indexOf(")") > -1);
  }

  function assertUrl(url) {
    if (!url) {
      throw new Error("Empty value is not URL");
    }
    if (!isUrl(url)) {
      const exc = new Error("Not a URL");
      exc.scheme = url.split(":")[0];
      throw exc;
    }
  }

  function isSecureWebUri(url) {
    return isUrl(url) && url.toLowerCase().startsWith("https");
  }

  function assertOrigin(url) {
    assertUrl(url);
    if (url.search(/^https?:/i) !== -1) {
      let newUrl = new URL(url);
      if (newUrl.pathname != "/") {
        throw new Error("Bad origin, might include path");
      }
    }
  }

  function originFromUrl(url) {
    if (!url) {
      return null;
    }
    if (url.search(/^https?:/i) === -1) {
      // Non-HTTP URLs don't have an origin
      return null;
    }
    try {
      let tryUrl = new URL(url);
      return tryUrl.origin;
    } catch {
      return null;
    }
  }

  /** Check if the given object has all of the required attributes, and no extra
    attributes exception those in optional */
  function checkObject(obj, required, optional) {
    if (typeof obj !== "object" || obj === null) {
      throw new Error(
        "Cannot check non-object: " +
          typeof obj +
          " that is " +
          JSON.stringify(obj)
      );
    }
    required = required || [];
    for (const attr of required) {
      if (!(attr in obj)) {
        return false;
      }
    }
    optional = optional || [];
    for (const attr in obj) {
      if (!required.includes(attr) && !optional.includes(attr)) {
        return false;
      }
    }
    return true;
  }

  /** Create a JSON object from a normal object, given the required and optional
    attributes (filtering out any other attributes).  Optional attributes are
    only kept when they are truthy. */
  function jsonify(obj, required, optional) {
    required = required || [];
    const result = {};
    for (const attr of required) {
      result[attr] = obj[attr];
    }
    optional = optional || [];
    for (const attr of optional) {
      if (obj[attr]) {
        result[attr] = obj[attr];
      }
    }
    return result;
  }

  /** True if the two objects look alike.  Null, undefined, and absent properties
    are all treated as equivalent.  Traverses objects and arrays */
  function deepEqual(a, b) {
    if ((a === null || a === undefined) && (b === null || b === undefined)) {
      return true;
    }
    if (typeof a !== "object" || typeof b !== "object") {
      return a === b;
    }
    if (Array.isArray(a)) {
      if (!Array.isArray(b)) {
        return false;
      }
      if (a.length !== b.length) {
        return false;
      }
      for (let i = 0; i < a.length; i++) {
        if (!deepEqual(a[i], b[i])) {
          return false;
        }
      }
    }
    if (Array.isArray(b)) {
      return false;
    }
    const seen = new Set();
    for (const attr of Object.keys(a)) {
      if (!deepEqual(a[attr], b[attr])) {
        return false;
      }
      seen.add(attr);
    }
    for (const attr of Object.keys(b)) {
      if (!seen.has(attr)) {
        if (!deepEqual(a[attr], b[attr])) {
          return false;
        }
      }
    }
    return true;
  }

  function makeRandomId() {
    // Note: this isn't for secure contexts, only for non-conflicting IDs
    let id = "";
    while (id.length < 12) {
      let num;
      if (!id) {
        num = Date.now() % Math.pow(36, 3);
      } else {
        num = Math.floor(Math.random() * Math.pow(36, 3));
      }
      id += num.toString(36);
    }
    return id;
  }

  class AbstractShot {
    constructor(backend, id, attrs) {
      attrs = attrs || {};
      assert(
        /^[a-zA-Z0-9]{1,4000}\/[a-z0-9._-]{1,4000}$/.test(id),
        "Bad ID (should be alphanumeric):",
        JSON.stringify(id)
      );
      this._backend = backend;
      this._id = id;
      this.origin = attrs.origin || null;
      this.fullUrl = attrs.fullUrl || null;
      if (!attrs.fullUrl && attrs.url) {
        console.warn("Received deprecated attribute .url");
        this.fullUrl = attrs.url;
      }
      if (this.origin && !isSecureWebUri(this.origin)) {
        this.origin = "";
      }
      if (this.fullUrl && !isSecureWebUri(this.fullUrl)) {
        this.fullUrl = "";
      }
      this.docTitle = attrs.docTitle || null;
      this.userTitle = attrs.userTitle || null;
      this.createdDate = attrs.createdDate || Date.now();
      this.siteName = attrs.siteName || null;
      this.images = [];
      if (attrs.images) {
        this.images = attrs.images.map(json => new this.Image(json));
      }
      this.openGraph = attrs.openGraph || null;
      this.twitterCard = attrs.twitterCard || null;
      this.documentSize = attrs.documentSize || null;
      this.thumbnail = attrs.thumbnail || null;
      this.abTests = attrs.abTests || null;
      this.firefoxChannel = attrs.firefoxChannel || null;
      this._clips = {};
      if (attrs.clips) {
        for (const clipId in attrs.clips) {
          const clip = attrs.clips[clipId];
          this._clips[clipId] = new this.Clip(this, clipId, clip);
        }
      }

      const isProd =
        typeof process !== "undefined" && process.env.NODE_ENV === "production";

      for (const attr in attrs) {
        if (
          attr !== "clips" &&
          attr !== "id" &&
          !this.REGULAR_ATTRS.includes(attr) &&
          !this.DEPRECATED_ATTRS.includes(attr)
        ) {
          if (isProd) {
            console.warn("Unexpected attribute: " + attr);
          } else {
            throw new Error("Unexpected attribute: " + attr);
          }
        } else if (attr === "id") {
          console.warn("passing id in attrs in AbstractShot constructor");
          console.trace();
          assert(attrs.id === this.id);
        }
      }
    }

    /** Update any and all attributes in the json object, with deep updating
      of `json.clips` */
    update(json) {
      const ALL_ATTRS = ["clips"].concat(this.REGULAR_ATTRS);
      assert(
        checkObject(json, [], ALL_ATTRS),
        "Bad attr to new Shot():",
        Object.keys(json)
      );
      for (const attr in json) {
        if (attr === "clips") {
          continue;
        }
        if (
          typeof json[attr] === "object" &&
          typeof this[attr] === "object" &&
          this[attr] !== null
        ) {
          let val = this[attr];
          if (val.toJSON) {
            val = val.toJSON();
          }
          if (!deepEqual(json[attr], val)) {
            this[attr] = json[attr];
          }
        } else if (json[attr] !== this[attr] && (json[attr] || this[attr])) {
          this[attr] = json[attr];
        }
      }
      if (json.clips) {
        for (const clipId in json.clips) {
          if (!json.clips[clipId]) {
            this.delClip(clipId);
          } else if (!this.getClip(clipId)) {
            this.setClip(clipId, json.clips[clipId]);
          } else if (
            !deepEqual(this.getClip(clipId).toJSON(), json.clips[clipId])
          ) {
            this.setClip(clipId, json.clips[clipId]);
          }
        }
      }
    }

    /** Returns a JSON version of this shot */
    toJSON() {
      const result = {};
      for (const attr of this.REGULAR_ATTRS) {
        let val = this[attr];
        if (val && val.toJSON) {
          val = val.toJSON();
        }
        result[attr] = val;
      }
      result.clips = {};
      for (const attr in this._clips) {
        result.clips[attr] = this._clips[attr].toJSON();
      }
      return result;
    }

    /** A more minimal JSON representation for creating indexes of shots */
    asRecallJson() {
      const result = { clips: {} };
      for (const attr of this.RECALL_ATTRS) {
        let val = this[attr];
        if (val && val.toJSON) {
          val = val.toJSON();
        }
        result[attr] = val;
      }
      for (const name of this.clipNames()) {
        result.clips[name] = this.getClip(name).toJSON();
      }
      return result;
    }

    get backend() {
      return this._backend;
    }

    get id() {
      return this._id;
    }

    get url() {
      return this.fullUrl || this.origin;
    }
    set url(val) {
      throw new Error(".url is read-only");
    }

    get fullUrl() {
      return this._fullUrl;
    }
    set fullUrl(val) {
      if (val) {
        assertUrl(val);
      }
      this._fullUrl = val || undefined;
    }

    get origin() {
      return this._origin;
    }
    set origin(val) {
      if (val) {
        assertOrigin(val);
      }
      this._origin = val || undefined;
    }

    get isOwner() {
      return this._isOwner;
    }

    set isOwner(val) {
      this._isOwner = val || undefined;
    }

    get filename() {
      let filenameTitle = this.title;
      const date = new Date(this.createdDate);
      /* eslint-disable no-control-regex */
      filenameTitle = filenameTitle
        .replace(/[\\/]/g, "_")
        .replace(/[\u200e\u200f\u202a-\u202e]/g, "")
        .replace(/[\x00-\x1f\x7f-\x9f:*?|"<>;,+=\[\]]+/g, " ")
        .replace(/^[\s\u180e.]+|[\s\u180e.]+$/g, "");
      /* eslint-enable no-control-regex */
      filenameTitle = filenameTitle.replace(/\s{1,4000}/g, " ");
      const currentDateTime = new Date(
        date.getTime() - date.getTimezoneOffset() * 60 * 1000
      ).toISOString();
      const filenameDate = currentDateTime.substring(0, 10);
      const filenameTime = currentDateTime.substring(11, 19).replace(/:/g, "-");
      let clipFilename = `Screenshot ${filenameDate} at ${filenameTime} ${filenameTitle}`;

      // Crop the filename size at less than 246 bytes, so as to leave
      // room for the extension and an ellipsis [...]. Note that JS
      // strings are UTF16 but the filename will be converted to UTF8
      // when saving which could take up more space, and we want a
      // maximum of 255 bytes (not characters). Here, we iterate
      // and crop at shorter and shorter points until we fit into
      // 255 bytes.
      let suffix = "";
      for (let cropSize = 246; cropSize >= 0; cropSize -= 32) {
        if (new Blob([clipFilename]).size > 246) {
          clipFilename = clipFilename.substring(0, cropSize);
          suffix = "[...]";
        } else {
          break;
        }
      }

      clipFilename += suffix;

      const clip = this.getClip(this.clipNames()[0]);
      let extension = ".png";
      if (clip && clip.image && clip.image.type) {
        if (clip.image.type === "jpeg") {
          extension = ".jpg";
        }
      }
      return clipFilename + extension;
    }

    get urlDisplay() {
      if (!this.url) {
        return null;
      }
      if (/^https?:\/\//i.test(this.url)) {
        let txt = this.url;
        txt = txt.replace(/^[a-z]{1,4000}:\/\//i, "");
        txt = txt.replace(/\/.{0,4000}/, "");
        txt = txt.replace(/^www\./i, "");
        return txt;
      } else if (this.url.startsWith("data:")) {
        return "data:url";
      }
      let txt = this.url;
      txt = txt.replace(/\?.{0,4000}/, "");
      return txt;
    }

    get viewUrl() {
      const url = this.backend + "/" + this.id;
      return url;
    }

    get creatingUrl() {
      let url = `${this.backend}/creating/${this.id}`;
      url += `?title=${encodeURIComponent(this.title || "")}`;
      url += `&url=${encodeURIComponent(this.url)}`;
      return url;
    }

    get jsonUrl() {
      return this.backend + "/data/" + this.id;
    }

    get oembedUrl() {
      return this.backend + "/oembed?url=" + encodeURIComponent(this.viewUrl);
    }

    get docTitle() {
      return this._title;
    }
    set docTitle(val) {
      assert(val === null || typeof val === "string", "Bad docTitle:", val);
      this._title = val;
    }

    get openGraph() {
      return this._openGraph || null;
    }
    set openGraph(val) {
      assert(val === null || typeof val === "object", "Bad openGraph:", val);
      if (val) {
        assert(
          checkObject(val, [], this._OPENGRAPH_PROPERTIES),
          "Bad attr to openGraph:",
          Object.keys(val)
        );
        this._openGraph = val;
      } else {
        this._openGraph = null;
      }
    }

    get twitterCard() {
      return this._twitterCard || null;
    }
    set twitterCard(val) {
      assert(val === null || typeof val === "object", "Bad twitterCard:", val);
      if (val) {
        assert(
          checkObject(val, [], this._TWITTERCARD_PROPERTIES),
          "Bad attr to twitterCard:",
          Object.keys(val)
        );
        this._twitterCard = val;
      } else {
        this._twitterCard = null;
      }
    }

    get userTitle() {
      return this._userTitle;
    }
    set userTitle(val) {
      assert(val === null || typeof val === "string", "Bad userTitle:", val);
      this._userTitle = val;
    }

    get title() {
      // FIXME: we shouldn't support both openGraph.title and ogTitle
      const ogTitle = this.openGraph && this.openGraph.title;
      const twitterTitle = this.twitterCard && this.twitterCard.title;
      let title =
        this.userTitle || ogTitle || twitterTitle || this.docTitle || this.url;
      if (Array.isArray(title)) {
        title = title[0];
      }
      if (!title) {
        title = "Screenshot";
      }
      return title;
    }

    get createdDate() {
      return this._createdDate;
    }
    set createdDate(val) {
      assert(val === null || typeof val === "number", "Bad createdDate:", val);
      this._createdDate = val;
    }

    clipNames() {
      const names = Object.getOwnPropertyNames(this._clips);
      names.sort(function (a, b) {
        return a.sortOrder < b.sortOrder ? 1 : 0;
      });
      return names;
    }
    getClip(name) {
      return this._clips[name];
    }
    addClip(val) {
      const name = makeRandomId();
      this.setClip(name, val);
      return name;
    }
    setClip(name, val) {
      const clip = new this.Clip(this, name, val);
      this._clips[name] = clip;
    }
    delClip(name) {
      if (!this._clips[name]) {
        throw new Error("No existing clip with id: " + name);
      }
      delete this._clips[name];
    }
    delAllClips() {
      this._clips = {};
    }
    biggestClipSortOrder() {
      let biggest = 0;
      for (const clipId in this._clips) {
        biggest = Math.max(biggest, this._clips[clipId].sortOrder);
      }
      return biggest;
    }
    updateClipUrl(clipId, clipUrl) {
      const clip = this.getClip(clipId);
      if (clip && clip.image) {
        clip.image.url = clipUrl;
      } else {
        console.warn("Tried to update the url of a clip with no image:", clip);
      }
    }

    get siteName() {
      return this._siteName || null;
    }
    set siteName(val) {
      assert(typeof val === "string" || !val);
      this._siteName = val;
    }

    get documentSize() {
      return this._documentSize;
    }
    set documentSize(val) {
      assert(typeof val === "object" || !val);
      if (val) {
        assert(
          checkObject(
            val,
            ["height", "width"],
            "Bad attr to documentSize:",
            Object.keys(val)
          )
        );
        assert(typeof val.height === "number");
        assert(typeof val.width === "number");
        this._documentSize = val;
      } else {
        this._documentSize = null;
      }
    }

    get thumbnail() {
      return this._thumbnail;
    }
    set thumbnail(val) {
      assert(typeof val === "string" || !val);
      if (val) {
        assert(isUrl(val));
        this._thumbnail = val;
      } else {
        this._thumbnail = null;
      }
    }

    get abTests() {
      return this._abTests;
    }
    set abTests(val) {
      if (val === null || val === undefined) {
        this._abTests = null;
        return;
      }
      assert(
        typeof val === "object",
        "abTests should be an object, not:",
        typeof val
      );
      assert(!Array.isArray(val), "abTests should not be an Array");
      for (const name in val) {
        assert(
          val[name] && typeof val[name] === "string",
          `abTests.${name} should be a string:`,
          typeof val[name]
        );
      }
      this._abTests = val;
    }

    get firefoxChannel() {
      return this._firefoxChannel;
    }
    set firefoxChannel(val) {
      if (val === null || val === undefined) {
        this._firefoxChannel = null;
        return;
      }
      assert(
        typeof val === "string",
        "firefoxChannel should be a string, not:",
        typeof val
      );
      this._firefoxChannel = val;
    }
  }

  AbstractShot.prototype.REGULAR_ATTRS = `
origin fullUrl docTitle userTitle createdDate images
siteName openGraph twitterCard documentSize
thumbnail abTests firefoxChannel
`.split(/\s+/g);

  // Attributes that will be accepted in the constructor, but ignored/dropped
  AbstractShot.prototype.DEPRECATED_ATTRS = `
microdata history ogTitle createdDevice head body htmlAttrs bodyAttrs headAttrs
readable hashtags comments showPage isPublic resources url
fullScreenThumbnail favicon
`.split(/\s+/g);

  AbstractShot.prototype.RECALL_ATTRS = `
url docTitle userTitle createdDate openGraph twitterCard images thumbnail
`.split(/\s+/g);

  AbstractShot.prototype._OPENGRAPH_PROPERTIES = `
title type url image audio description determiner locale site_name video
image:secure_url image:type image:width image:height
video:secure_url video:type video:width image:height
audio:secure_url audio:type
article:published_time article:modified_time article:expiration_time article:author article:section article:tag
book:author book:isbn book:release_date book:tag
profile:first_name profile:last_name profile:username profile:gender
`.split(/\s+/g);

  AbstractShot.prototype._TWITTERCARD_PROPERTIES = `
card site title description image
player player:width player:height player:stream player:stream:content_type
`.split(/\s+/g);

  /** Represents one found image in the document (not a clip) */
  class _Image {
    // FIXME: either we have to notify the shot of updates, or make
    // this read-only
    constructor(json) {
      assert(typeof json === "object", "Clip Image given a non-object", json);
      assert(
        checkObject(json, ["url"], ["dimensions", "title", "alt"]),
        "Bad attrs for Image:",
        Object.keys(json)
      );
      assert(isUrl(json.url), "Bad Image url:", json.url);
      this.url = json.url;
      assert(
        !json.dimensions ||
          (typeof json.dimensions.x === "number" &&
            typeof json.dimensions.y === "number"),
        "Bad Image dimensions:",
        json.dimensions
      );
      this.dimensions = json.dimensions;
      assert(
        typeof json.title === "string" || !json.title,
        "Bad Image title:",
        json.title
      );
      this.title = json.title;
      assert(
        typeof json.alt === "string" || !json.alt,
        "Bad Image alt:",
        json.alt
      );
      this.alt = json.alt;
    }

    toJSON() {
      return jsonify(this, ["url"], ["dimensions"]);
    }
  }

  AbstractShot.prototype.Image = _Image;

  /** Represents a clip, either a text or image clip */
  class _Clip {
    constructor(shot, id, json) {
      this._shot = shot;
      assert(
        checkObject(json, ["createdDate", "image"], ["sortOrder"]),
        "Bad attrs for Clip:",
        Object.keys(json)
      );
      assert(typeof id === "string" && id, "Bad Clip id:", id);
      this._id = id;
      this.createdDate = json.createdDate;
      if ("sortOrder" in json) {
        assert(
          typeof json.sortOrder === "number" || !json.sortOrder,
          "Bad Clip sortOrder:",
          json.sortOrder
        );
      }
      if ("sortOrder" in json) {
        this.sortOrder = json.sortOrder;
      } else {
        const biggestOrder = shot.biggestClipSortOrder();
        this.sortOrder = biggestOrder + 100;
      }
      this.image = json.image;
    }

    toString() {
      return `[Shot Clip id=${this.id} sortOrder=${this.sortOrder} image ${this.image.dimensions.x}x${this.image.dimensions.y}]`;
    }

    toJSON() {
      return jsonify(this, ["createdDate"], ["sortOrder", "image"]);
    }

    get id() {
      return this._id;
    }

    get createdDate() {
      return this._createdDate;
    }
    set createdDate(val) {
      assert(typeof val === "number" || !val, "Bad Clip createdDate:", val);
      this._createdDate = val;
    }

    get image() {
      return this._image;
    }
    set image(image) {
      if (!image) {
        this._image = undefined;
        return;
      }
      assert(
        checkObject(
          image,
          ["url"],
          ["dimensions", "text", "location", "captureType", "type"]
        ),
        "Bad attrs for Clip Image:",
        Object.keys(image)
      );
      assert(isValidClipImageUrl(image.url), "Bad Clip image URL:", image.url);
      assert(
        image.captureType === "madeSelection" ||
          image.captureType === "selection" ||
          image.captureType === "visible" ||
          image.captureType === "auto" ||
          image.captureType === "fullPage" ||
          image.captureType === "fullPageTruncated" ||
          !image.captureType,
        "Bad image.captureType:",
        image.captureType
      );
      assert(
        typeof image.text === "string" || !image.text,
        "Bad Clip image text:",
        image.text
      );
      if (image.dimensions) {
        assert(
          typeof image.dimensions.x === "number" &&
            typeof image.dimensions.y === "number",
          "Bad Clip image dimensions:",
          image.dimensions
        );
      }
      if (image.type) {
        assert(
          image.type === "png" || image.type === "jpeg",
          "Unexpected image type:",
          image.type
        );
      }
      assert(
        image.location &&
          typeof image.location.left === "number" &&
          typeof image.location.right === "number" &&
          typeof image.location.top === "number" &&
          typeof image.location.bottom === "number",
        "Bad Clip image pixel location:",
        image.location
      );
      if (
        image.location.topLeftElement ||
        image.location.topLeftOffset ||
        image.location.bottomRightElement ||
        image.location.bottomRightOffset
      ) {
        assert(
          typeof image.location.topLeftElement === "string" &&
            image.location.topLeftOffset &&
            typeof image.location.topLeftOffset.x === "number" &&
            typeof image.location.topLeftOffset.y === "number" &&
            typeof image.location.bottomRightElement === "string" &&
            image.location.bottomRightOffset &&
            typeof image.location.bottomRightOffset.x === "number" &&
            typeof image.location.bottomRightOffset.y === "number",
          "Bad Clip image element location:",
          image.location
        );
      }
      this._image = image;
    }

    isDataUrl() {
      if (this.image) {
        return this.image.url.startsWith("data:");
      }
      return false;
    }

    get sortOrder() {
      return this._sortOrder || null;
    }
    set sortOrder(val) {
      assert(typeof val === "number" || !val, "Bad Clip sortOrder:", val);
      this._sortOrder = val;
    }
  }

  AbstractShot.prototype.Clip = _Clip;

  if (typeof exports !== "undefined") {
    exports.AbstractShot = AbstractShot;
    exports.originFromUrl = originFromUrl;
    exports.isValidClipImageUrl = isValidClipImageUrl;
  }

  return exports;
})();
null;
PK
!<��pzzbuild/thumbnailGenerator.js/* 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/. */

this.thumbnailGenerator = (function () {
  let exports = {}; // This is used in webextension/background/takeshot.js,
  // server/src/pages/shot/controller.js, and
  // server/scr/pages/shotindex/view.js. It is used in a browser
  // environment.

  // Resize down 1/2 at a time produces better image quality.
  // Not quite as good as using a third-party filter (which will be
  // slower), but good enough.
  const maxResizeScaleFactor = 0.5;

  // The shot will be scaled or cropped down to 210px on x, and cropped or
  // scaled down to a maximum of 280px on y.
  // x: 210
  // y: <= 280
  const maxThumbnailWidth = 210;
  const maxThumbnailHeight = 280;

  /**
   * @param {int} imageHeight Height in pixels of the original image.
   * @param {int} imageWidth Width in pixels of the original image.
   * @returns {width, height, scaledX, scaledY}
   */
  function getThumbnailDimensions(imageWidth, imageHeight) {
    const displayAspectRatio = 3 / 4;
    const imageAspectRatio = imageWidth / imageHeight;
    let thumbnailImageWidth, thumbnailImageHeight;
    let scaledX, scaledY;

    if (imageAspectRatio > displayAspectRatio) {
      // "Landscape" mode
      // Scale on y, crop on x
      const yScaleFactor =
        imageHeight > maxThumbnailHeight
          ? maxThumbnailHeight / imageHeight
          : 1.0;
      thumbnailImageHeight = scaledY = Math.round(imageHeight * yScaleFactor);
      scaledX = Math.round(imageWidth * yScaleFactor);
      thumbnailImageWidth = Math.min(scaledX, maxThumbnailWidth);
    } else {
      // "Portrait" mode
      // Scale on x, crop on y
      const xScaleFactor =
        imageWidth > maxThumbnailWidth ? maxThumbnailWidth / imageWidth : 1.0;
      thumbnailImageWidth = scaledX = Math.round(imageWidth * xScaleFactor);
      scaledY = Math.round(imageHeight * xScaleFactor);
      // The CSS could widen the image, in which case we crop more off of y.
      thumbnailImageHeight = Math.min(
        scaledY,
        maxThumbnailHeight,
        maxThumbnailHeight / (maxThumbnailWidth / imageWidth)
      );
    }

    return {
      width: thumbnailImageWidth,
      height: thumbnailImageHeight,
      scaledX,
      scaledY,
    };
  }

  /**
   * @param {dataUrl} String Data URL of the original image.
   * @param {int} imageHeight Height in pixels of the original image.
   * @param {int} imageWidth Width in pixels of the original image.
   * @param {String} urlOrBlob 'blob' for a blob, otherwise data url.
   * @returns A promise that resolves to the data URL or blob of the thumbnail image, or null.
   */
  function createThumbnail(dataUrl, imageWidth, imageHeight, urlOrBlob) {
    // There's cost associated with generating, transmitting, and storing
    // thumbnails, so we'll opt out if the image size is below a certain threshold
    const thumbnailThresholdFactor = 1.2;
    const thumbnailWidthThreshold =
      maxThumbnailWidth * thumbnailThresholdFactor;
    const thumbnailHeightThreshold =
      maxThumbnailHeight * thumbnailThresholdFactor;

    if (
      imageWidth <= thumbnailWidthThreshold &&
      imageHeight <= thumbnailHeightThreshold
    ) {
      // Do not create a thumbnail.
      return Promise.resolve(null);
    }

    const thumbnailDimensions = getThumbnailDimensions(imageWidth, imageHeight);

    return new Promise(resolve => {
      const thumbnailImage = new Image();
      let srcWidth = imageWidth;
      let srcHeight = imageHeight;
      let destWidth, destHeight;

      thumbnailImage.onload = function () {
        destWidth = Math.round(srcWidth * maxResizeScaleFactor);
        destHeight = Math.round(srcHeight * maxResizeScaleFactor);
        if (
          destWidth <= thumbnailDimensions.scaledX ||
          destHeight <= thumbnailDimensions.scaledY
        ) {
          srcWidth = Math.round(
            srcWidth * (thumbnailDimensions.width / thumbnailDimensions.scaledX)
          );
          srcHeight = Math.round(
            srcHeight *
              (thumbnailDimensions.height / thumbnailDimensions.scaledY)
          );
          destWidth = thumbnailDimensions.width;
          destHeight = thumbnailDimensions.height;
        }

        const thumbnailCanvas = document.createElement("canvas");
        thumbnailCanvas.width = destWidth;
        thumbnailCanvas.height = destHeight;
        const ctx = thumbnailCanvas.getContext("2d");
        ctx.imageSmoothingEnabled = false;

        ctx.drawImage(
          thumbnailImage,
          0,
          0,
          srcWidth,
          srcHeight,
          0,
          0,
          destWidth,
          destHeight
        );

        if (
          thumbnailCanvas.width <= thumbnailDimensions.width ||
          thumbnailCanvas.height <= thumbnailDimensions.height
        ) {
          if (urlOrBlob === "blob") {
            thumbnailCanvas.toBlob(blob => {
              resolve(blob);
            });
          } else {
            resolve(thumbnailCanvas.toDataURL("image/png"));
          }
          return;
        }

        srcWidth = destWidth;
        srcHeight = destHeight;
        thumbnailImage.src = thumbnailCanvas.toDataURL();
      };
      thumbnailImage.src = dataUrl;
    });
  }

  function createThumbnailUrl(shot) {
    const image = shot.getClip(shot.clipNames()[0]).image;
    if (!image.url) {
      return Promise.resolve(null);
    }
    return createThumbnail(
      image.url,
      image.dimensions.x,
      image.dimensions.y,
      "dataurl"
    );
  }

  function createThumbnailBlobFromPromise(shot, blobToUrlPromise) {
    return blobToUrlPromise.then(dataUrl => {
      const image = shot.getClip(shot.clipNames()[0]).image;
      return createThumbnail(
        dataUrl,
        image.dimensions.x,
        image.dimensions.y,
        "blob"
      );
    });
  }

  if (typeof exports !== "undefined") {
    exports.getThumbnailDimensions = getThumbnailDimensions;
    exports.createThumbnailUrl = createThumbnailUrl;
    exports.createThumbnailBlobFromPromise = createThumbnailBlobFromPromise;
  }

  return exports;
})();
null;
PK
!<{��
catcher.js/* 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";

// eslint-disable-next-line no-var
var global = this;

this.catcher = (function () {
  const exports = {};

  let handler;

  let queue = [];

  const log = global.log;

  exports.unhandled = function (error, info) {
    if (!error.noReport) {
      log.error("Unhandled error:", error, info);
    }
    const e = makeError(error, info);
    if (!handler) {
      queue.push(e);
    } else {
      handler(e);
    }
  };

  /** Turn an exception into an error object */
  function makeError(exc, info) {
    let result;
    if (exc.fromMakeError) {
      result = exc;
    } else {
      result = {
        fromMakeError: true,
        name: exc.name || "ERROR",
        message: String(exc),
        stack: exc.stack,
      };
      for (const attr in exc) {
        result[attr] = exc[attr];
      }
    }
    if (info) {
      for (const attr of Object.keys(info)) {
        result[attr] = info[attr];
      }
    }
    return result;
  }

  /** Wrap the function, and if it raises any exceptions then call unhandled() */
  exports.watchFunction = function watchFunction(func, quiet) {
    return function () {
      try {
        return func.apply(this, arguments);
      } catch (e) {
        if (!quiet) {
          exports.unhandled(e);
        }
        throw e;
      }
    };
  };

  exports.watchPromise = function watchPromise(promise, quiet) {
    return promise.catch(e => {
      if (quiet) {
        if (!e.noReport) {
          log.debug("------Error in promise:", e);
          log.debug(e.stack);
        }
      } else {
        if (!e.noReport) {
          log.error("------Error in promise:", e);
          log.error(e.stack);
        }
        exports.unhandled(makeError(e));
      }
      throw e;
    });
  };

  exports.registerHandler = function (h) {
    if (handler) {
      log.error("registerHandler called after handler was already registered");
      return;
    }
    handler = h;
    for (const error of queue) {
      handler(error);
    }
    queue = [];
  };

  return exports;
})();
null;
PK
!<�A��clipboard.js/* 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 catcher, assertIsBlankDocument, browser */

"use strict";

this.clipboard = (function () {
  const exports = {};

  exports.copy = function (text) {
    return new Promise(resolve => {
      const element = document.createElement("iframe");
      element.src = browser.runtime.getURL("blank.html");
      // We can't actually hide the iframe while copying, but we can make
      // it close to invisible:
      element.style.opacity = "0";
      element.style.width = "1px";
      element.style.height = "1px";
      element.style.display = "block";
      element.addEventListener(
        "load",
        catcher.watchFunction(() => {
          try {
            const doc = element.contentDocument;
            assertIsBlankDocument(doc);
            const el = doc.createElement("textarea");
            doc.body.appendChild(el);
            el.value = text;
            if (!text) {
              const exc = new Error("Clipboard copy given empty text");
              exc.noPopup = true;
              catcher.unhandled(exc);
            }
            el.select();
            if (doc.activeElement !== el) {
              const unhandledTag = doc.activeElement
                ? doc.activeElement.tagName
                : "No active element";
              const exc = new Error("Clipboard el.select failed");
              exc.activeElement = unhandledTag;
              exc.noPopup = true;
              catcher.unhandled(exc);
            }
            const copied = doc.execCommand("copy");
            if (!copied) {
              catcher.unhandled(new Error("Clipboard copy failed"));
            }
            el.remove();
            resolve(copied);
          } finally {
            element.remove();
          }
        }),
        { once: true }
      );
      document.body.appendChild(element);
    });
  };

  return exports;
})();
null;
PK
!<퉇JdomainFromUrl.js/* 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/. */

/** Returns the domain of a URL, but safely and in ASCII; URLs without domains
    (such as about:blank) return the scheme, Unicode domains get stripped down
    to ASCII */

"use strict";

this.domainFromUrl = (function () {
  return function urlDomainForId(location) {
    // eslint-disable-line no-unused-vars
    let domain = location.hostname;
    if (!domain) {
      domain = location.origin.split(":")[0];
      if (!domain) {
        domain = "unknown";
      }
    }
    if (domain.search(/^[a-z0-9._-]{1,1000}$/i) === -1) {
      // Probably a unicode domain; we could use punycode but it wouldn't decode
      // well in the URL anyway.  Instead we'll punt.
      domain = domain.replace(/[^a-z0-9._-]/gi, "");
      if (!domain) {
        domain = "site";
      }
    }
    return domain;
  };
})();
null;
PK
!<��Lq��#experiments/screenshots/schema.json[
  {
    "namespace": "experiments.screenshots",
    "description": "Firefox Screenshots internal API",
    "functions": [
      {
        "name": "getUpdateChannel",
        "type": "function",
        "description": "Returns the Firefox channel (AppConstants.MOZ_UPDATE_CHANNEL)",
        "parameters": [],
        "async": true
      },
      {
        "name": "isHistoryEnabled",
        "type": "function",
        "description": "Returns the value of the 'places.history.enabled' preference",
        "parameters": [],
        "async": true
      }
    ],
    "events": [
      {
        "name": "onScreenshotCommand",
        "type": "function",
        "description": "Fired when the command event for the Screenshots menuitem is fired.",
        "parameters": [
          {
            "name": "isContextMenuClick",
            "type": "boolean"
          }
        ]
      }
    ]
  }
]
PK
!<RLYlog.js/* 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 buildSettings */
/* eslint-disable no-console */

"use strict";

this.log = (function () {
  const exports = {};
  const logLevel = "warn";

  const levels = ["debug", "info", "warn", "error"];
  const shouldLog = {};

  {
    let startLogging = false;
    for (const level of levels) {
      if (logLevel === level) {
        startLogging = true;
      }
      if (startLogging) {
        shouldLog[level] = true;
      }
    }
  }

  function stub() {}
  exports.debug = exports.info = exports.warn = exports.error = stub;

  if (shouldLog.debug) {
    exports.debug = console.debug;
  }

  if (shouldLog.info) {
    exports.info = console.info;
  }

  if (shouldLog.warn) {
    exports.warn = console.warn;
  }

  if (shouldLog.error) {
    exports.error = console.error;
  }

  return exports;
})();
null;
PK
!<��{��
manifest.json{
  "manifest_version": 2,
  "name": "Firefox Screenshots",
  "version": "39.0.1",
  "description": "Take clips and screenshots from the Web and save them temporarily or permanently.",
  "author": "Mozilla <screenshots-feedback@mozilla.com>",
  "homepage_url": "https://github.com/mozilla-services/screenshots",
  "incognito": "spanning",
  "browser_specific_settings": {
    "gecko": {
      "id": "screenshots@mozilla.org",
      "strict_min_version": "57.0a1"
    }
  },
  "l10n_resources": ["browser/screenshots.ftl"],
  "background": {
    "scripts": ["background/startBackground.js"]
  },
  "content_scripts": [
    {
      "matches": ["https://screenshots.firefox.com/*"],
      "js": [
        "log.js",
        "catcher.js",
        "selector/callBackground.js",
        "sitehelper.js"
      ]
    }
  ],
  "web_accessible_resources": ["blank.html"],
  "permissions": [
    "activeTab",
    "downloads",
    "tabs",
    "storage",
    "notifications",
    "clipboardWrite",
    "contextMenus",
    "mozillaAddons",
    "telemetry",
    "<all_urls>",
    "https://screenshots.firefox.com/",
    "resource://pdf.js/",
    "about:reader*"
  ],
  "experiment_apis": {
    "screenshots": {
      "schema": "experiments/screenshots/schema.json",
      "parent": {
        "scopes": ["addon_parent"],
        "script": "experiments/screenshots/api.js",
        "paths": [["experiments", "screenshots"]]
      }
    }
  }
}
PK
!<�){9	moz.build# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.

with Files("**"):
    BUG_COMPONENT = ("Firefox", "Screenshots")

# This file list is automatically generated by Screenshots' export scripts.
# AUTOMATIC INSERTION START
FINAL_TARGET_FILES.features["screenshots@mozilla.org"] += [
    "assertIsBlankDocument.js",
    "assertIsTrusted.js",
    "blank.html",
    "blobConverters.js",
    "catcher.js",
    "clipboard.js",
    "domainFromUrl.js",
    "log.js",
    "manifest.json",
    "moz.build",
    "randomString.js",
    "sitehelper.js",
]

FINAL_TARGET_FILES.features["screenshots@mozilla.org"]["background"] += [
    "background/analytics.js",
    "background/communication.js",
    "background/deviceInfo.js",
    "background/main.js",
    "background/selectorLoader.js",
    "background/senderror.js",
    "background/startBackground.js",
    "background/takeshot.js",
]

FINAL_TARGET_FILES.features["screenshots@mozilla.org"]["build"] += [
    "build/inlineSelectionCss.js",
    "build/selection.js",
    "build/shot.js",
    "build/thumbnailGenerator.js",
]

FINAL_TARGET_FILES.features["screenshots@mozilla.org"]["experiments"][
    "screenshots"
] += ["experiments/screenshots/api.js", "experiments/screenshots/schema.json"]

FINAL_TARGET_FILES.features["screenshots@mozilla.org"]["selector"] += [
    "selector/callBackground.js",
    "selector/documentMetadata.js",
    "selector/shooter.js",
    "selector/ui.js",
    "selector/uicontrol.js",
    "selector/util.js",
]

# AUTOMATIC INSERTION END

BROWSER_CHROME_MANIFESTS += ["test/browser/browser.toml"]
PK
!<4���RRrandomString.js/* 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/. */

/* exported randomString */

"use strict";

this.randomString = function randomString(length, chars) {
  const randomStringChars =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  chars = chars || randomStringChars;
  let result = "";
  for (let i = 0; i < length; i++) {
    result += chars[Math.floor(Math.random() * chars.length)];
  }
  return result;
};
null;
PK
!<
2&^~~selector/callBackground.js/* 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, browser */

"use strict";

this.callBackground = function callBackground(funcName, ...args) {
  return browser.runtime.sendMessage({ funcName, args }).then(result => {
    if (result && result.type === "success") {
      return result.value;
    } else if (result && result.type === "error") {
      const exc = new Error(result.message || "Unknown error");
      exc.name = "BackgroundError";
      if ("errorCode" in result) {
        exc.errorCode = result.errorCode;
      }
      if ("popupMessage" in result) {
        exc.popupMessage = result.popupMessage;
      }
      throw exc;
    } else {
      log.error("Unexpected background result:", result);
      const exc = new Error(
        `Bad response type from background page: ${
          (result && result.type) || undefined
        }`
      );
      exc.resultType = result ? result.type || "undefined" : "undefined result";
      throw exc;
    }
  });
};
null;
PK
!<���UUselector/documentMetadata.js/* 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";

this.documentMetadata = (function () {
  function findSiteName() {
    let el = document.querySelector("meta[property~='og:site_name'][content]");
    if (el) {
      return el.getAttribute("content");
    }
    // nytimes.com uses this property:
    el = document.querySelector("meta[name='cre'][content]");
    if (el) {
      return el.getAttribute("content");
    }
    return null;
  }

  function getOpenGraph() {
    const openGraph = {};
    // If you update this, also update _OPENGRAPH_PROPERTIES in shot.js:
    const forceSingle = `title type url`.split(" ");
    const openGraphProperties = `
    title type url image audio description determiner locale site_name video
    image:secure_url image:type image:width image:height
    video:secure_url video:type video:width image:height
    audio:secure_url audio:type
    article:published_time article:modified_time article:expiration_time article:author article:section article:tag
    book:author book:isbn book:release_date book:tag
    profile:first_name profile:last_name profile:username profile:gender
    `.split(/\s+/g);
    for (const prop of openGraphProperties) {
      let elems = document.querySelectorAll(
        `meta[property~='og:${prop}'][content]`
      );
      if (forceSingle.includes(prop) && elems.length > 1) {
        elems = [elems[0]];
      }
      let value;
      if (elems.length > 1) {
        value = [];
        for (const elem of elems) {
          const v = elem.getAttribute("content");
          if (v) {
            value.push(v);
          }
        }
        if (!value.length) {
          value = null;
        }
      } else if (elems.length === 1) {
        value = elems[0].getAttribute("content");
      }
      if (value) {
        openGraph[prop] = value;
      }
    }
    return openGraph;
  }

  function getTwitterCard() {
    const twitterCard = {};
    // If you update this, also update _TWITTERCARD_PROPERTIES in shot.js:
    const properties = `
    card site title description image
    player player:width player:height player:stream player:stream:content_type
    `.split(/\s+/g);
    for (const prop of properties) {
      const elem = document.querySelector(
        `meta[name='twitter:${prop}'][content]`
      );
      if (elem) {
        const value = elem.getAttribute("content");
        if (value) {
          twitterCard[prop] = value;
        }
      }
    }
    return twitterCard;
  }

  return function documentMetadata() {
    const result = {};
    result.docTitle = document.title;
    result.siteName = findSiteName();
    result.openGraph = getOpenGraph();
    result.twitterCard = getTwitterCard();
    return result;
  };
})();
null;
PK
!<�|�Hselector/shooter.js/* 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 global, browser, documentMetadata, util, uicontrol, ui, catcher */
/* globals domainFromUrl, randomString, shot, blobConverters */

"use strict";

this.shooter = (function () {
  // eslint-disable-line no-unused-vars
  const exports = {};
  const { AbstractShot } = shot;

  const RANDOM_STRING_LENGTH = 16;
  let backend;
  let shotObject;
  const callBackground = global.callBackground;

  function regexpEscape(str) {
    // http://stackoverflow.com/questions/3115150/how-to-escape-regular-expression-special-characters-using-javascript
    return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
  }

  function sanitizeError(data) {
    const href = new RegExp(regexpEscape(window.location.href), "g");
    const origin = new RegExp(
      `${regexpEscape(window.location.origin)}[^ \t\n\r",>]*`,
      "g"
    );
    const json = JSON.stringify(data)
      .replace(href, "REDACTED_HREF")
      .replace(origin, "REDACTED_URL");
    const result = JSON.parse(json);
    return result;
  }

  catcher.registerHandler(errorObj => {
    callBackground("reportError", sanitizeError(errorObj));
  });

  function hideUIFrame() {
    ui.iframe.hide();
    return Promise.resolve(null);
  }

  function screenshotPage(dataUrl, selectedPos, type, screenshotTaskFn) {
    let promise = Promise.resolve(dataUrl);

    if (!dataUrl) {
      promise = callBackground(
        "screenshotPage",
        selectedPos.toJSON(),
        type,
        window.devicePixelRatio
      );
    }

    catcher.watchPromise(
      promise.then(dataLoc => {
        screenshotTaskFn(dataLoc);
      })
    );
  }

  exports.downloadShot = function (selectedPos, previewDataUrl, type) {
    const shotPromise = previewDataUrl
      ? Promise.resolve(previewDataUrl)
      : hideUIFrame();
    catcher.watchPromise(
      shotPromise.then(dataUrl => {
        screenshotPage(dataUrl, selectedPos, type, url => {
          let typeFromDataUrl = blobConverters.getTypeFromDataUrl(url);
          typeFromDataUrl = typeFromDataUrl
            ? typeFromDataUrl.split("/", 2)[1]
            : null;
          shotObject.delAllClips();
          shotObject.addClip({
            createdDate: Date.now(),
            image: {
              url,
              type: typeFromDataUrl,
              location: selectedPos,
            },
          });
          ui.triggerDownload(url, shotObject.filename);
          uicontrol.deactivate();
        });
      })
    );
  };

  exports.preview = function (selectedPos, type) {
    catcher.watchPromise(
      hideUIFrame().then(dataUrl => {
        screenshotPage(dataUrl, selectedPos, type, url => {
          ui.iframe.usePreview();
          ui.Preview.display(url);
        });
      })
    );
  };

  let copyInProgress = null;
  exports.copyShot = function (selectedPos, previewDataUrl, type) {
    // This is pretty slow. We'll ignore additional user triggered copy events
    // while it is in progress.
    if (copyInProgress) {
      return;
    }
    // A max of five seconds in case some error occurs.
    copyInProgress = setTimeout(() => {
      copyInProgress = null;
    }, 5000);

    const unsetCopyInProgress = () => {
      if (copyInProgress) {
        clearTimeout(copyInProgress);
        copyInProgress = null;
      }
    };
    const shotPromise = previewDataUrl
      ? Promise.resolve(previewDataUrl)
      : hideUIFrame();
    catcher.watchPromise(
      shotPromise.then(dataUrl => {
        screenshotPage(dataUrl, selectedPos, type, url => {
          const blob = blobConverters.dataUrlToBlob(url);
          catcher.watchPromise(
            callBackground("copyShotToClipboard", blob).then(() => {
              uicontrol.deactivate();
              unsetCopyInProgress();
            }, unsetCopyInProgress)
          );
        });
      })
    );
  };

  exports.sendEvent = function (...args) {
    const maybeOptions = args[args.length - 1];

    if (typeof maybeOptions === "object") {
      maybeOptions.incognito = browser.extension.inIncognitoContext;
    } else {
      args.push({ incognito: browser.extension.inIncognitoContext });
    }
  };

  catcher.watchFunction(() => {
    shotObject = new AbstractShot(
      backend,
      randomString(RANDOM_STRING_LENGTH) + "/" + domainFromUrl(location),
      {
        origin: shot.originFromUrl(location.href),
      }
    );
    shotObject.update(documentMetadata());
  })();

  return exports;
})();
null;
PK
!<���qqselector/ui.js/* 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 browser, log, util, catcher, inlineSelectionCss, callBackground, assertIsTrusted, assertIsBlankDocument, 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("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.runtime.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 initializeIframe() {
    const el = document.createElement("iframe");
    el.src = browser.runtime.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";
    el.style.backgroundColor = "transparent";
    el.style.colorScheme = "light";
    return el;
  }

  const iframeSelection = (exports.iframeSelection = {
    element: null,
    addClassName: "",
    sizeTracking: {
      timer: null,
      windowDelayer: null,
      lastHeight: null,
      lastWidth: null,
    },
    document: null,
    window: null,
    display(installHandlerOnDocument) {
      return new Promise(resolve => {
        if (!this.element) {
          this.element = initializeIframe();
          this.element.id = "firefox-screenshots-selection-iframe";
          this.element.style.display = "none";
          this.element.style.setProperty("max-width", "none", "important");
          this.element.style.setProperty("max-height", "none", "important");
          this.element.style.setProperty("position", "absolute", "important");
          this.element.setAttribute("role", "dialog");
          this.updateElementSize();
          this.element.addEventListener(
            "load",
            watchFunction(() => {
              this.document = this.element.contentDocument;
              this.window = this.element.contentWindow;
              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";
      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 = this.window = null;
    },
  });

  iframeSelection.onResize = watchFunction(
    assertIsTrusted(onResize.bind(iframeSelection)),
    true
  );

  const iframePreSelection = (exports.iframePreSelection = {
    element: null,
    document: null,
    window: null,
    display(installHandlerOnDocument, standardOverlayCallbacks) {
      return new Promise(resolve => {
        if (!this.element) {
          this.element = initializeIframe();
          this.element.id = "firefox-screenshots-preselection-iframe";
          this.element.style.setProperty("position", "fixed", "important");
          this.element.style.width = "100%";
          this.element.style.height = "100%";
          this.element.style.setProperty("max-width", "none", "important");
          this.element.style.setProperty("max-height", "none", "important");
          this.element.setAttribute("role", "dialog");
          this.element.addEventListener(
            "load",
            watchFunction(() => {
              this.document = this.element.contentDocument;
              this.window = this.element.contentWindow;
              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="all-buttons-container">
                       <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");
              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";
      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 = this.document = this.window = 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,
    window: null,
    display(installHandlerOnDocument, standardOverlayCallbacks) {
      return new Promise(resolve => {
        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.height = "100%";
          this.element.style.width = "100%";
          this.element.style.setProperty("max-width", "none", "important");
          this.element.style.setProperty("max-height", "none", "important");
          this.element.setAttribute("role", "dialog");
          this.element.onload = watchFunction(() => {
            msgsPromise.then(([cancelTitle, copyTitle, downloadTitle]) => {
              assertIsBlankDocument(this.element.contentDocument);
              this.document = this.element.contentDocument;
              this.window = this.element.contentWindow;
              // 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="chrome://browser/content/screenshots/cancel.svg"/>
                        </button>
                        <button class="highlight-button-copy" title="${copyTitle}">
                          <img src="chrome://browser/content/screenshots/copy.svg"/>
                          <span data-l10n-id="screenshots-copy-button"/>
                        </button>
                        <button class="highlight-button-download" title="${downloadTitle}">
                          <img src="chrome://browser/content/screenshots/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";
      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();
    },

    getContentWindow() {
      return this.currentIframe.element.contentWindow;
    },

    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.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 = "chrome://browser/content/screenshots/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 = "chrome://browser/content/screenshots/copy.svg";
      copy.appendChild(copyImg);
      copy.appendChild(copyString);
      buttons.appendChild(copy);

      const download = makeEl("button", "highlight-button-download");
      const downloadImg = makeEl("img");
      downloadImg.src =
        "chrome://browser/content/screenshots/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;
    },

    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 = iframe.getContentWindow().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;
PK
!<�3�`q`qselector/uicontrol.js/* 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, catcher, util, ui, slides, global */
/* globals shooter, callBackground, selectorLoader, assertIsTrusted, selection */

"use strict";

this.uicontrol = (function () {
  const exports = {};

  /** ********************************************************
   * selection
   */

  /* States:

  "crosshairs":
    Nothing has happened, and the crosshairs will follow the movement of the mouse
  "draggingReady":
    The user has pressed the mouse button, but hasn't moved enough to create a selection
  "dragging":
    The user has pressed down a mouse button, and is dragging out an area far enough to show a selection
  "selected":
    The user has selected an area
  "resizing":
    The user is resizing the selection
  "cancelled":
    Everything has been cancelled
  "previewing":
    The user is previewing the full-screen/visible image

  A mousedown goes from crosshairs to dragging.
  A mouseup goes from dragging to selected
  A click outside of the selection goes from selected to crosshairs
  A click on one of the draggers goes from selected to resizing

  State variables:

  state (string, one of the above)
  mousedownPos (object with x/y during draggingReady, shows where the selection started)
  selectedPos (object with x/y/h/w during selected or dragging, gives the entire selection)
  resizeDirection (string: top, topLeft, etc, during resizing)
  resizeStartPos (x/y position where resizing started)
  mouseupNoAutoselect (true if a mouseup in draggingReady should not trigger autoselect)

  */

  const { watchFunction, watchPromise } = catcher;

  const MAX_PAGE_HEIGHT = 10000;
  const MAX_PAGE_WIDTH = 10000;
  // An autoselection smaller than these will be ignored entirely:
  const MIN_DETECT_ABSOLUTE_HEIGHT = 10;
  const MIN_DETECT_ABSOLUTE_WIDTH = 30;
  // An autoselection smaller than these will not be preferred:
  const MIN_DETECT_HEIGHT = 30;
  const MIN_DETECT_WIDTH = 100;
  // An autoselection bigger than either of these will be ignored:
  const MAX_DETECT_HEIGHT = Math.max(window.innerHeight + 100, 700);
  const MAX_DETECT_WIDTH = Math.max(window.innerWidth + 100, 1000);
  // This is how close (in pixels) you can get to the edge of the window and then
  // it will scroll:
  const SCROLL_BY_EDGE = 20;
  // This is how wide the inboard scrollbars are, generally 0 except on Mac
  const SCROLLBAR_WIDTH = window.navigator.platform.match(/Mac/i) ? 17 : 0;

  const { Selection } = selection;
  const { sendEvent } = shooter;
  const log = global.log;

  function round10(n) {
    return Math.floor(n / 10) * 10;
  }

  function eventOptionsForBox(box) {
    return {
      cd1: round10(Math.abs(box.bottom - box.top)),
      cd2: round10(Math.abs(box.right - box.left)),
    };
  }

  function eventOptionsForResize(boxStart, boxEnd) {
    return {
      cd1: round10(
        boxEnd.bottom - boxEnd.top - (boxStart.bottom - boxStart.top)
      ),
      cd2: round10(
        boxEnd.right - boxEnd.left - (boxStart.right - boxStart.left)
      ),
    };
  }

  function eventOptionsForMove(posStart, posEnd) {
    return {
      cd1: round10(posEnd.y - posStart.y),
      cd2: round10(posEnd.x - posStart.x),
    };
  }

  function downloadShot() {
    const previewDataUrl = captureType === "fullPageTruncated" ? null : dataUrl;
    // Downloaded shots don't have dimension limits
    removeDimensionLimitsOnFullPageShot();
    shooter.downloadShot(selectedPos, previewDataUrl, captureType);
  }

  function copyShot() {
    const previewDataUrl = captureType === "fullPageTruncated" ? null : dataUrl;
    // Copied shots don't have dimension limits
    removeDimensionLimitsOnFullPageShot();
    shooter.copyShot(selectedPos, previewDataUrl, captureType);
  }

  /** *********************************************
   * State and stateHandlers infrastructure
   */

  // This enumerates all the anchors on the selection, and what part of the
  // selection they move:
  const movements = {
    topLeft: ["x1", "y1"],
    top: [null, "y1"],
    topRight: ["x2", "y1"],
    left: ["x1", null],
    right: ["x2", null],
    bottomLeft: ["x1", "y2"],
    bottom: [null, "y2"],
    bottomRight: ["x2", "y2"],
    move: ["*", "*"],
  };

  const doNotAutoselectTags = {
    H1: true,
    H2: true,
    H3: true,
    H4: true,
    H5: true,
    H6: true,
  };

  let captureType;

  function removeDimensionLimitsOnFullPageShot() {
    if (captureType === "fullPageTruncated") {
      captureType = "fullPage";
      selectedPos = new Selection(
        0,
        0,
        getDocumentWidth(),
        getDocumentHeight()
      );
    }
  }

  const standardDisplayCallbacks = {
    cancel: () => {
      sendEvent("cancel-shot", "overlay-cancel-button");
      exports.deactivate();
    },
    download: () => {
      sendEvent("download-shot", "overlay-download-button");
      downloadShot();
    },
    copy: () => {
      sendEvent("copy-shot", "overlay-copy-button");
      copyShot();
    },
  };

  const standardOverlayCallbacks = {
    cancel: () => {
      sendEvent("cancel-shot", "cancel-preview-button");
      exports.deactivate();
    },
    onClickCancel: e => {
      sendEvent("cancel-shot", "cancel-selection-button");
      e.preventDefault();
      e.stopPropagation();
      exports.deactivate();
    },
    onClickVisible: () => {
      callBackground("captureTelemetry", "visible");
      sendEvent("capture-visible", "selection-button");
      selectedPos = new Selection(
        window.scrollX,
        window.scrollY,
        window.scrollX + document.documentElement.clientWidth,
        window.scrollY + window.innerHeight
      );
      captureType = "visible";
      setState("previewing");
    },
    onClickFullPage: () => {
      callBackground("captureTelemetry", "full_page");
      sendEvent("capture-full-page", "selection-button");
      captureType = "fullPage";
      const width = getDocumentWidth();
      if (width > MAX_PAGE_WIDTH) {
        captureType = "fullPageTruncated";
      }
      const height = getDocumentHeight();
      if (height > MAX_PAGE_HEIGHT) {
        captureType = "fullPageTruncated";
      }
      selectedPos = new Selection(0, 0, width, height);
      setState("previewing");
    },
    onDownloadPreview: () => {
      sendEvent(
        `download-${captureType
          .replace(/([a-z])([A-Z])/g, "$1-$2")
          .toLowerCase()}`,
        "download-preview-button"
      );
      downloadShot();
    },
    onCopyPreview: () => {
      sendEvent(
        `copy-${captureType.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()}`,
        "copy-preview-button"
      );
      copyShot();
    },
  };

  /** Holds all the objects that handle events for each state: */
  const stateHandlers = {};

  function getState() {
    return getState.state;
  }
  getState.state = "cancel";

  function setState(s) {
    if (!stateHandlers[s]) {
      throw new Error("Unknown state: " + s);
    }
    const cur = getState.state;
    const handler = stateHandlers[cur];
    if (handler.end) {
      handler.end();
    }
    getState.state = s;
    if (stateHandlers[s].start) {
      stateHandlers[s].start();
    }
  }

  /** Various values that the states use: */
  let mousedownPos;
  let selectedPos;
  let resizeDirection;
  let resizeStartPos;
  let resizeStartSelected;
  let resizeHasMoved;
  let mouseupNoAutoselect = false;
  let autoDetectRect;

  /** Represents a single x/y point, typically for a mouse click that doesn't have a drag: */
  class Pos {
    constructor(x, y) {
      this.x = x;
      this.y = y;
    }

    elementFromPoint() {
      return ui.iframe.getElementFromPoint(
        this.x - window.pageXOffset,
        this.y - window.pageYOffset
      );
    }

    distanceTo(x, y) {
      return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));
    }
  }

  /** *********************************************
   * all stateHandlers
   */

  let dataUrl;

  stateHandlers.previewing = {
    start() {
      shooter.preview(selectedPos, captureType);
    },
  };

  stateHandlers.crosshairs = {
    cachedEl: null,

    start() {
      selectedPos = mousedownPos = null;
      this.cachedEl = null;
      watchPromise(
        ui.iframe
          .display(installHandlersOnDocument, standardOverlayCallbacks)
          .then(() => {
            ui.iframe.usePreSelection();
            ui.Box.remove();
          })
      );
    },

    mousemove(event) {
      ui.PixelDimensions.display(
        event.pageX,
        event.pageY,
        event.pageX,
        event.pageY
      );
      if (
        event.target.classList &&
        !event.target.classList.contains("preview-overlay")
      ) {
        // User is hovering over a toolbar button or control
        autoDetectRect = null;
        if (this.cachedEl) {
          this.cachedEl = null;
        }
        ui.HoverBox.hide();
        return;
      }
      let el;
      if (
        event.target.classList &&
        event.target.classList.contains("preview-overlay")
      ) {
        // The hover is on the overlay, so we need to figure out the real element
        el = ui.iframe.getElementFromPoint(
          event.pageX + window.scrollX - window.pageXOffset,
          event.pageY + window.scrollY - window.pageYOffset
        );
        const xpos = Math.floor(
          (10 * (event.pageX - window.innerWidth / 2)) / window.innerWidth
        );
        const ypos = Math.floor(
          (10 * (event.pageY - window.innerHeight / 2)) / window.innerHeight
        );

        for (let i = 0; i < 2; i++) {
          const move = `translate(${xpos}px, ${ypos}px)`;
          event.target.getElementsByClassName("eyeball")[i].style.transform =
            move;
        }
      } else {
        // The hover is on the element we care about, so we use that
        el = event.target;
      }
      if (this.cachedEl && this.cachedEl === el) {
        // Still hovering over the same element
        return;
      }
      this.cachedEl = el;
      this.setAutodetectBasedOnElement(el);
    },

    setAutodetectBasedOnElement(el) {
      let lastRect;
      let lastNode;
      let rect;
      let attemptExtend = false;
      let node = el;
      while (node) {
        rect = Selection.getBoundingClientRect(node);
        if (!rect) {
          rect = lastRect;
          break;
        }
        if (rect.width < MIN_DETECT_WIDTH || rect.height < MIN_DETECT_HEIGHT) {
          // Avoid infinite loop for elements with zero or nearly zero height,
          // like non-clearfixed float parents with or without borders.
          break;
        }
        if (rect.width > MAX_DETECT_WIDTH || rect.height > MAX_DETECT_HEIGHT) {
          // Then the last rectangle is better
          rect = lastRect;
          attemptExtend = true;
          break;
        }
        if (
          rect.width >= MIN_DETECT_WIDTH &&
          rect.height >= MIN_DETECT_HEIGHT
        ) {
          if (!doNotAutoselectTags[node.tagName]) {
            break;
          }
        }
        lastRect = rect;
        lastNode = node;
        node = node.parentNode;
      }
      if (rect && node) {
        const evenBetter = this.evenBetterElement(node, rect);
        if (evenBetter) {
          node = lastNode = evenBetter;
          rect = Selection.getBoundingClientRect(evenBetter);
          attemptExtend = false;
        }
      }
      if (rect && attemptExtend) {
        let extendNode = lastNode.nextSibling;
        while (extendNode) {
          if (extendNode.nodeType === document.ELEMENT_NODE) {
            break;
          }
          extendNode = extendNode.nextSibling;
          if (!extendNode) {
            const parent = lastNode.parentNode;
            for (let i = 0; i < parent.childNodes.length; i++) {
              if (parent.childNodes[i] === lastNode) {
                extendNode = parent.childNodes[i + 1];
              }
            }
          }
        }
        if (extendNode) {
          const extendSelection = Selection.getBoundingClientRect(extendNode);
          const extendRect = rect.union(extendSelection);
          if (
            extendRect.width <= MAX_DETECT_WIDTH &&
            extendRect.height <= MAX_DETECT_HEIGHT
          ) {
            rect = extendRect;
          }
        }
      }

      if (
        rect &&
        (rect.width < MIN_DETECT_ABSOLUTE_WIDTH ||
          rect.height < MIN_DETECT_ABSOLUTE_HEIGHT)
      ) {
        rect = null;
      }
      if (!rect) {
        ui.HoverBox.hide();
      } else {
        ui.HoverBox.display(rect);
      }
      autoDetectRect = rect;
    },

    /** When we find an element, maybe there's one that's just a little bit better... */
    evenBetterElement(node) {
      let el = node.parentNode;
      const ELEMENT_NODE = document.ELEMENT_NODE;
      while (el && el.nodeType === ELEMENT_NODE) {
        if (!el.getAttribute) {
          return null;
        }
        const role = el.getAttribute("role");
        if (
          role === "article" ||
          (el.className &&
            typeof el.className === "string" &&
            el.className.search("tweet ") !== -1)
        ) {
          const rect = Selection.getBoundingClientRect(el);
          if (!rect) {
            return null;
          }
          if (
            rect.width <= MAX_DETECT_WIDTH &&
            rect.height <= MAX_DETECT_HEIGHT
          ) {
            return el;
          }
          return null;
        }
        el = el.parentNode;
      }
      return null;
    },

    mousedown(event) {
      // FIXME: this is happening but we don't know why, we'll track it now
      // but avoid popping up messages:
      if (typeof ui === "undefined") {
        const exc = new Error("Undefined ui in mousedown");
        exc.unloadTime = unloadTime;
        exc.nowTime = Date.now();
        exc.noPopup = true;
        exc.noReport = true;
        throw exc;
      }
      if (ui.isHeader(event.target)) {
        return undefined;
      }
      // If the pageX is greater than this, then probably it's an attempt to get
      // to the scrollbar, or an actual scroll, and not an attempt to start the
      // selection:
      const maxX = window.innerWidth - SCROLLBAR_WIDTH;
      if (event.pageX >= maxX) {
        event.stopPropagation();
        event.preventDefault();
        return false;
      }

      mousedownPos = new Pos(
        event.pageX + window.scrollX,
        event.pageY + window.scrollY
      );
      setState("draggingReady");
      event.stopPropagation();
      event.preventDefault();
      return false;
    },

    end() {
      ui.HoverBox.remove();
      ui.PixelDimensions.remove();
    },
  };

  stateHandlers.draggingReady = {
    minMove: 40, // px
    minAutoImageWidth: 40,
    minAutoImageHeight: 40,
    maxAutoElementWidth: 800,
    maxAutoElementHeight: 600,

    start() {
      ui.iframe.usePreSelection();
      ui.Box.remove();
    },

    mousemove(event) {
      if (mousedownPos.distanceTo(event.pageX, event.pageY) > this.minMove) {
        selectedPos = new Selection(
          mousedownPos.x,
          mousedownPos.y,
          event.pageX + window.scrollX,
          event.pageY + window.scrollY
        );
        mousedownPos = null;
        setState("dragging");
      }
    },

    mouseup() {
      // If we don't get into "dragging" then we attempt an autoselect
      if (mouseupNoAutoselect) {
        sendEvent("cancel-selection", "selection-background-mousedown");
        setState("crosshairs");
        return false;
      }
      if (autoDetectRect) {
        selectedPos = autoDetectRect;
        selectedPos.x1 += window.scrollX;
        selectedPos.y1 += window.scrollY;
        selectedPos.x2 += window.scrollX;
        selectedPos.y2 += window.scrollY;
        autoDetectRect = null;
        mousedownPos = null;
        ui.iframe.useSelection();
        ui.Box.display(selectedPos, standardDisplayCallbacks);
        sendEvent(
          "make-selection",
          "selection-click",
          eventOptionsForBox(selectedPos)
        );
        setState("selected");
        sendEvent("autoselect");
        callBackground("captureTelemetry", "element");
      } else {
        sendEvent("no-selection", "no-element-found");
        setState("crosshairs");
      }
      return undefined;
    },

    click(event) {
      this.mouseup(event);
    },

    findGoodEl() {
      let el = mousedownPos.elementFromPoint();
      if (!el) {
        return null;
      }
      const isGoodEl = element => {
        if (element.nodeType !== document.ELEMENT_NODE) {
          return false;
        }
        if (element.tagName === "IMG") {
          const rect = element.getBoundingClientRect();
          return (
            rect.width >= this.minAutoImageWidth &&
            rect.height >= this.minAutoImageHeight
          );
        }
        const display = window.getComputedStyle(element).display;
        if (["block", "inline-block", "table"].includes(display)) {
          return true;
          // FIXME: not sure if this is useful:
          // let rect = el.getBoundingClientRect();
          // return rect.width <= this.maxAutoElementWidth && rect.height <= this.maxAutoElementHeight;
        }
        return false;
      };
      while (el) {
        if (isGoodEl(el)) {
          return el;
        }
        el = el.parentNode;
      }
      return null;
    },

    end() {
      mouseupNoAutoselect = false;
    },
  };

  stateHandlers.dragging = {
    start() {
      ui.iframe.useSelection();
      ui.Box.display(selectedPos);
    },

    mousemove(event) {
      selectedPos.x2 = util.truncateX(event.pageX);
      selectedPos.y2 = util.truncateY(event.pageY);
      scrollIfByEdge(event.pageX, event.pageY);
      ui.Box.display(selectedPos);
      ui.PixelDimensions.display(
        event.pageX,
        event.pageY,
        selectedPos.width,
        selectedPos.height
      );
    },

    mouseup(event) {
      selectedPos.x2 = util.truncateX(event.pageX);
      selectedPos.y2 = util.truncateY(event.pageY);
      ui.Box.display(selectedPos, standardDisplayCallbacks);
      sendEvent(
        "make-selection",
        "selection-drag",
        eventOptionsForBox({
          top: selectedPos.y1,
          bottom: selectedPos.y2,
          left: selectedPos.x1,
          right: selectedPos.x2,
        })
      );
      setState("selected");
      callBackground("captureTelemetry", "custom");
    },

    end() {
      ui.PixelDimensions.remove();
    },
  };

  stateHandlers.selected = {
    start() {
      ui.iframe.useSelection();
    },

    mousedown(event) {
      const target = event.target;
      if (target.tagName === "HTML") {
        // This happens when you click on the scrollbar
        return undefined;
      }
      const direction = ui.Box.draggerDirection(target);
      if (direction) {
        sendEvent("start-resize-selection", "handle");
        stateHandlers.resizing.startResize(event, direction);
      } else if (ui.Box.isSelection(target)) {
        sendEvent("start-move-selection", "selection");
        stateHandlers.resizing.startResize(event, "move");
      } else if (!ui.Box.isControl(target)) {
        mousedownPos = new Pos(event.pageX, event.pageY);
        setState("crosshairs");
      }
      event.preventDefault();
      return false;
    },
  };

  stateHandlers.resizing = {
    start() {
      ui.iframe.useSelection();
      selectedPos.sortCoords();
    },

    startResize(event, direction) {
      selectedPos.sortCoords();
      resizeDirection = direction;
      resizeStartPos = new Pos(event.pageX, event.pageY);
      resizeStartSelected = selectedPos.clone();
      resizeHasMoved = false;
      setState("resizing");
    },

    mousemove(event) {
      this._resize(event);
      if (resizeDirection !== "move") {
        ui.PixelDimensions.display(
          event.pageX,
          event.pageY,
          selectedPos.width,
          selectedPos.height
        );
      }
      return false;
    },

    mouseup(event) {
      this._resize(event);
      sendEvent("selection-resized");
      ui.Box.display(selectedPos, standardDisplayCallbacks);
      if (resizeHasMoved) {
        if (resizeDirection === "move") {
          const startPos = new Pos(
            resizeStartSelected.left,
            resizeStartSelected.top
          );
          const endPos = new Pos(selectedPos.left, selectedPos.top);
          sendEvent(
            "move-selection",
            "mouseup",
            eventOptionsForMove(startPos, endPos)
          );
        } else {
          sendEvent(
            "resize-selection",
            "mouseup",
            eventOptionsForResize(resizeStartSelected, selectedPos)
          );
        }
      } else if (resizeDirection === "move") {
        sendEvent("keep-resize-selection", "mouseup");
      } else {
        sendEvent("keep-move-selection", "mouseup");
      }
      setState("selected");
      callBackground("captureTelemetry", "custom");
    },

    _resize(event) {
      const diffX = event.pageX - resizeStartPos.x;
      const diffY = event.pageY - resizeStartPos.y;
      const movement = movements[resizeDirection];
      if (movement[0]) {
        let moveX = movement[0];
        moveX = moveX === "*" ? ["x1", "x2"] : [moveX];
        for (const moveDir of moveX) {
          selectedPos[moveDir] = util.truncateX(
            resizeStartSelected[moveDir] + diffX
          );
        }
      }
      if (movement[1]) {
        let moveY = movement[1];
        moveY = moveY === "*" ? ["y1", "y2"] : [moveY];
        for (const moveDir of moveY) {
          selectedPos[moveDir] = util.truncateY(
            resizeStartSelected[moveDir] + diffY
          );
        }
      }
      if (diffX || diffY) {
        resizeHasMoved = true;
      }
      scrollIfByEdge(event.pageX, event.pageY);
      ui.Box.display(selectedPos);
    },

    end() {
      resizeDirection = resizeStartPos = resizeStartSelected = null;
      selectedPos.sortCoords();
      ui.PixelDimensions.remove();
    },
  };

  stateHandlers.cancel = {
    start() {
      ui.iframe.hide();
      ui.Box.remove();
    },
  };

  function getDocumentWidth() {
    return Math.max(
      document.body && document.body.clientWidth,
      document.documentElement.clientWidth,
      document.body && document.body.scrollWidth,
      document.documentElement.scrollWidth
    );
  }
  function getDocumentHeight() {
    return Math.max(
      document.body && document.body.clientHeight,
      document.documentElement.clientHeight,
      document.body && document.body.scrollHeight,
      document.documentElement.scrollHeight
    );
  }

  function scrollIfByEdge(pageX, pageY) {
    const top = window.scrollY;
    const bottom = top + window.innerHeight;
    const left = window.scrollX;
    const right = left + window.innerWidth;
    if (pageY + SCROLL_BY_EDGE >= bottom && bottom < getDocumentHeight()) {
      window.scrollBy(0, SCROLL_BY_EDGE);
    } else if (pageY - SCROLL_BY_EDGE <= top) {
      window.scrollBy(0, -SCROLL_BY_EDGE);
    }
    if (pageX + SCROLL_BY_EDGE >= right && right < getDocumentWidth()) {
      window.scrollBy(SCROLL_BY_EDGE, 0);
    } else if (pageX - SCROLL_BY_EDGE <= left) {
      window.scrollBy(-SCROLL_BY_EDGE, 0);
    }
  }

  /** *********************************************
   * Selection communication
   */

  exports.activate = function () {
    if (!document.body) {
      callBackground("abortStartShot");
      const tagName = String(document.documentElement.tagName || "").replace(
        /[^a-z0-9]/gi,
        ""
      );
      sendEvent("abort-start-shot", `document-is-${tagName}`);
      selectorLoader.unloadModules();
      return;
    }
    if (isFrameset()) {
      callBackground("abortStartShot");
      sendEvent("abort-start-shot", "frame-page");
      selectorLoader.unloadModules();
      return;
    }
    addHandlers();
    setState("crosshairs");
  };

  function isFrameset() {
    return document.body.tagName === "FRAMESET";
  }

  exports.deactivate = function () {
    try {
      sendEvent("internal", "deactivate");
      setState("cancel");
      selectorLoader.unloadModules();
    } catch (e) {
      log.error("Error in deactivate", e);
      // Sometimes this fires so late that the document isn't available
      // We don't care about the exception, so we swallow it here
    }
  };

  let unloadTime = 0;

  exports.unload = function () {
    // Note that ui.unload() will be called on its own
    unloadTime = Date.now();
    removeHandlers();
  };

  /** *********************************************
   * Event handlers
   */

  const primedDocumentHandlers = new Map();
  let registeredDocumentHandlers = [];

  function addHandlers() {
    ["mouseup", "mousedown", "mousemove", "click"].forEach(eventName => {
      const fn = watchFunction(
        assertIsTrusted(function (event) {
          if (typeof event.button === "number" && event.button !== 0) {
            // Not a left click
            return undefined;
          }
          if (
            event.ctrlKey ||
            event.shiftKey ||
            event.altKey ||
            event.metaKey
          ) {
            // Modified click of key
            return undefined;
          }
          const state = getState();
          const handler = stateHandlers[state];
          if (handler[event.type]) {
            return handler[event.type](event);
          }
          return undefined;
        })
      );
      primedDocumentHandlers.set(eventName, fn);
    });
    primedDocumentHandlers.set(
      "keyup",
      watchFunction(assertIsTrusted(keyupHandler))
    );
    primedDocumentHandlers.set(
      "keydown",
      watchFunction(assertIsTrusted(keydownHandler))
    );
    window.document.addEventListener(
      "visibilitychange",
      visibilityChangeHandler
    );
    window.addEventListener("beforeunload", beforeunloadHandler);
  }

  let mousedownSetOnDocument = false;

  function installHandlersOnDocument(docObj) {
    for (const [eventName, handler] of primedDocumentHandlers) {
      const watchHandler = watchFunction(handler);
      const useCapture = eventName !== "keyup";
      docObj.addEventListener(eventName, watchHandler, useCapture);
      registeredDocumentHandlers.push({
        name: eventName,
        doc: docObj,
        handler: watchHandler,
        useCapture,
      });
    }
    if (!mousedownSetOnDocument) {
      const mousedownHandler = primedDocumentHandlers.get("mousedown");
      document.addEventListener("mousedown", mousedownHandler, true);
      registeredDocumentHandlers.push({
        name: "mousedown",
        doc: document,
        handler: mousedownHandler,
        useCapture: true,
      });
      mousedownSetOnDocument = true;
    }
  }

  function beforeunloadHandler() {
    sendEvent("cancel-shot", "tab-load");
    exports.deactivate();
  }

  function keydownHandler(event) {
    // In MacOS, the keyup event for 'c' is not fired when performing cmd+c.
    if (
      event.code === "KeyC" &&
      (event.ctrlKey || event.metaKey) &&
      ["previewing", "selected"].includes(getState.state)
    ) {
      catcher.watchPromise(
        callBackground("getPlatformOs").then(os => {
          if (
            (event.ctrlKey && os !== "mac") ||
            (event.metaKey && os === "mac")
          ) {
            sendEvent("copy-shot", "keyboard-copy");
            copyShot();
          }
        })
      );
    }
  }

  function keyupHandler(event) {
    if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) {
      // unused modifier keys
      return;
    }
    if ((event.key || event.code) === "Escape") {
      sendEvent("cancel-shot", "keyboard-escape");
      exports.deactivate();
    }
    // Enter to trigger Download by default. But if the user tabbed to
    // select another button, then we do not want this.
    if (
      (event.key || event.code) === "Enter" &&
      getState.state === "selected" &&
      ui.iframe.document().activeElement.tagName === "BODY"
    ) {
      sendEvent("download-shot", "keyboard-enter");
      downloadShot();
    }
  }

  function visibilityChangeHandler(event) {
    // The document is the event target
    if (event.target.hidden) {
      sendEvent("internal", "document-hidden");
    }
  }

  function removeHandlers() {
    window.removeEventListener("beforeunload", beforeunloadHandler);
    window.document.removeEventListener(
      "visibilitychange",
      visibilityChangeHandler
    );
    for (const {
      name,
      doc,
      handler,
      useCapture,
    } of registeredDocumentHandlers) {
      doc.removeEventListener(name, handler, !!useCapture);
    }
    registeredDocumentHandlers = [];
  }

  catcher.watchFunction(exports.activate)();

  return exports;
})();

null;
PK
!<|ҵ���selector/util.js/* 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";

this.util = (function () {
  // eslint-disable-line no-unused-vars
  const exports = {};

  /** Removes a node from its document, if it's a node and the node is attached to a parent */
  exports.removeNode = function (el) {
    if (el && el.parentNode) {
      el.remove();
    }
  };

  /** Truncates the X coordinate to the document size */
  exports.truncateX = function (x) {
    const max = Math.max(
      document.documentElement.clientWidth,
      document.body.clientWidth,
      document.documentElement.scrollWidth,
      document.body.scrollWidth
    );
    if (x < 0) {
      return 0;
    } else if (x > max) {
      return max;
    }
    return x;
  };

  /** Truncates the Y coordinate to the document size */
  exports.truncateY = function (y) {
    const max = Math.max(
      document.documentElement.clientHeight,
      document.body.clientHeight,
      document.documentElement.scrollHeight,
      document.body.scrollHeight
    );
    if (y < 0) {
      return 0;
    } else if (y > max) {
      return max;
    }
    return y;
  };

  // Pixels of wiggle the captured region gets in captureSelectedText:
  const CAPTURE_WIGGLE = 10;
  const ELEMENT_NODE = document.ELEMENT_NODE;

  exports.captureEnclosedText = function (box) {
    const scrollX = window.scrollX;
    const scrollY = window.scrollY;
    const text = [];
    function traverse(el) {
      let elBox = el.getBoundingClientRect();
      elBox = {
        top: elBox.top + scrollY,
        bottom: elBox.bottom + scrollY,
        left: elBox.left + scrollX,
        right: elBox.right + scrollX,
      };
      if (
        elBox.bottom < box.top ||
        elBox.top > box.bottom ||
        elBox.right < box.left ||
        elBox.left > box.right
      ) {
        // Totally outside of the box
        return;
      }
      if (
        elBox.bottom > box.bottom + CAPTURE_WIGGLE ||
        elBox.top < box.top - CAPTURE_WIGGLE ||
        elBox.right > box.right + CAPTURE_WIGGLE ||
        elBox.left < box.left - CAPTURE_WIGGLE
      ) {
        // Partially outside the box
        for (let i = 0; i < el.childNodes.length; i++) {
          const child = el.childNodes[i];
          if (child.nodeType === ELEMENT_NODE) {
            traverse(child);
          }
        }
        return;
      }
      addText(el);
    }
    function addText(el) {
      let t;
      if (el.tagName === "IMG") {
        t = el.getAttribute("alt") || el.getAttribute("title");
      } else if (el.tagName === "A") {
        t = el.innerText;
        if (
          el.getAttribute("href") &&
          !el.getAttribute("href").startsWith("#")
        ) {
          t += " (" + el.href + ")";
        }
      } else {
        t = el.innerText;
      }
      if (t) {
        text.push(t);
      }
    }
    traverse(document.body);
    if (text.length) {
      let result = text.join("\n");
      result = result.replace(/^\s+/, "");
      result = result.replace(/\s+$/, "");
      result = result.replace(/[ \t]+\n/g, "\n");
      return result;
    }
    return null;
  };

  return exports;
})();
null;
PK
!<b���@@
sitehelper.js/* 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 catcher, callBackground, content */
/** This is a content script added to all screenshots.firefox.com pages, and allows the site to
    communicate with the add-on */

"use strict";

this.sitehelper = (function () {
  catcher.registerHandler(errorObj => {
    callBackground("reportError", errorObj);
  });

  const capabilities = {};
  function registerListener(name, func) {
    capabilities[name] = name;
    document.addEventListener(name, func);
  }

  function sendCustomEvent(name, detail) {
    if (typeof detail === "object") {
      // Note sending an object can lead to security problems, while a string
      // is safe to transfer:
      detail = JSON.stringify(detail);
    }
    document.dispatchEvent(new CustomEvent(name, { detail }));
  }

  registerListener(
    "delete-everything",
    catcher.watchFunction(() => {
      // FIXME: reset some data in the add-on
    }, false)
  );

  registerListener(
    "copy-to-clipboard",
    catcher.watchFunction(event => {
      catcher.watchPromise(callBackground("copyShotToClipboard", event.detail));
    })
  );

  registerListener(
    "show-notification",
    catcher.watchFunction(event => {
      catcher.watchPromise(callBackground("showNotification", event.detail));
    })
  );

  // Depending on the script loading order, the site might get the addon-present event,
  // but probably won't - instead the site will ask for that event after it has loaded
  registerListener(
    "request-addon-present",
    catcher.watchFunction(() => {
      sendCustomEvent("addon-present", capabilities);
    })
  );

  sendCustomEvent("addon-present", capabilities);
})();
null;
PK  7

Zerion Mini Shell 1.0