diff --git a/addon/webextension/background/main.js b/addon/webextension/background/main.js index 91ee1203ca..2f986c0661 100644 --- a/addon/webextension/background/main.js +++ b/addon/webextension/background/main.js @@ -1,4 +1,4 @@ -/* globals selectorLoader, analytics, communication, catcher, log, makeUuid, auth, senderror, startBackground, blobConverters */ +/* globals selectorLoader, analytics, communication, catcher, log, makeUuid, auth, senderror, startBackground, blobConverters buildSettings */ "use strict"; @@ -206,6 +206,25 @@ this.main = (function() { return null; }); + // This is used for truncated full page downloads and copy to clipboards. + // Those longer operations need to display an animated spinner/loader, so + // it's preferable to perform toDataURL() in the background. + communication.register("canvasToDataURL", (sender, imageData) => { + const canvas = document.createElement("canvas"); + canvas.width = imageData.width; + canvas.height = imageData.height; + canvas.getContext("2d").putImageData(imageData, 0, 0); + let dataUrl = canvas.toDataURL(); + if (buildSettings.pngToJpegCutoff && dataUrl.length > buildSettings.pngToJpegCutoff) { + const jpegDataUrl = canvas.toDataURL("image/jpeg"); + if (jpegDataUrl.length < dataUrl.length) { + // Only use the JPEG if it is actually smaller + dataUrl = jpegDataUrl; + } + } + return dataUrl; + }); + communication.register("copyShotToClipboard", (sender, blob) => { return blobConverters.blobToArray(blob).then(buffer => { return browser.clipboard.setImageData( diff --git a/addon/webextension/background/takeshot.js b/addon/webextension/background/takeshot.js index 63a87f1c00..53774c9f55 100644 --- a/addon/webextension/background/takeshot.js +++ b/addon/webextension/background/takeshot.js @@ -18,10 +18,11 @@ this.takeshot = (function() { if (!shot.clipNames().length) { // canvas.drawWindow isn't available, so we fall back to captureVisibleTab capturePromise = screenshotPage(selectedPos, scroll).then((dataUrl) => { + imageBlob = buildSettings.uploadBinary ? blobConverters.dataUrlToBlob(dataUrl) : null; shot.addClip({ createdDate: Date.now(), image: { - url: "data:", + url: buildSettings.uploadBinary ? "" : dataUrl, captureType, text: captureText, location: selectedPos, @@ -33,17 +34,6 @@ this.takeshot = (function() { }); }); } - let convertBlobPromise = Promise.resolve(); - if (buildSettings.uploadBinary && !imageBlob) { - const clipImage = shot.getClip(shot.clipNames()[0]).image; - imageBlob = blobConverters.dataUrlToBlob(clipImage.url); - clipImage.url = ""; - } else if (!buildSettings.uploadBinary && imageBlob) { - convertBlobPromise = blobConverters.blobToDataUrl(imageBlob).then((dataUrl) => { - shot.getClip(shot.clipNames()[0]).image.url = dataUrl; - }); - imageBlob = null; - } const shotAbTests = {}; const abTests = auth.getAbTests(); for (const testName of Object.keys(abTests)) { @@ -55,8 +45,6 @@ this.takeshot = (function() { shot.abTests = shotAbTests; } return catcher.watchPromise(capturePromise.then(() => { - return convertBlobPromise; - }).then(() => { if (buildSettings.uploadBinary) { const blobToUrlPromise = blobConverters.blobToDataUrl(imageBlob); return thumbnailGenerator.createThumbnailBlobFromPromise(shot, blobToUrlPromise); diff --git a/addon/webextension/selector/shooter.js b/addon/webextension/selector/shooter.js index 60642c6897..7e6b5bfa4c 100644 --- a/addon/webextension/selector/shooter.js +++ b/addon/webextension/selector/shooter.js @@ -39,10 +39,7 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars supportsDrawWindow = !!ctx.drawWindow; })(); - const screenshotPage = exports.screenshotPage = function(selectedPos, captureType) { - if (!supportsDrawWindow) { - return null; - } + function captureToCanvas(selectedPos, captureType) { const height = selectedPos.bottom - selectedPos.top; const width = selectedPos.right - selectedPos.left; const canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); @@ -61,6 +58,14 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars } 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) { @@ -73,6 +78,18 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars return dataUrl; }; + function screenshotPageAsync(selectedPos, captureType) { + if (!supportsDrawWindow) { + return Promise.resolve(null); + } + const canvas = captureToCanvas(selectedPos, captureType); + ui.iframe.showLoader(); + const width = selectedPos.right - selectedPos.left; + const height = selectedPos.bottom - selectedPos.top; + const imageData = canvas.getContext("2d").getImageData(0, 0, width, height); + return callBackground("canvasToDataURL", imageData); + } + let isSaving = null; exports.takeShot = function(captureType, selectedPos, url) { @@ -109,12 +126,12 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars let type = blobConverters.getTypeFromDataUrl(dataUrl); type = type ? type.split("/", 2)[1] : null; if (dataUrl) { - imageBlob = blobConverters.dataUrlToBlob(dataUrl); + imageBlob = buildSettings.uploadBinary ? blobConverters.dataUrlToBlob(dataUrl) : null; shotObject.delAllClips(); shotObject.addClip({ createdDate: Date.now(), image: { - url: "data:", + url: buildSettings.uploadBinary ? "" : dataUrl, type, captureType, text: captureText, @@ -164,34 +181,36 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars }; exports.downloadShot = function(selectedPos, previewDataUrl) { - const dataUrl = previewDataUrl || screenshotPage(selectedPos); - let promise = Promise.resolve(dataUrl); - if (!dataUrl) { - promise = callBackground( - "screenshotPage", - selectedPos.asJson(), - { - scrollX: window.scrollX, - scrollY: window.scrollY, - innerHeight: window.innerHeight, - innerWidth: window.innerWidth + const shotPromise = previewDataUrl ? Promise.resolve(previewDataUrl) : screenshotPageAsync(selectedPos, "fullPage"); + catcher.watchPromise(shotPromise.then(dataUrl => { + let promise = Promise.resolve(dataUrl); + if (!dataUrl) { + promise = callBackground( + "screenshotPage", + selectedPos.asJson(), + { + 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 + } }); - } - 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(); - })); + ui.triggerDownload(dataUrl, shotObject.filename); + uicontrol.deactivate(); + })); + })) }; let copyInProgress = null; @@ -212,12 +231,14 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars copyInProgress = null; } } - const dataUrl = previewDataUrl || screenshotPage(selectedPos); - const blob = blobConverters.dataUrlToBlob(dataUrl); - catcher.watchPromise(callBackground("copyShotToClipboard", blob).then(() => { - uicontrol.deactivate(); - unsetCopyInProgress(); - }, unsetCopyInProgress)); + const shotPromise = previewDataUrl ? Promise.resolve(previewDataUrl) : screenshotPageAsync(selectedPos, "fullPage"); + catcher.watchPromise(shotPromise.then(dataUrl => { + const blob = blobConverters.dataUrlToBlob(dataUrl); + catcher.watchPromise(callBackground("copyShotToClipboard", blob).then(() => { + uicontrol.deactivate(); + unsetCopyInProgress(); + }, unsetCopyInProgress)); + })); }; exports.sendEvent = function(...args) { diff --git a/addon/webextension/selector/ui.js b/addon/webextension/selector/ui.js index 7c473f51fd..95deaa47f7 100644 --- a/addon/webextension/selector/ui.js +++ b/addon/webextension/selector/ui.js @@ -420,6 +420,9 @@ this.ui = (function() { // eslint-disable-line no-unused-vars } + `; installHandlerOnDocument(this.document); @@ -460,6 +463,12 @@ this.ui = (function() { // eslint-disable-line no-unused-vars this.element.focus(); }, + showLoader() { + this.document.body.querySelector(".preview-image").style.display = "none"; + this.document.body.querySelector(".notice").style.display = "none"; + this.document.body.querySelector(".loader").style.display = ""; + }, + remove() { this.hide(); util.removeNode(this.element); @@ -486,6 +495,13 @@ this.ui = (function() { // eslint-disable-line no-unused-vars this.currentIframe.unhide(); }, + showLoader() { + if (this.currentIframe.showLoader) { + this.currentIframe.showLoader(); + this.currentIframe.unhide(); + } + }, + getElementFromPoint(x, y) { return this.currentIframe.getElementFromPoint(x, y); }, diff --git a/addon/webextension/selector/uicontrol.js b/addon/webextension/selector/uicontrol.js index b423c56091..860e2f3476 100644 --- a/addon/webextension/selector/uicontrol.js +++ b/addon/webextension/selector/uicontrol.js @@ -200,15 +200,15 @@ this.uicontrol = (function() { }, onDownloadPreview: () => { sendEvent(`download-${captureType.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()}`, "download-preview-button"); - // Downloaded shots don't have dimension limits const previewDataUrl = (captureType === "fullPageTruncated") ? null : dataUrl; + // Downloaded shots don't have dimension limits removeDimensionLimitsOnFullPageShot(); shooter.downloadShot(selectedPos, previewDataUrl); }, onCopyPreview: () => { sendEvent(`copy-${captureType.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()}`, "copy-preview-button"); - // Copied shots don't have dimension limits const previewDataUrl = (captureType === "fullPageTruncated") ? null : dataUrl; + // Copied shots don't have dimension limits removeDimensionLimitsOnFullPageShot(); shooter.copyShot(selectedPos, previewDataUrl); }