gecko-dev/browser/extensions/screenshots/selector/shooter.js
Jared Hirsch 34900f4fc3 Bug 1498410 - Part 3 - Export Screenshots 35.0.0 (code excluding translations and Raven upgrade); r=aswan,ianbicking
MozReview-Commit-ID: IF010Y5ERks

Differential Revision: https://phabricator.services.mozilla.com/D8505

--HG--
rename : browser/extensions/screenshots/webextension/assertIsBlankDocument.js => browser/extensions/screenshots/assertIsBlankDocument.js
rename : browser/extensions/screenshots/webextension/assertIsTrusted.js => browser/extensions/screenshots/assertIsTrusted.js
rename : browser/extensions/screenshots/webextension/background/analytics.js => browser/extensions/screenshots/background/analytics.js
rename : browser/extensions/screenshots/webextension/background/auth.js => browser/extensions/screenshots/background/auth.js
rename : browser/extensions/screenshots/webextension/background/communication.js => browser/extensions/screenshots/background/communication.js
rename : browser/extensions/screenshots/webextension/background/deviceInfo.js => browser/extensions/screenshots/background/deviceInfo.js
rename : browser/extensions/screenshots/webextension/background/main.js => browser/extensions/screenshots/background/main.js
rename : browser/extensions/screenshots/webextension/background/selectorLoader.js => browser/extensions/screenshots/background/selectorLoader.js
rename : browser/extensions/screenshots/webextension/background/senderror.js => browser/extensions/screenshots/background/senderror.js
rename : browser/extensions/screenshots/webextension/background/startBackground.js => browser/extensions/screenshots/background/startBackground.js
rename : browser/extensions/screenshots/webextension/background/takeshot.js => browser/extensions/screenshots/background/takeshot.js
rename : browser/extensions/screenshots/webextension/blank.html => browser/extensions/screenshots/blank.html
rename : browser/extensions/screenshots/webextension/blobConverters.js => browser/extensions/screenshots/blobConverters.js
rename : browser/extensions/screenshots/webextension/build/buildSettings.js => browser/extensions/screenshots/build/buildSettings.js
rename : browser/extensions/screenshots/webextension/build/inlineSelectionCss.js => browser/extensions/screenshots/build/inlineSelectionCss.js
rename : browser/extensions/screenshots/webextension/build/onboardingCss.js => browser/extensions/screenshots/build/onboardingCss.js
rename : browser/extensions/screenshots/webextension/build/onboardingHtml.js => browser/extensions/screenshots/build/onboardingHtml.js
rename : browser/extensions/screenshots/webextension/build/selection.js => browser/extensions/screenshots/build/selection.js
rename : browser/extensions/screenshots/webextension/build/shot.js => browser/extensions/screenshots/build/shot.js
rename : browser/extensions/screenshots/webextension/build/thumbnailGenerator.js => browser/extensions/screenshots/build/thumbnailGenerator.js
rename : browser/extensions/screenshots/webextension/catcher.js => browser/extensions/screenshots/catcher.js
rename : browser/extensions/screenshots/webextension/clipboard.js => browser/extensions/screenshots/clipboard.js
rename : browser/extensions/screenshots/webextension/domainFromUrl.js => browser/extensions/screenshots/domainFromUrl.js
rename : browser/extensions/screenshots/webextension/icons/back-highlight.svg => browser/extensions/screenshots/icons/back-highlight.svg
rename : browser/extensions/screenshots/webextension/icons/back.svg => browser/extensions/screenshots/icons/back.svg
rename : browser/extensions/screenshots/webextension/icons/cancel.svg => browser/extensions/screenshots/icons/cancel.svg
rename : browser/extensions/screenshots/webextension/icons/cloud.svg => browser/extensions/screenshots/icons/cloud.svg
rename : browser/extensions/screenshots/webextension/icons/copied-notification.svg => browser/extensions/screenshots/icons/copied-notification.svg
rename : browser/extensions/screenshots/webextension/icons/copy.svg => browser/extensions/screenshots/icons/copy.svg
rename : browser/extensions/screenshots/webextension/icons/done.svg => browser/extensions/screenshots/icons/done.svg
rename : browser/extensions/screenshots/webextension/icons/download.svg => browser/extensions/screenshots/icons/download.svg
rename : browser/extensions/screenshots/webextension/icons/help-16.svg => browser/extensions/screenshots/icons/help-16.svg
rename : browser/extensions/screenshots/webextension/icons/icon-highlight-32-v2.svg => browser/extensions/screenshots/icons/icon-highlight-32-v2.svg
rename : browser/extensions/screenshots/webextension/icons/icon-v2.svg => browser/extensions/screenshots/icons/icon-v2.svg
rename : browser/extensions/screenshots/webextension/icons/icon-welcome-face-without-eyes.svg => browser/extensions/screenshots/icons/icon-welcome-face-without-eyes.svg
rename : browser/extensions/screenshots/webextension/icons/menu-fullpage.svg => browser/extensions/screenshots/icons/menu-fullpage.svg
rename : browser/extensions/screenshots/webextension/icons/menu-myshot-white.svg => browser/extensions/screenshots/icons/menu-myshot-white.svg
rename : browser/extensions/screenshots/webextension/icons/menu-myshot.svg => browser/extensions/screenshots/icons/menu-myshot.svg
rename : browser/extensions/screenshots/webextension/icons/menu-visible.svg => browser/extensions/screenshots/icons/menu-visible.svg
rename : browser/extensions/screenshots/webextension/icons/onboarding-1.png => browser/extensions/screenshots/icons/onboarding-1.png
rename : browser/extensions/screenshots/webextension/icons/onboarding-2.png => browser/extensions/screenshots/icons/onboarding-2.png
rename : browser/extensions/screenshots/webextension/icons/onboarding-4.png => browser/extensions/screenshots/icons/onboarding-4.png
rename : browser/extensions/screenshots/webextension/icons/onboarding-5.png => browser/extensions/screenshots/icons/onboarding-5.png
rename : browser/extensions/screenshots/webextension/log.js => browser/extensions/screenshots/log.js
rename : browser/extensions/screenshots/webextension/makeUuid.js => browser/extensions/screenshots/makeUuid.js
rename : browser/extensions/screenshots/webextension/manifest.json => browser/extensions/screenshots/manifest.json
rename : browser/extensions/screenshots/webextension/onboarding/slides.html => browser/extensions/screenshots/onboarding/slides.html
rename : browser/extensions/screenshots/webextension/onboarding/slides.js => browser/extensions/screenshots/onboarding/slides.js
rename : browser/extensions/screenshots/webextension/randomString.js => browser/extensions/screenshots/randomString.js
rename : browser/extensions/screenshots/webextension/selector/callBackground.js => browser/extensions/screenshots/selector/callBackground.js
rename : browser/extensions/screenshots/webextension/selector/documentMetadata.js => browser/extensions/screenshots/selector/documentMetadata.js
rename : browser/extensions/screenshots/webextension/selector/shooter.js => browser/extensions/screenshots/selector/shooter.js
rename : browser/extensions/screenshots/webextension/selector/ui.js => browser/extensions/screenshots/selector/ui.js
rename : browser/extensions/screenshots/webextension/selector/uicontrol.js => browser/extensions/screenshots/selector/uicontrol.js
rename : browser/extensions/screenshots/webextension/selector/util.js => browser/extensions/screenshots/selector/util.js
rename : browser/extensions/screenshots/webextension/sitehelper.js => browser/extensions/screenshots/sitehelper.js
extra : rebase_source : ef20dd4f7efd19de76dd4a16a9aae43f5560fd69
extra : source : 426257ad4b83e3cffc628f76ae8bd55c2fa4fbaf
2018-10-15 20:10:31 +00:00

283 lines
9.4 KiB
JavaScript

/* globals global, documentMetadata, util, uicontrol, ui, catcher */
/* globals buildSettings, 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;
const MAX_CANVAS_DIMENSION = 32767;
let backend;
let shotObject;
let supportsDrawWindow;
const callBackground = global.callBackground;
const clipboard = global.clipboard;
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));
});
catcher.watchFunction(() => {
const canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
const ctx = canvas.getContext("2d");
supportsDrawWindow = !!ctx.drawWindow;
})();
function captureToCanvas(selectedPos, captureType) {
let height = selectedPos.bottom - selectedPos.top;
let width = selectedPos.right - selectedPos.left;
const canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
const ctx = canvas.getContext("2d");
// Scale the canvas for high-density displays, except for full-page shots.
let expand = window.devicePixelRatio !== 1;
if (captureType === "fullPage" || captureType === "fullPageTruncated") {
expand = false;
canvas.width = width;
canvas.height = height;
} else {
canvas.width = width * window.devicePixelRatio;
canvas.height = height * window.devicePixelRatio;
}
if (expand) {
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
}
// Double-check canvas width and height are within the canvas pixel limit.
// If the canvas dimensions are too great, crop the canvas and also crop
// the selection by a devicePixelRatio-scaled amount.
if (canvas.width > MAX_CANVAS_DIMENSION) {
canvas.width = MAX_CANVAS_DIMENSION;
width = expand ? Math.floor(canvas.width / window.devicePixelRatio) : canvas.width;
}
if (canvas.height > MAX_CANVAS_DIMENSION) {
canvas.height = MAX_CANVAS_DIMENSION;
height = expand ? Math.floor(canvas.height / window.devicePixelRatio) : canvas.height;
}
ui.iframe.hide();
ctx.drawWindow(window, selectedPos.left, selectedPos.top, width, height, "#fff");
return canvas;
}
const screenshotPage = exports.screenshotPage = function(selectedPos, captureType) {
if (!supportsDrawWindow) {
return null;
}
const canvas = captureToCanvas(selectedPos, captureType);
const limit = buildSettings.pngToJpegCutoff;
let dataUrl = canvas.toDataURL();
if (limit && dataUrl.length > limit) {
const jpegDataUrl = canvas.toDataURL("image/jpeg");
if (jpegDataUrl.length < dataUrl.length) {
// Only use the JPEG if it is actually smaller
dataUrl = jpegDataUrl;
}
}
return dataUrl;
};
function screenshotPageAsync(selectedPos, captureType) {
if (!supportsDrawWindow) {
return Promise.resolve(null);
}
const canvas = captureToCanvas(selectedPos, captureType);
ui.iframe.showLoader();
const imageData = canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height);
return callBackground("canvasToDataURL", imageData);
}
let isSaving = null;
exports.takeShot = function(captureType, selectedPos, url) {
// isSaving indicates we're aleady in the middle of saving
// we use a timeout so in the case of a failure the button will
// still start working again
if (Math.floor(selectedPos.left) === Math.floor(selectedPos.right) ||
Math.floor(selectedPos.top) === Math.floor(selectedPos.bottom)) {
const exc = new Error("Empty selection");
exc.popupMessage = "EMPTY_SELECTION";
exc.noReport = true;
catcher.unhandled(exc);
return;
}
let imageBlob;
const uicontrol = global.uicontrol;
let deactivateAfterFinish = true;
if (isSaving) {
return;
}
isSaving = setTimeout(() => {
if (typeof ui !== "undefined") {
// ui might disappear while the timer is running because the save succeeded
ui.Box.clearSaveDisabled();
}
isSaving = null;
}, 1000);
selectedPos = selectedPos.toJSON();
let captureText = "";
if (buildSettings.captureText) {
captureText = util.captureEnclosedText(selectedPos);
}
const dataUrl = url || screenshotPage(selectedPos, captureType);
let type = blobConverters.getTypeFromDataUrl(dataUrl);
type = type ? type.split("/", 2)[1] : null;
if (dataUrl) {
imageBlob = buildSettings.uploadBinary ? blobConverters.dataUrlToBlob(dataUrl) : null;
shotObject.delAllClips();
shotObject.addClip({
createdDate: Date.now(),
image: {
url: buildSettings.uploadBinary ? "" : dataUrl,
type,
captureType,
text: captureText,
location: selectedPos,
dimensions: {
x: selectedPos.right - selectedPos.left,
y: selectedPos.bottom - selectedPos.top,
},
},
});
}
catcher.watchPromise(callBackground("takeShot", {
captureType,
captureText,
scroll: {
scrollX: window.scrollX,
scrollY: window.scrollY,
innerHeight: window.innerHeight,
innerWidth: window.innerWidth,
},
selectedPos,
shotId: shotObject.id,
shot: shotObject.toJSON(),
imageBlob,
}).then((url) => {
return clipboard.copy(url).then((copied) => {
return callBackground("openShot", { url, copied });
});
}, (error) => {
if ("popupMessage" in error && (error.popupMessage === "REQUEST_ERROR" || error.popupMessage === "CONNECTION_ERROR")) {
// The error has been signaled to the user, but unlike other errors (or
// success) we should not abort the selection
deactivateAfterFinish = false;
// We need to unhide the UI since screenshotPage() hides it.
ui.iframe.unhide();
return;
}
if (error.name !== "BackgroundError") {
// BackgroundError errors are reported in the Background page
throw error;
}
}).then(() => {
if (deactivateAfterFinish) {
uicontrol.deactivate();
}
}));
};
exports.downloadShot = function(selectedPos, previewDataUrl, type) {
const shotPromise = previewDataUrl ? Promise.resolve(previewDataUrl) : screenshotPageAsync(selectedPos, type);
catcher.watchPromise(shotPromise.then(dataUrl => {
let promise = Promise.resolve(dataUrl);
if (!dataUrl) {
promise = callBackground(
"screenshotPage",
selectedPos.toJSON(),
{
scrollX: window.scrollX,
scrollY: window.scrollY,
innerHeight: window.innerHeight,
innerWidth: window.innerWidth,
});
}
catcher.watchPromise(promise.then((dataUrl) => {
let type = blobConverters.getTypeFromDataUrl(dataUrl);
type = type ? type.split("/", 2)[1] : null;
shotObject.delAllClips();
shotObject.addClip({
createdDate: Date.now(),
image: {
url: dataUrl,
type,
location: selectedPos,
},
});
ui.triggerDownload(dataUrl, shotObject.filename);
uicontrol.deactivate();
}));
}));
};
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) : screenshotPageAsync(selectedPos, type);
catcher.watchPromise(shotPromise.then(dataUrl => {
const blob = blobConverters.dataUrlToBlob(dataUrl);
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});
}
callBackground("sendEvent", ...args);
};
catcher.watchFunction(() => {
shotObject = new AbstractShot(
backend,
randomString(RANDOM_STRING_LENGTH) + "/" + domainFromUrl(location),
{
origin: shot.originFromUrl(location.href),
}
);
shotObject.update(documentMetadata());
})();
return exports;
})();
null;