diff --git a/addon/webextension/background/senderror.js b/addon/webextension/background/senderror.js index 83b16069f3..0983c1dcbf 100644 --- a/addon/webextension/background/senderror.js +++ b/addon/webextension/background/senderror.js @@ -37,6 +37,9 @@ this.senderror = (function() { MY_SHOTS: { title: browser.i18n.getMessage("selfScreenshotErrorTitle") }, + EMPTY_SELECTION: { + title: browser.i18n.getMessage("emptySelectionErrorTitle") + }, generic: { title: browser.i18n.getMessage("genericErrorTitle"), info: browser.i18n.getMessage("genericErrorDetails"), @@ -112,7 +115,9 @@ this.senderror = (function() { if (!errorObj.noPopup) { exports.showError(errorObj); } - exports.reportError(errorObj); + if (!errorObj.noReport) { + exports.reportError(errorObj); + } }); return exports; diff --git a/addon/webextension/selector/shooter.js b/addon/webextension/selector/shooter.js index a8170e3248..cf23709a83 100644 --- a/addon/webextension/selector/shooter.js +++ b/addon/webextension/selector/shooter.js @@ -67,12 +67,21 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars // 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)) { + let exc = new Error("Empty selection"); + exc.popupMessage = "EMPTY_SELECTION"; + exc.noReport = true; + catcher.unhandled(exc); + return; + } const uicontrol = global.uicontrol; let deactivateAfterFinish = true; if (isSaving) { return; } isSaving = setTimeout(() => { + ui.Box.clearSaveDisabled(); isSaving = null; }, 1000); selectedPos = selectedPos.asJson(); @@ -82,6 +91,7 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars } let dataUrl = screenshotPage(selectedPos); if (dataUrl) { + shot.delAllClips(); shot.addClip({ createdDate: Date.now(), image: { diff --git a/addon/webextension/selector/ui.js b/addon/webextension/selector/ui.js index 7db4b2bfe6..8a5d3de69b 100644 --- a/addon/webextension/selector/ui.js +++ b/addon/webextension/selector/ui.js @@ -541,6 +541,10 @@ this.ui = (function() { // eslint-disable-line no-unused-vars return false; }, + clearSaveDisabled() { + this.save.removeAttribute("disabled"); + }, + el: null, boxTopEl: null, boxLeftEl: null, diff --git a/docs/error-handling.md b/docs/error-handling.md index 118c6314c2..d7336eb0dc 100644 --- a/docs/error-handling.md +++ b/docs/error-handling.md @@ -105,3 +105,5 @@ Note that any information will be serialized as JSON. Specifically `undefined` If you add `exc.popupMessage = "Something happened"` then that detail will be added. Be careful about localization here. Add `exc.noPopup = true` if you don't want the user notified about the error (but the error will still be sent to Sentry). + +Add `exc.noReport = true` if you don't want the error reported to Sentry. diff --git a/locales/en-US/webextension.properties b/locales/en-US/webextension.properties index 4d7112fa20..d0b5b3e475 100644 --- a/locales/en-US/webextension.properties +++ b/locales/en-US/webextension.properties @@ -27,6 +27,8 @@ loginErrorDetails = We couldn’t save your shot because there is a problem with unshootablePageErrorTitle = We can’t screenshot this page. unshootablePageErrorDetails = This isn’t a standard Web page, so you can’t take a screenshot of it. selfScreenshotErrorTitle = You can’t take a shot of a Firefox Screenshots page! +# Fired when someone makes a zero-width or zero-height selection +emptySelectionErrorTitle = Your selection is too small genericErrorTitle = Whoa! Firefox Screenshots went haywire. genericErrorDetails = We’re not sure what just happened. Care to try again or take a shot of a different page? # Section for onboarding strings diff --git a/server/src/servershot.js b/server/src/servershot.js index d77656be18..974178f4c7 100644 --- a/server/src/servershot.js +++ b/server/src/servershot.js @@ -15,16 +15,19 @@ const PNG_HEADER = Buffer.from(PNG_HEADER_BASE64, "base64"); function assertPng(dataUrl) { const urlHeader = "data:image/png;base64,"; if (!dataUrl.startsWith(urlHeader)) { + mozlog.warn("invalid-data-url", {msg: "Invalid data: URL submitted", prefix: dataUrl.substr(0, urlHeader.length + 10)}); throw new Error('invalid data url'); } // only decode enough to get the header // we're lucky that 9 bytes is exactly 12 base64 characters const base64Header = dataUrl.substr(urlHeader.length, PNG_HEADER_BASE64.length); if (base64Header.length < PNG_HEADER_BASE64.length) { + mozlog.warn("invalid-data-image", {msg: "Invalid PNG image submitted", prefix: dataUrl.substr(0, urlHeader.length + PNG_HEADER_BASE64.length)}); throw new Error('invalid image'); } const header = Buffer.from(base64Header, "base64"); // 9 bytes if (!PNG_HEADER.equals(header.slice(0, 8))) { + mozlog.warn("invalid-data-image-decoded", {msg: "Invalid PNG image (after base64 decoding)"}); throw new Error('invalid png'); } } diff --git a/shared/shot.js b/shared/shot.js index 1db7f5026a..70da019cdb 100644 --- a/shared/shot.js +++ b/shared/shot.js @@ -493,6 +493,9 @@ class AbstractShot { } delete this._clips[name]; } + delAllClips() { + this._clips = {}; + } biggestClipSortOrder() { let biggest = 0; for (let clipId in this._clips) {