/* 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 ;
Messung V0.5 C=92 H=97 G=94
¤ Dauer der Verarbeitung: 0.25 Sekunden
(vorverarbeitet)
¤
*© Formatika GbR, Deutschland