/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* globals log, util, catcher, inlineSelectionCss, callBackground, assertIsTrusted, assertIsBlankDocument, buildSettings blobConverters */
"use strict";
this.ui = (function() { // eslint-disable-line no-unused-vars
const exports = {};
const SAVE_BUTTON_HEIGHT = 50;
const { watchFunction } = catcher;
exports.isHeader = function(el) {
while (el) {
if (el.classList &&
(el.classList.contains("myshots-button") ||
el.classList.contains("visible") ||
el.classList.contains("full-page") ||
el.classList.contains("cancel-shot"))) {
return true;
}
el = el.parentNode;
}
return false;
};
const substitutedCss = inlineSelectionCss.replace(/MOZ_EXTENSION([^"]+)/g, (match, filename) => {
return browser.extension.getURL(filename);
});
function makeEl(tagName, className) {
if (!iframe.document()) {
throw new Error("Attempted makeEl before iframe was initialized");
}
const el = iframe.document().createElement(tagName);
if (className) {
el.className = className;
}
return el;
}
function onResize() {
if (this.sizeTracking.windowDelayer) {
clearTimeout(this.sizeTracking.windowDelayer);
}
this.sizeTracking.windowDelayer = setTimeout(watchFunction(() => {
this.updateElementSize(true);
}), 50);
}
function localizeText(doc) {
const els = doc.querySelectorAll("[data-l10n-id], [data-l10n-title]");
for (const el of els) {
const id = el.getAttribute("data-l10n-id");
if (id) {
const text = browser.i18n.getMessage(id);
el.textContent = text;
}
const title = el.getAttribute("data-l10n-title");
if (title) {
const titleText = browser.i18n.getMessage(title);
const sanitized = titleText && titleText.replace("&", "&")
.replace('"', """)
.replace("<", "<")
.replace(">", ">");
el.setAttribute("title", sanitized);
}
}
}
function highContrastCheck(win) {
const doc = win.document;
const el = doc.createElement("div");
el.style.backgroundImage = "url('#')";
el.style.display = "none";
doc.body.appendChild(el);
const computed = win.getComputedStyle(el);
doc.body.removeChild(el);
// When Windows is in High Contrast mode, Firefox replaces background
// image URLs with the string "none".
return (computed && computed.backgroundImage === "none");
}
const showMyShots = exports.showMyShots = function() {
return window.hasAnyShots;
};
function initializeIframe() {
const el = document.createElement("iframe");
el.src = browser.extension.getURL("blank.html");
el.style.zIndex = "99999999999";
el.style.border = "none";
el.style.top = "0";
el.style.left = "0";
el.style.margin = "0";
el.scrolling = "no";
el.style.clip = "auto";
return el;
}
const iframeSelection = exports.iframeSelection = {
element: null,
addClassName: "",
sizeTracking: {
timer: null,
windowDelayer: null,
lastHeight: null,
lastWidth: null,
},
document: null,
display(installHandlerOnDocument) {
return new Promise((resolve, reject) => {
if (!this.element) {
this.element = initializeIframe();
this.element.id = "firefox-screenshots-selection-iframe";
this.element.style.display = "none";
this.element.style.setProperty("position", "absolute", "important");
this.element.style.setProperty("background-color", "transparent");
this.element.setAttribute("role", "dialog");
this.updateElementSize();
this.element.addEventListener("load", watchFunction(() => {
this.document = this.element.contentDocument;
assertIsBlankDocument(this.document);
// eslint-disable-next-line no-unsanitized/property
this.document.documentElement.innerHTML = `
`;
installHandlerOnDocument(this.document);
if (this.addClassName) {
this.document.body.className = this.addClassName;
}
this.document.documentElement.dir = browser.i18n.getMessage("@@bidi_dir");
this.document.documentElement.lang = browser.i18n.getMessage("@@ui_locale");
resolve();
}), {once: true});
document.body.appendChild(this.element);
} else {
resolve();
}
});
},
hide() {
this.element.style.display = "none";
this.stopSizeWatch();
},
unhide() {
this.updateElementSize();
this.element.style.display = "block";
catcher.watchPromise(callBackground("sendEvent", "internal", "unhide-selection-frame"));
if (highContrastCheck(this.element.contentWindow)) {
this.element.contentDocument.body.classList.add("hcm");
}
this.initSizeWatch();
this.element.focus();
},
updateElementSize(force) {
// Note: if someone sizes down the page, then the iframe will keep the
// document from naturally shrinking. We use force to temporarily hide
// the element so that we can tell if the document shrinks
const visible = this.element.style.display !== "none";
if (force && visible) {
this.element.style.display = "none";
}
const height = Math.max(
document.documentElement.clientHeight,
document.body.clientHeight,
document.documentElement.scrollHeight,
document.body.scrollHeight);
if (height !== this.sizeTracking.lastHeight) {
this.sizeTracking.lastHeight = height;
this.element.style.height = height + "px";
}
// Do not use window.innerWidth since that includes the width of the
// scroll bar.
const width = Math.max(
document.documentElement.clientWidth,
document.body.clientWidth,
document.documentElement.scrollWidth,
document.body.scrollWidth);
if (width !== this.sizeTracking.lastWidth) {
this.sizeTracking.lastWidth = width;
this.element.style.width = width + "px";
// Since this frame has an absolute position relative to the parent
// document, if the parent document's body has a relative position and
// left and/or top not at 0, then the left and/or top of the parent
// document's body is not at (0, 0) of the viewport. That makes the
// frame shifted relative to the viewport. These margins negates that.
if (window.getComputedStyle(document.body).position === "relative") {
const docBoundingRect = document.documentElement.getBoundingClientRect();
const bodyBoundingRect = document.body.getBoundingClientRect();
this.element.style.marginLeft = `-${bodyBoundingRect.left - docBoundingRect.left}px`;
this.element.style.marginTop = `-${bodyBoundingRect.top - docBoundingRect.top}px`;
}
}
if (force && visible) {
this.element.style.display = "block";
}
},
initSizeWatch() {
this.stopSizeWatch();
this.sizeTracking.timer = setInterval(watchFunction(this.updateElementSize.bind(this)), 2000);
window.addEventListener("resize", this.onResize, true);
},
stopSizeWatch() {
if (this.sizeTracking.timer) {
clearTimeout(this.sizeTracking.timer);
this.sizeTracking.timer = null;
}
if (this.sizeTracking.windowDelayer) {
clearTimeout(this.sizeTracking.windowDelayer);
this.sizeTracking.windowDelayer = null;
}
this.sizeTracking.lastHeight = this.sizeTracking.lastWidth = null;
window.removeEventListener("resize", this.onResize, true);
},
getElementFromPoint(x, y) {
this.element.style.pointerEvents = "none";
let el;
try {
el = document.elementFromPoint(x, y);
} finally {
this.element.style.pointerEvents = "";
}
return el;
},
remove() {
this.stopSizeWatch();
util.removeNode(this.element);
this.element = this.document = null;
},
};
iframeSelection.onResize = watchFunction(assertIsTrusted(onResize.bind(iframeSelection)), true);
const iframePreSelection = exports.iframePreSelection = {
element: null,
document: null,
display(installHandlerOnDocument, standardOverlayCallbacks) {
return new Promise((resolve, reject) => {
if (!this.element) {
this.element = initializeIframe();
this.element.id = "firefox-screenshots-preselection-iframe";
this.element.style.setProperty("position", "fixed", "important");
this.element.style.setProperty("background-color", "transparent");
this.element.style.width = "100%";
this.element.style.height = "100%";
this.element.setAttribute("role", "dialog");
this.element.addEventListener("load", watchFunction(() => {
this.document = this.element.contentDocument;
assertIsBlankDocument(this.document);
// eslint-disable-next-line no-unsanitized/property
this.document.documentElement.innerHTML = `