mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-11 05:39:41 +02:00
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
283 lines
9.4 KiB
JavaScript
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;
|