From 1c53d3b56b74eda2c14071d6bc2b878dc13e5f63 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Thu, 13 Apr 2017 09:49:17 +0100 Subject: [PATCH] Bug 1346825 - Import Screenshots version 6.3.0 into mozilla-central. rs=Mossop. This is imported from https://github.com/mozilla-services/screenshots/. It has been reviewed as patches landed, but also reviewed by Mossop and kmag. This also includes the patch from bug 1356394 MozReview-Commit-ID: FXIVw7WjxlN --HG-- extra : source : 426aa68b32f0c6405bc297498d6f80996f4e013a extra : amend_source : 38c0f96735572c1ab24746d6766f8170a425a7d0 --- browser/extensions/screenshots/bootstrap.js | 145 + browser/extensions/screenshots/install.rdf | 20 + browser/extensions/screenshots/moz.build | 5 +- .../screenshots/test/browser/browser.ini | 5 + .../browser/browser_screenshots_ui_check.js | 20 + .../screenshots/test/browser/head.js | 61 + .../webextension/_locales/ach/messages.json | 123 + .../webextension/_locales/be/messages.json | 123 + .../webextension/_locales/bg/messages.json | 123 + .../webextension/_locales/bn_BD/messages.json | 123 + .../webextension/_locales/cs/messages.json | 123 + .../webextension/_locales/de/messages.json | 123 + .../webextension/_locales/dsb/messages.json | 123 + .../webextension/_locales/el/messages.json | 123 + .../webextension/_locales/en_GB/messages.json | 106 + .../webextension/_locales/en_US/messages.json | 123 + .../webextension/_locales/es_AR/messages.json | 123 + .../webextension/_locales/es_CL/messages.json | 123 + .../webextension/_locales/es_ES/messages.json | 123 + .../webextension/_locales/es_MX/messages.json | 123 + .../webextension/_locales/et/messages.json | 123 + .../webextension/_locales/fa/messages.json | 123 + .../webextension/_locales/fr/messages.json | 123 + .../webextension/_locales/fy_NL/messages.json | 123 + .../webextension/_locales/gu_IN/messages.json | 123 + .../webextension/_locales/he/messages.json | 94 + .../webextension/_locales/hsb/messages.json | 123 + .../webextension/_locales/hu/messages.json | 123 + .../webextension/_locales/hy_AM/messages.json | 106 + .../webextension/_locales/id/messages.json | 123 + .../webextension/_locales/it/messages.json | 123 + .../webextension/_locales/ja/messages.json | 123 + .../webextension/_locales/kab/messages.json | 123 + .../webextension/_locales/kk/messages.json | 85 + .../webextension/_locales/ko/messages.json | 123 + .../webextension/_locales/lij/messages.json | 106 + .../webextension/_locales/lo/messages.json | 38 + .../webextension/_locales/lt/messages.json | 123 + .../webextension/_locales/ms/messages.json | 106 + .../webextension/_locales/nb_NO/messages.json | 123 + .../webextension/_locales/nl/messages.json | 123 + .../webextension/_locales/nn_NO/messages.json | 123 + .../webextension/_locales/pa_IN/messages.json | 47 + .../webextension/_locales/pl/messages.json | 123 + .../webextension/_locales/pt_BR/messages.json | 123 + .../webextension/_locales/pt_PT/messages.json | 123 + .../webextension/_locales/rm/messages.json | 123 + .../webextension/_locales/ru/messages.json | 123 + .../webextension/_locales/sk/messages.json | 123 + .../webextension/_locales/sl/messages.json | 123 + .../webextension/_locales/sq/messages.json | 14 + .../webextension/_locales/sr/messages.json | 123 + .../webextension/_locales/sv_SE/messages.json | 123 + .../webextension/_locales/th/messages.json | 123 + .../webextension/_locales/tl/messages.json | 123 + .../webextension/_locales/tr/messages.json | 123 + .../webextension/_locales/uk/messages.json | 123 + .../webextension/_locales/ur/messages.json | 103 + .../webextension/_locales/zh_CN/messages.json | 123 + .../webextension/_locales/zh_TW/messages.json | 123 + .../webextension/assertIsTrusted.js | 20 + .../webextension/background/analytics.js | 81 + .../webextension/background/auth.js | 216 ++ .../webextension/background/communication.js | 80 + .../webextension/background/deviceInfo.js | 34 + .../webextension/background/main.js | 276 ++ .../webextension/background/selectorLoader.js | 114 + .../webextension/background/senderror.js | 117 + .../webextension/background/takeshot.js | 128 + .../screenshots/webextension/blank.html | 1 + .../webextension/build/buildSettings.js | 6 + .../webextension/build/inlineSelectionCss.js | 438 +++ .../webextension/build/onboardingCss.js | 244 ++ .../webextension/build/onboardingHtml.js | 66 + .../screenshots/webextension/build/raven.js | 2833 +++++++++++++++++ .../screenshots/webextension/build/shot.js | 727 +++++ .../webextension/buildSettings.js.template | 5 + .../screenshots/webextension/catcher.js | 83 + .../screenshots/webextension/clipboard.js | 23 + .../screenshots/webextension/domainFromUrl.js | 29 + .../screenshots/webextension/icons/back.svg | 10 + .../screenshots/webextension/icons/cancel.svg | 11 + .../screenshots/webextension/icons/copy.png | Bin 0 -> 985 bytes .../screenshots/webextension/icons/done.svg | 10 + .../webextension/icons/download.svg | 11 + .../webextension/icons/icon-128.png | Bin 0 -> 2489 bytes .../webextension/icons/icon-16.png | Bin 0 -> 373 bytes .../webextension/icons/icon-19.png | Bin 0 -> 619 bytes .../webextension/icons/icon-256.png | Bin 0 -> 4474 bytes .../webextension/icons/icon-32.png | Bin 0 -> 727 bytes .../webextension/icons/icon-38.png | Bin 0 -> 1102 bytes .../webextension/icons/icon-48.png | Bin 0 -> 1054 bytes .../webextension/icons/icon-64.png | Bin 0 -> 1371 bytes .../webextension/icons/icon-highlight-19.png | Bin 0 -> 1049 bytes .../webextension/icons/icon-highlight-38.png | Bin 0 -> 1058 bytes .../webextension/icons/icon-starred-19.png | Bin 0 -> 993 bytes .../webextension/icons/icon-starred-38.png | Bin 0 -> 1280 bytes .../webextension/icons/menu-fullpage.svg | 24 + .../webextension/icons/menu-myshot.svg | 17 + .../webextension/icons/menu-visible.svg | 19 + .../webextension/icons/onboarding-1.png | Bin 0 -> 27607 bytes .../webextension/icons/onboarding-2.png | Bin 0 -> 71258 bytes .../webextension/icons/onboarding-3.png | Bin 0 -> 34779 bytes .../webextension/icons/onboarding-4.png | Bin 0 -> 35881 bytes .../screenshots/webextension/log.js | 47 + .../screenshots/webextension/makeUuid.js | 19 + .../screenshots/webextension/manifest.json | 87 + .../webextension/onboarding/slides.html | 60 + .../webextension/onboarding/slides.js | 202 ++ .../screenshots/webextension/randomString.js | 14 + .../webextension/selector/callBackground.js | 21 + .../webextension/selector/documentMetadata.js | 87 + .../webextension/selector/shooter.js | 151 + .../screenshots/webextension/selector/ui.js | 609 ++++ .../webextension/selector/uicontrol.js | 907 ++++++ .../screenshots/webextension/selector/util.js | 106 + .../screenshots/webextension/sitehelper.js | 43 + 117 files changed, 14448 insertions(+), 1 deletion(-) create mode 100644 browser/extensions/screenshots/bootstrap.js create mode 100644 browser/extensions/screenshots/install.rdf create mode 100644 browser/extensions/screenshots/test/browser/browser.ini create mode 100644 browser/extensions/screenshots/test/browser/browser_screenshots_ui_check.js create mode 100644 browser/extensions/screenshots/test/browser/head.js create mode 100644 browser/extensions/screenshots/webextension/_locales/ach/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/be/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/bg/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/bn_BD/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/cs/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/de/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/dsb/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/el/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/en_GB/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/en_US/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/es_AR/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/es_CL/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/es_ES/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/es_MX/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/et/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/fa/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/fr/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/fy_NL/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/gu_IN/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/he/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/hsb/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/hu/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/hy_AM/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/id/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/it/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/ja/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/kab/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/kk/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/ko/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/lij/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/lo/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/lt/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/ms/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/nb_NO/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/nl/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/nn_NO/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/pa_IN/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/pl/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/pt_BR/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/pt_PT/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/rm/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/ru/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/sk/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/sl/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/sq/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/sr/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/sv_SE/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/th/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/tl/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/tr/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/uk/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/ur/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/zh_CN/messages.json create mode 100644 browser/extensions/screenshots/webextension/_locales/zh_TW/messages.json create mode 100644 browser/extensions/screenshots/webextension/assertIsTrusted.js create mode 100644 browser/extensions/screenshots/webextension/background/analytics.js create mode 100644 browser/extensions/screenshots/webextension/background/auth.js create mode 100644 browser/extensions/screenshots/webextension/background/communication.js create mode 100644 browser/extensions/screenshots/webextension/background/deviceInfo.js create mode 100644 browser/extensions/screenshots/webextension/background/main.js create mode 100644 browser/extensions/screenshots/webextension/background/selectorLoader.js create mode 100644 browser/extensions/screenshots/webextension/background/senderror.js create mode 100644 browser/extensions/screenshots/webextension/background/takeshot.js create mode 100644 browser/extensions/screenshots/webextension/blank.html create mode 100644 browser/extensions/screenshots/webextension/build/buildSettings.js create mode 100644 browser/extensions/screenshots/webextension/build/inlineSelectionCss.js create mode 100644 browser/extensions/screenshots/webextension/build/onboardingCss.js create mode 100644 browser/extensions/screenshots/webextension/build/onboardingHtml.js create mode 100644 browser/extensions/screenshots/webextension/build/raven.js create mode 100644 browser/extensions/screenshots/webextension/build/shot.js create mode 100644 browser/extensions/screenshots/webextension/buildSettings.js.template create mode 100644 browser/extensions/screenshots/webextension/catcher.js create mode 100644 browser/extensions/screenshots/webextension/clipboard.js create mode 100644 browser/extensions/screenshots/webextension/domainFromUrl.js create mode 100644 browser/extensions/screenshots/webextension/icons/back.svg create mode 100644 browser/extensions/screenshots/webextension/icons/cancel.svg create mode 100644 browser/extensions/screenshots/webextension/icons/copy.png create mode 100644 browser/extensions/screenshots/webextension/icons/done.svg create mode 100644 browser/extensions/screenshots/webextension/icons/download.svg create mode 100644 browser/extensions/screenshots/webextension/icons/icon-128.png create mode 100644 browser/extensions/screenshots/webextension/icons/icon-16.png create mode 100644 browser/extensions/screenshots/webextension/icons/icon-19.png create mode 100644 browser/extensions/screenshots/webextension/icons/icon-256.png create mode 100644 browser/extensions/screenshots/webextension/icons/icon-32.png create mode 100644 browser/extensions/screenshots/webextension/icons/icon-38.png create mode 100644 browser/extensions/screenshots/webextension/icons/icon-48.png create mode 100644 browser/extensions/screenshots/webextension/icons/icon-64.png create mode 100644 browser/extensions/screenshots/webextension/icons/icon-highlight-19.png create mode 100644 browser/extensions/screenshots/webextension/icons/icon-highlight-38.png create mode 100644 browser/extensions/screenshots/webextension/icons/icon-starred-19.png create mode 100644 browser/extensions/screenshots/webextension/icons/icon-starred-38.png create mode 100644 browser/extensions/screenshots/webextension/icons/menu-fullpage.svg create mode 100644 browser/extensions/screenshots/webextension/icons/menu-myshot.svg create mode 100644 browser/extensions/screenshots/webextension/icons/menu-visible.svg create mode 100644 browser/extensions/screenshots/webextension/icons/onboarding-1.png create mode 100644 browser/extensions/screenshots/webextension/icons/onboarding-2.png create mode 100644 browser/extensions/screenshots/webextension/icons/onboarding-3.png create mode 100644 browser/extensions/screenshots/webextension/icons/onboarding-4.png create mode 100644 browser/extensions/screenshots/webextension/log.js create mode 100644 browser/extensions/screenshots/webextension/makeUuid.js create mode 100644 browser/extensions/screenshots/webextension/manifest.json create mode 100644 browser/extensions/screenshots/webextension/onboarding/slides.html create mode 100644 browser/extensions/screenshots/webextension/onboarding/slides.js create mode 100644 browser/extensions/screenshots/webextension/randomString.js create mode 100644 browser/extensions/screenshots/webextension/selector/callBackground.js create mode 100644 browser/extensions/screenshots/webextension/selector/documentMetadata.js create mode 100644 browser/extensions/screenshots/webextension/selector/shooter.js create mode 100644 browser/extensions/screenshots/webextension/selector/ui.js create mode 100644 browser/extensions/screenshots/webextension/selector/uicontrol.js create mode 100644 browser/extensions/screenshots/webextension/selector/util.js create mode 100644 browser/extensions/screenshots/webextension/sitehelper.js diff --git a/browser/extensions/screenshots/bootstrap.js b/browser/extensions/screenshots/bootstrap.js new file mode 100644 index 000000000000..2d36108b1c88 --- /dev/null +++ b/browser/extensions/screenshots/bootstrap.js @@ -0,0 +1,145 @@ +/* globals AddonManager, Components, LegacyExtensionsUtils, Services, + XPCOMUtils */ + +const OLD_ADDON_PREF_NAME = "extensions.jid1-NeEaf3sAHdKHPA@jetpack.deviceIdInfo"; +const OLD_ADDON_ID = "jid1-NeEaf3sAHdKHPA@jetpack"; +const ADDON_ID = "screenshots@mozilla.org"; +const TELEMETRY_ENABLED_PREF = "toolkit.telemetry.enabled"; +const PREF_BRANCH = "extensions.screenshots."; +const USER_DISABLE_PREF = "extensions.screenshots.disabled"; +const SYSTEM_DISABLE_PREF = "extensions.screenshots.system-disabled"; + +const { interfaces: Ci, utils: Cu } = Components; +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", + "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Console", + "resource://gre/modules/Console.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "LegacyExtensionsUtils", + "resource://gre/modules/LegacyExtensionsUtils.jsm"); + +let addonResourceURI; +let appStartupDone; +const appStartupPromise = new Promise((resolve,reject) => { + appStartupDone = resolve; +}); + +const prefs = Services.prefs; +const prefObserver = { + register: function() { + prefs.addObserver(PREF_BRANCH, this, false); + }, + + unregister: function() { + prefs.removeObserver(PREF_BRANCH, this); + }, + + observe: function(aSubject, aTopic, aData) { + // aSubject is the nsIPrefBranch we're observing (after appropriate QI) + // aData is the name of the pref that's been changed (relative to aSubject) + if (aData == USER_DISABLE_PREF || aData == SYSTEM_DISABLE_PREF) { + // eslint-disable-next-line promise/catch-or-return + appStartupPromise.then(handleStartup); + } + } +}; + +const appStartupObserver = { + register: function() { + Services.obs.addObserver(this, "sessionstore-windows-restored", false); + }, + + unregister: function() { + Services.obs.removeObserver(this, "sessionstore-windows-restored", false); + }, + + observe: function() { + appStartupDone(); + this.unregister(); + } +} + +const APP_STARTUP = 1; +function startup(data, reason) { // eslint-disable-line no-unused-vars + if (reason === APP_STARTUP) { + appStartupObserver.register(); + } else { + appStartupDone(); + } + prefObserver.register(); + addonResourceURI = data.resourceURI; + // eslint-disable-next-line promise/catch-or-return + appStartupPromise.then(handleStartup); +} + +function shutdown(data, reason) { // eslint-disable-line no-unused-vars + prefObserver.unregister(); +} + +function install(data, reason) {} // eslint-disable-line no-unused-vars + +function uninstall(data, reason) {} // eslint-disable-line no-unused-vars + +function getBoolPref(pref) { + return prefs.getPrefType(pref) && prefs.getBoolPref(pref); +} + +function shouldDisable() { + return getBoolPref(USER_DISABLE_PREF) || getBoolPref(SYSTEM_DISABLE_PREF); +} + +function handleStartup() { + const webExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor({ + id: ADDON_ID, + resourceURI: addonResourceURI + }); + + if (!shouldDisable() && !webExtension.started) { + start(webExtension); + } else if (shouldDisable()) { + stop(webExtension); + } +} + +function start(webExtension) { + webExtension.startup().then((api) => { + api.browser.runtime.onMessage.addListener(handleMessage); + }).catch((err) => { + // The startup() promise will be rejected if the webExtension was + // already started (a harmless error), or if initializing the + // WebExtension failed and threw (an important error). + console.error(err); + if (err.message !== "This embedded extension has already been started") { + // TODO: Should we send these errors to Sentry? #2420 + } + }); +} + +function stop(webExtension) { + webExtension.shutdown(); +} + +function handleMessage(msg, sender, sendReply) { + if (!msg) { + return; + } + + if (msg.funcName === "getTelemetryPref") { + let telemetryEnabled = getBoolPref(TELEMETRY_ENABLED_PREF); + sendReply({type: "success", value: telemetryEnabled}); + } else if (msg.funcName === "getOldDeviceInfo") { + let oldDeviceInfo = prefs.prefHasUserValue(OLD_ADDON_PREF_NAME) && prefs.getCharPref(OLD_ADDON_PREF_NAME); + sendReply({type: "success", value: oldDeviceInfo || null}); + } else if (msg.funcName === "removeOldAddon") { + AddonManager.getAddonByID(OLD_ADDON_ID, (addon) => { + prefs.clearUserPref(OLD_ADDON_PREF_NAME); + if (addon) { + addon.uninstall(); + } + sendReply({type: "success", value: !!addon}); + }); + return true; + } +} diff --git a/browser/extensions/screenshots/install.rdf b/browser/extensions/screenshots/install.rdf new file mode 100644 index 000000000000..b3684b747200 --- /dev/null +++ b/browser/extensions/screenshots/install.rdf @@ -0,0 +1,20 @@ + + + + screenshots@mozilla.org + Firefox Screenshots + + + {ec8030f7-c20a-464f-9b0e-13a3a9e97384} + 51.0a1 + * + + + 2 + 6.3.0 + true + https://pageshot.net/ + true + + diff --git a/browser/extensions/screenshots/moz.build b/browser/extensions/screenshots/moz.build index 69c7b0f82d52..c7427b9c5ac3 100644 --- a/browser/extensions/screenshots/moz.build +++ b/browser/extensions/screenshots/moz.build @@ -13,9 +13,12 @@ FINAL_TARGET_FILES.features['screenshots@mozilla.org'] += [ # AUTOMATIC INSERTION START FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"] += [ 'webextension/assertIsTrusted.js', + 'webextension/blank.html', + 'webextension/buildSettings.js.template', 'webextension/catcher.js', 'webextension/clipboard.js', 'webextension/domainFromUrl.js', + 'webextension/log.js', 'webextension/makeUuid.js', 'webextension/manifest.json', 'webextension/randomString.js', @@ -250,7 +253,7 @@ FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["backgrou ] FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["build"] += [ - 'webextension/build/defaultSentryDsn.js', + 'webextension/build/buildSettings.js', 'webextension/build/inlineSelectionCss.js', 'webextension/build/onboardingCss.js', 'webextension/build/onboardingHtml.js', diff --git a/browser/extensions/screenshots/test/browser/browser.ini b/browser/extensions/screenshots/test/browser/browser.ini new file mode 100644 index 000000000000..6971b17b2e4a --- /dev/null +++ b/browser/extensions/screenshots/test/browser/browser.ini @@ -0,0 +1,5 @@ +[DEFAULT] +support-files = + head.js + +[browser_screenshots_ui_check.js] diff --git a/browser/extensions/screenshots/test/browser/browser_screenshots_ui_check.js b/browser/extensions/screenshots/test/browser/browser_screenshots_ui_check.js new file mode 100644 index 000000000000..06944d12a0a2 --- /dev/null +++ b/browser/extensions/screenshots/test/browser/browser_screenshots_ui_check.js @@ -0,0 +1,20 @@ +"use strict"; + +/* global add_task, is, promiseScreenshotsEnabled, promiseScreenshotsReset, + registerCleanupFunction */ + +function checkElements(expectPresent, l) { + for (let id of l) { + is(!!document.getElementById(id), expectPresent, "element " + id + (expectPresent ? " is" : " is not") + " present"); + } +} + +add_task(function*() { + yield promiseScreenshotsEnabled(); + + registerCleanupFunction(function* () { + yield promiseScreenshotsReset(); + }); + + checkElements(true, ["screenshots_mozilla_org-browser-action"]); +}); diff --git a/browser/extensions/screenshots/test/browser/head.js b/browser/extensions/screenshots/test/browser/head.js new file mode 100644 index 000000000000..4b041cee9635 --- /dev/null +++ b/browser/extensions/screenshots/test/browser/head.js @@ -0,0 +1,61 @@ +/* global CustomizableUI, info, Services */ + +// Currently Screenshots is disabled in tests. We want these tests to work under +// either case that Screenshots is disabled or enabled on startup of the browser, +// and that at the end we're reset to the correct state. +let enabledOnStartup = false; + +// ScreenshotsEnabled/Disabled promises return true if it was already +// Enabled/Disabled, and false if it need to Enable/Disable. +function promiseScreenshotsEnabled() { + if (!Services.prefs.getBoolPref("extensions.screenshots.system-disabled", false)) { + info("Screenshots was already enabled, assuming enabled by default for tests"); + enabledOnStartup = true; + return Promise.resolve(true); + } + info("Screenshots is not enabled"); + return new Promise((resolve, reject) => { + let listener = { + onWidgetAfterCreation(widgetid) { + if (widgetid == "screenshots_mozilla_org-browser-action") { + info("screenshots_mozilla_org-browser-action button created"); + CustomizableUI.removeListener(listener); + resolve(false); + } + } + } + CustomizableUI.addListener(listener); + info("Set Screenshots disabled pref to false."); + Services.prefs.setBoolPref("extensions.screenshots.system-disabled", false); + }); +} + +function promiseScreenshotsDisabled() { + if (Services.prefs.getBoolPref("extensions.screenshots.system-disabled", false)) { + info("Screenshots already disabled"); + return Promise.resolve(true); + } + return new Promise((resolve, reject) => { + let listener = { + onWidgetDestroyed(widgetid) { + if (widgetid == "screenshots_mozilla_org-browser-action") { + CustomizableUI.removeListener(listener); + info("screenshots_mozilla_org-browser-action destroyed"); + resolve(false); + } + } + } + CustomizableUI.addListener(listener); + info("Set Screenshots disabled pref to true."); + Services.prefs.setBoolPref("extensions.screenshots.system-disabled", true); + }); +} + +function promiseScreenshotsReset() { // eslint-disable-line no-unused-vars + if (enabledOnStartup) { + info("Reset is enabling Screenshots addon"); + return promiseScreenshotsEnabled(); + } + info("Reset is disabling Screenshots addon"); + return promiseScreenshotsDisabled(); +} diff --git a/browser/extensions/screenshots/webextension/_locales/ach/messages.json b/browser/extensions/screenshots/webextension/_locales/ach/messages.json new file mode 100644 index 000000000000..6431e7ad939c --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/ach/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Mak vidio ma ki ngolo macego cego ki cal me wang kio ki i Kakube ka igwok gi pi tutunu onyo matwal." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Mak cal me wang kio" + }, + "myShotsLink": { + "message": "Cal Na" + }, + "screenshotInstructions": { + "message": "Ywar onyo dii ii potbuk me yero bute. Dii ESC me juko." + }, + "saveScreenshotSelectedArea": { + "message": "Gwoki" + }, + "saveScreenshotVisibleArea": { + "message": "Gwok ma nen" + }, + "saveScreenshotFullPage": { + "message": "Gwok potbuk weng" + }, + "cancelScreenshot": { + "message": "Juki" + }, + "downloadScreenshot": { + "message": "Gam" + }, + "notificationLinkCopiedTitle": { + "message": "Ki loko kakube" + }, + "notificationLinkCopiedDetails": { + "message": "Ki loko kakube me cal mamegi i bao me coc. Dii $META_KEY$-V me mwono ne.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Pe tye katic." + }, + "requestErrorDetails": { + "message": "Timwa kica! Pe onongo wa twero gwoko cal mamegi. Tim ber item doki lacen." + }, + "connectionErrorTitle": { + "message": "Pe watwero kube ki cal me wang kio mamegi." + }, + "connectionErrorDetails": { + "message": "Tim ber i rot kakube ni me intanet. Kace itwero kube i intanet, peko mo pi tutuno romo bedo tye i tic me Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Pe onongo wa twero gwoko cal mamegi pien peko mo tye i tic me Firefox Screenshots. Tim ber item doki lacen." + }, + "unshootablePageErrorTitle": { + "message": "Pe watwero mako cal me wang kio me potbuk man." + }, + "unshootablePageErrorDetails": { + "message": "Man pe obedo Kakube me rwom, pi meno pe watwero mako cal me wang kio ne." + }, + "selfScreenshotErrorTitle": { + "message": "Pe itwero mako cal me potbuk pa Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "Woo! Firefox Screenshots opo oo." + }, + "genericErrorDetails": { + "message": "Pe wa ngeyo ngo ma otime kombedi. Iromo temo ne doki onyo mako cal pa potbuk mukene?" + }, + "tourBodyOne": { + "message": "Maki, gwoki, ki nywak cal me wang kio labongo weko Firefox." + }, + "tourHeaderTwo": { + "message": "Mak ngo ma imito keken" + }, + "tourBodyTwo": { + "message": "Dii ka i ywar me mako cal pa but potbuk keken. Itwero bene wot iwiye me wero yer mamegi." + }, + "tourHeaderThree": { + "message": "Kit ma imito" + }, + "tourBodyThree": { + "message": "Gwok cal mamegi ma ki ngolo ii Kakube pi nywako i yoo ma yot, onyo gamo gi i kompiuta ni. Itwero bene diyo mapeca me Cal Na me nongo cal ma i mako weng." + }, + "tourHeaderFour": { + "message": "Mak dirica onyo Potbuk weng" + }, + "tourBodyFour": { + "message": "Yer mapeca ma i tung lacuc malo me mako kabedo ma nen i dirica onyo me mako potbuk weng." + }, + "tourSkip": { + "message": "Kal" + }, + "tourNext": { + "message": "Cal malubo" + }, + "tourPrevious": { + "message": "Cal mukato" + }, + "tourDone": { + "message": "Otum" + }, + "termsAndPrivacyNotice": { + "message": "Tic ki Firefox Screenshots nyuto ni, i yee $TERMSANDPRIVACYNOTICETERMSLINK$ ki $TERMSANDPRIVACYNOTICEPRIVACYLINK$ me Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Cik" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Ngec me mung" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/be/messages.json b/browser/extensions/screenshots/webextension/_locales/be/messages.json new file mode 100644 index 000000000000..4c74a9d5b5d5 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/be/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Рабіце кліпы і здымкі экрана ў Сеціве і захоўвайце іх часова або назаўжды." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Зрабіць здымак экрана" + }, + "myShotsLink": { + "message": "Мае здымкі" + }, + "screenshotInstructions": { + "message": "Пацягніце або пстрыкніце на старонцы для выбару вобласці. Для адмены націсніце ESC." + }, + "saveScreenshotSelectedArea": { + "message": "Захаваць" + }, + "saveScreenshotVisibleArea": { + "message": "Захаваць бачную вобласць" + }, + "saveScreenshotFullPage": { + "message": "Захаваць усю старонку" + }, + "cancelScreenshot": { + "message": "Скасаваць" + }, + "downloadScreenshot": { + "message": "Сцягнуць" + }, + "notificationLinkCopiedTitle": { + "message": "Спасылка скапіявана" + }, + "notificationLinkCopiedDetails": { + "message": "Спасылка на ваш здымак была скапіявана ў буфер абмену. Націсніце $META_KEY$-V для ўстаўкі.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Адбылася памылка." + }, + "requestErrorDetails": { + "message": "Выбачайце! Нам не ўдалося захаваць ваш здымак. Паспрабуйце пазней." + }, + "connectionErrorTitle": { + "message": "Мы не можам атрымаць доступ да вашых скрыншотаў." + }, + "connectionErrorDetails": { + "message": "Калі ласка, праверце ваша злучэнне з Інтэрнэтам. Калі ў вас усё ў парадку з падлучэннем да Інтэрнэту, магчыма, паўсталі часовыя праблемы са службай Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Нам не ўдалося захаваць ваш здымак, таму што ўзніклі праблемы са службай Firefox Screenshots. Паспрабуйце пазней." + }, + "unshootablePageErrorTitle": { + "message": "Мы не можам зрабіць скрыншот гэтай старонкі." + }, + "unshootablePageErrorDetails": { + "message": "Гэта не стандартная вэб-старонка, таму вы не можаце зрабіць яе скрыншот." + }, + "selfScreenshotErrorTitle": { + "message": "Вы не можаце зрабіць здымак старонкі Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "Вой! З Firefox Screenshots нешта не так." + }, + "genericErrorDetails": { + "message": "Мы не ўпэўненыя, у чым праблема. Паспрабаваць яшчэ раз, ці зрабіць здымак іншай старонкі?" + }, + "tourBodyOne": { + "message": "Рабіце здымкі экрана, захоўвайце і дзяліцеся імі не выходзячы з Firefox." + }, + "tourHeaderTwo": { + "message": "Рабіце скрыншоты чаго заўгодна" + }, + "tourBodyTwo": { + "message": "Пстрыкніце і пацягніце мышшу для захопу часткі старонкі. Вы таксама можаце навесці курсор мышы для падсвятлення абранай вобласці." + }, + "tourHeaderThree": { + "message": "Як вам падабаецца" + }, + "tourBodyThree": { + "message": "Захоўваеце свае здымкі ў Інтэрнэце, каб лёгка імі дзяліцца, або загружайце іх на свой кампутар. Вы таксама можаце прагледзець усе захаваныя здымкі, націснуўшы на кнопку Мае здымкі." + }, + "tourHeaderFour": { + "message": "Рабіце захоп вокнаў або цэлых старонак" + }, + "tourBodyFour": { + "message": "З дапамогай кнопак у верхнім правым куце выбірайце захоп бачнай вобласці акна або старонкі цалкам." + }, + "tourSkip": { + "message": "Прапусьціць" + }, + "tourNext": { + "message": "Наступны слайд" + }, + "tourPrevious": { + "message": "Папярэдні слайд" + }, + "tourDone": { + "message": "Гатова" + }, + "termsAndPrivacyNotice": { + "message": "Выкарыстоўваючы Firefox Screenshots, вы згаджаецеся з яго $TERMSANDPRIVACYNOTICETERMSLINK$ і $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Умовамі выкарыстання" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Паведамленнем аб прыватнасці" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/bg/messages.json b/browser/extensions/screenshots/webextension/_locales/bg/messages.json new file mode 100644 index 000000000000..b4c99998c9bc --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/bg/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Прави клипове и снимки на уебстраница и ги запазва временно или за постоянно." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Снимка на екрана" + }, + "myShotsLink": { + "message": "Моите снимки" + }, + "screenshotInstructions": { + "message": "За да изберете участък влачете или щракнете с мишката в страницата. Натиснете ESC за отказ." + }, + "saveScreenshotSelectedArea": { + "message": "Запазване" + }, + "saveScreenshotVisibleArea": { + "message": "Запазване на видимата област" + }, + "saveScreenshotFullPage": { + "message": "Запазване на цялата страница" + }, + "cancelScreenshot": { + "message": "Отказ" + }, + "downloadScreenshot": { + "message": "Изтегляне" + }, + "notificationLinkCopiedTitle": { + "message": "Препратката е копирана" + }, + "notificationLinkCopiedDetails": { + "message": "Препратка към снимката е копирана в системния буфер. За да я поставите натиснете $META_KEY$-V.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Повреда." + }, + "requestErrorDetails": { + "message": "Съжаляваме! Снимката не е запазена. Опитайте по-късно." + }, + "connectionErrorTitle": { + "message": "Няма връзка с вашите снимки." + }, + "connectionErrorDetails": { + "message": "Моля, проверете своята връзка към интернет. Ако имате връзка с Мрежата, в такъв случай може да има временен проблем с услугата на Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Снимката не може да бъде запазена, защото има проблем с услугата на Firefox Screenshots. Опитайте по-късно." + }, + "unshootablePageErrorTitle": { + "message": "Снимка на тази страница не може да бъде направена." + }, + "unshootablePageErrorDetails": { + "message": "Това не е обикновена уебстраница и за това снимка не може да ѝ бъде направена." + }, + "selfScreenshotErrorTitle": { + "message": "Не може да правите снимки на страницата на Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "Леле! Нещо се обърка с Firefox Screenshots." + }, + "genericErrorDetails": { + "message": "Не сме сигурни какво точно се случи. Може да опитате отново, както и да снимате друга страница." + }, + "tourBodyOne": { + "message": "Правете, запазвайте и споделяйте снимки на екрана без да напускате Firefox." + }, + "tourHeaderTwo": { + "message": "Уловете само нужното" + }, + "tourBodyTwo": { + "message": "Щракнете с мишката или влачете, за да уловите части от страницата. Посочвайки елементите на страницата те се осветяват." + }, + "tourHeaderThree": { + "message": "Както ви харесва" + }, + "tourBodyThree": { + "message": "Запазете снимките на страници от Мрежата за по-лесно споделяне или ги изтеглете на компютъра си. А бутонът „Моите снимки“ ще ви покаже всички направени от вас снимки." + }, + "tourHeaderFour": { + "message": "Улавяйте прозорци и цели страници" + }, + "tourBodyFour": { + "message": "Използвайте бутоните в горния десен ъгъл, за да уловите само видимата част или цялата страница." + }, + "tourSkip": { + "message": "Прескачане" + }, + "tourNext": { + "message": "Напред" + }, + "tourPrevious": { + "message": "Назад" + }, + "tourDone": { + "message": "Готово" + }, + "termsAndPrivacyNotice": { + "message": "Използвайки Firefox Screenshots вие се съгласявате с тези $TERMSANDPRIVACYNOTICETERMSLINK$ и $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Условия" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Политика на поверителност" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/bn_BD/messages.json b/browser/extensions/screenshots/webextension/_locales/bn_BD/messages.json new file mode 100644 index 000000000000..58c5bb0592c1 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/bn_BD/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "ওয়েব থেকে ক্লিপ এবং স্ক্রিনশট নিন এবং সেগুলো সাময়িকভাবে বা স্থায়ীভাবে সংরক্ষণ করুন।" + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "একটি স্ক্রীনশট নিন" + }, + "myShotsLink": { + "message": "আমার সটসমূহ" + }, + "screenshotInstructions": { + "message": "ড্রাগ করে অথবা পেজে ক্লিক করে একটি অংশ নির্বাচন করুন। বাতিল করতে ESC টিপুন।" + }, + "saveScreenshotSelectedArea": { + "message": "সংরক্ষণ" + }, + "saveScreenshotVisibleArea": { + "message": "যতটুকু দেখা যাচ্ছে সংরক্ষণ করুন" + }, + "saveScreenshotFullPage": { + "message": "সম্পূর্ণ পেজ সংরক্ষণ করুন" + }, + "cancelScreenshot": { + "message": "বাতিল" + }, + "downloadScreenshot": { + "message": "ডাউনলোড" + }, + "notificationLinkCopiedTitle": { + "message": "লিঙ্ক কপি করা হয়েছে" + }, + "notificationLinkCopiedDetails": { + "message": "আপার সট এর লিংক ক্লিপবোর্ডে কপি করা হয়েছে। পেস্ট করতে $META_KEY$-V চাপুন।", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "বিকল।" + }, + "requestErrorDetails": { + "message": "দুঃখিত! আমরা আপনার সট সংরক্ষণ করতে পারিনি। অনুগ্রহ পুনরায় চেষ্টা করুন।" + }, + "connectionErrorTitle": { + "message": "আমরা আপনার স্ক্রিটসটসমূহ সংযোগ করতে পারছি না।" + }, + "connectionErrorDetails": { + "message": "অনুগ্রহ করে আপনার ইন্টারনেট সংযোগ পরীক্ষা করুন। আর যদি আপনার ইন্টারনেট সংযোগ ঠিক থাকে, তাহলে Firefox স্ক্রিনশট সেবাটিতে সাময়িক সমস্যা দেখা দিয়েছে।" + }, + "loginErrorDetails": { + "message": "আমরা আপনার শট সংরক্ষণ করতে পারিনি কারণ সেখানে Firefox স্ক্রিণশট সেবার সমস্যা আছে। অনুগ্রহ করে আবার চেস্টা করুন।" + }, + "unshootablePageErrorTitle": { + "message": "আমার এই পেজের স্ক্রিনশট নিতে পারব না।" + }, + "unshootablePageErrorDetails": { + "message": "এটা কোন আদর্শ ওয়েব পেজ না, তাই আপনি এটার স্ক্রিনশট তুলতে পারবেন না।" + }, + "selfScreenshotErrorTitle": { + "message": "আপনি Firefox স্ক্রিনশটের পেজের শট নিতে পারেন না!" + }, + "genericErrorTitle": { + "message": "আয় হায়! Firefox স্ক্রিনশট পাগল হয়ে গেছে।" + }, + "genericErrorDetails": { + "message": "এই মাত্র কি ঘটেছে আমরা নিশ্চিত নই। আপনি কি অনুগ্রহ করে পুরনায় সট নেবেন কিংবা ভিন্ন একটি পেজে চেষ্টা করবেন?" + }, + "tourBodyOne": { + "message": "Firefox ত্যাগ করা ছাড়াই স্ক্রিনশট তোল, সংরক্ষণ কর এবং শেয়ার কর।" + }, + "tourHeaderTwo": { + "message": "ক্যাপচার করুন আপনি যা চান" + }, + "tourBodyTwo": { + "message": "একটি পেজের কিয়দংশ ক্যাপচার করতে ক্লিক করে ড্রাগ করুন। অতঃপর আপনি মাউজ হোভার করে আপনার নির্বাচিত অংশ হাইলাইট করতে পারবেন।" + }, + "tourHeaderThree": { + "message": "আপনি যেমন পছন্দ করেন" + }, + "tourBodyThree": { + "message": "আপনার ক্রপ করা সটসমূহ ওয়েবে রাখুন সহজে শেয়ার করার সুবিধার্থে, অথবা আপনার কম্পিউটারে ডাউনলোড করুন। আপনার সকল সটসমূহ খুঁজে পেতে আমার সটসমূহ বাটনে ক্লিক করুন।" + }, + "tourHeaderFour": { + "message": "উইন্ডো ক্যাপচার করুন অথবা পুরো পেজ" + }, + "tourBodyFour": { + "message": "ইউন্ডোতে দৃশ্যমান অংশ অথবা সম্পূর্ণ পেজ ক্যাপচার করতে উপরে ডানদিকের বাটনগুলো থেকে নির্বাচন করুন।" + }, + "tourSkip": { + "message": "এড়িয়ে যান" + }, + "tourNext": { + "message": "পরবর্তী স্লাইড" + }, + "tourPrevious": { + "message": "পূর্ববর্তী স্লাইড" + }, + "tourDone": { + "message": "সম্পন্ন" + }, + "termsAndPrivacyNotice": { + "message": "Firefox Screenshots ব্যবহারের জন্য, আপনি স্ক্রিনশটের $TERMSANDPRIVACYNOTICETERMSLINK$ এবং $TERMSANDPRIVACYNOTICEPRIVACYLINK$ নীতিতে আগ্রহী।", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "শর্তাবলী" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "গোপনীয়তা নীতি" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/cs/messages.json b/browser/extensions/screenshots/webextension/_locales/cs/messages.json new file mode 100644 index 000000000000..75fd4e46d5ec --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/cs/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Pořizujte snímky webových stránek a ukládejte je dočasně nebo natrvalo." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Pořídit snímek obrazovky" + }, + "myShotsLink": { + "message": "Mé snímky" + }, + "screenshotInstructions": { + "message": "Stiskněte tlačítko myši a tahem označte oblast snímku. Pro zrušení výběru stiskněte klávesu ESC." + }, + "saveScreenshotSelectedArea": { + "message": "Uložit" + }, + "saveScreenshotVisibleArea": { + "message": "Uložit viditelnou oblast" + }, + "saveScreenshotFullPage": { + "message": "Uložit celou stránku" + }, + "cancelScreenshot": { + "message": "Zrušit" + }, + "downloadScreenshot": { + "message": "Stáhnout" + }, + "notificationLinkCopiedTitle": { + "message": "Odkaz zkopírován" + }, + "notificationLinkCopiedDetails": { + "message": "Odkaz na váš snímek byl zkopírován do schránky. Pro vložení stiskněte $META_KEY$-V.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Mimo provoz." + }, + "requestErrorDetails": { + "message": "Je nám líto, ale nemohli jsme vás snímek uložit. Zkuste to prosím znovu později." + }, + "connectionErrorTitle": { + "message": "Nedaří se nám připojit k vašim snímkům." + }, + "connectionErrorDetails": { + "message": "Zkontrolujte prosím připojení k internetu. Pokud vám připojení funguje, mohlo dojít k dočasnému problému s naší službou Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Nemohli jsme uložit váš snímek, protože došlo k problému se službou Firefox Screenshots. Zkuste to prosím znovu později." + }, + "unshootablePageErrorTitle": { + "message": "Snímek této stránky nelze pořídit." + }, + "unshootablePageErrorDetails": { + "message": "Toto není běžná webová stránka, a proto z ní nelze pořizovat žádné snímky." + }, + "selfScreenshotErrorTitle": { + "message": "Nelze pořizovat snímek stránky Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "Jejda! Služba Firefox Screenshots přestala pracovat." + }, + "genericErrorDetails": { + "message": "Nejsme si jistí, co se právě stalo. Chcete to zkusit znovu, nebo zkusíte pořídit snímek na jiné stránce?" + }, + "tourBodyOne": { + "message": "Pořizujte, ukládejte a sdílejte snímky webových stránek bez opuštění Firefoxu." + }, + "tourHeaderTwo": { + "message": "Zachyťte, cokoliv chcete" + }, + "tourBodyTwo": { + "message": "Stiskem tlačítka myši a tahem můžete vybrat oblast stránky. Výběr můžete provést také najetím myši na prvek stránky." + }, + "tourHeaderThree": { + "message": "Jak sami chcete" + }, + "tourBodyThree": { + "message": "Uložte si oříznutý snímek stránky na web pro rychlejší sdílení, nebo si ho stáhněte do počítače. Pro zobrazení všech snímků stačí klepnout na tlačítko Mé snímky." + }, + "tourHeaderFour": { + "message": "Pořizujte snímky jen částí nebo i celých stránek" + }, + "tourBodyFour": { + "message": "Pomocí tlačítek vpravo nahoře můžete pořídit snímek jen viditelné části nebo úplně celé stránky." + }, + "tourSkip": { + "message": "Přeskočit" + }, + "tourNext": { + "message": "Další snímek" + }, + "tourPrevious": { + "message": "Předchozí snímek" + }, + "tourDone": { + "message": "Hotovo" + }, + "termsAndPrivacyNotice": { + "message": "Používáním služby Firefox Screenshots souhlasíte s jejími $TERMSANDPRIVACYNOTICETERMSLINK$ a $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "podmínkami" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "zásadami ochrany osobních údajů" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/de/messages.json b/browser/extensions/screenshots/webextension/_locales/de/messages.json new file mode 100644 index 000000000000..1fb8b0b4ef25 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/de/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Speichern Sie Ausschnitte und Bildschirmfotos von Webseiten, die Sie temporär oder dauerhaft speichern können." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Bildschirmfoto aufnehmen" + }, + "myShotsLink": { + "message": "Meine Bildschirmfotos" + }, + "screenshotInstructions": { + "message": "Ziehen oder Klicken Sie auf der Seite, um einen Bereich auszuwählen. Drücken Sie ESC zum Abbrechen." + }, + "saveScreenshotSelectedArea": { + "message": "Speichern" + }, + "saveScreenshotVisibleArea": { + "message": "Sichtbaren Bereich speichern" + }, + "saveScreenshotFullPage": { + "message": "Gesamte Seite speichern" + }, + "cancelScreenshot": { + "message": "Abbrechen" + }, + "downloadScreenshot": { + "message": "Herunterladen" + }, + "notificationLinkCopiedTitle": { + "message": "Link kopiert" + }, + "notificationLinkCopiedDetails": { + "message": "Der Link zu Ihrem Bildschirmfoto wurde in die Zwischenablage kopiert. Drücken Sie $META_KEY$-V zum Einfügen.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Außer Betrieb." + }, + "requestErrorDetails": { + "message": "Wir konnten Ihr Bildschirmfoto leider nicht speichern. Bitte versuchen Sie es später erneut." + }, + "connectionErrorTitle": { + "message": "Es war keine Verbindung zu Ihren Bildschirmfotos möglich." + }, + "connectionErrorDetails": { + "message": "Bitte überprüfen Sie Ihre Internetverbindung. Wenn diese funktioniert, gibt es eventuell ein temporäres Problem mit dem Dienst von Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Ihr Bildschirmfoto konnte nicht gespeichert werden, weil ein Problem mit dem Dienst Firefox Screenshots aufgetreten ist. Bitte versuchen Sie es später erneut." + }, + "unshootablePageErrorTitle": { + "message": "Ein Bildschirmfoto dieser Seite ist nicht möglich." + }, + "unshootablePageErrorDetails": { + "message": "Dies ist keine Standard-Webseite, daher sind keine Bildschirmfotos von ihr möglich." + }, + "selfScreenshotErrorTitle": { + "message": "Sie können kein Bildschirmfoto einer Firefox-Screenshots-Seite machen!" + }, + "genericErrorTitle": { + "message": "Firefox Screenshots funktioniert nicht richtig." + }, + "genericErrorDetails": { + "message": "Wir wissen auch nicht, was gerade passiert ist. Könnten Sie das Bildschirmfoto erneut oder auf einer anderen Seite aufnehmen?" + }, + "tourBodyOne": { + "message": "Bildschirmfotos aufnehmen, speichern und teilen, ohne Firefox zu verlassen." + }, + "tourHeaderTwo": { + "message": "Nehmen Sie auf, was Sie möchten" + }, + "tourBodyTwo": { + "message": "Klicken und ziehen Sie, um nur einen Teil einer Seite aufzunehmen. Sie können den Mauszeiger auch darüber bewegen, um Ihre Auswahl hervorzuheben." + }, + "tourHeaderThree": { + "message": "Wie Sie möchten" + }, + "tourBodyThree": { + "message": "Speichern Sie Ihre zugeschnittenen Bildschirmfotos im Internet, sodass sie leicht zu teilen sind, oder laden Sie sie auf Ihren Computer herunter. Sie können auch auf die Schaltfläche „Meine Bildschirmfotos“ klicken, um alle Ihre Bildschirmfotos zu finden." + }, + "tourHeaderFour": { + "message": "Fenster oder ganze Seiten speichern" + }, + "tourBodyFour": { + "message": "Nutzen Sie die Schaltflächen rechts oben, um den sichtbaren Bereich im Fenster oder eine ganze Seite zu speichern." + }, + "tourSkip": { + "message": "Überspringen" + }, + "tourNext": { + "message": "Nächste Folie" + }, + "tourPrevious": { + "message": "Vorherige Folie" + }, + "tourDone": { + "message": "Fertig" + }, + "termsAndPrivacyNotice": { + "message": "Durch die Verwendung von Firefox Screenshots stimmen Sie den entsprechenden $TERMSANDPRIVACYNOTICETERMSLINK$ und dem $TERMSANDPRIVACYNOTICEPRIVACYLINK$ zu.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Nutzungsbedingungen" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Datenschutzhinweis" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/dsb/messages.json b/browser/extensions/screenshots/webextension/_locales/dsb/messages.json new file mode 100644 index 000000000000..d11e8d33779a --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/dsb/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Wzejśo klipy a fota wobrazowki z weba a składujśo je nachylu abo na pśecej." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Foto wobrazowki gótowaś" + }, + "myShotsLink": { + "message": "Móje fota wobrazowki" + }, + "screenshotInstructions": { + "message": "Śěgniśo abo klikniśo na bok, aby wobcerk wubrał. Tłocćo na ESC, aby pśetergnuł." + }, + "saveScreenshotSelectedArea": { + "message": "Składowaś" + }, + "saveScreenshotVisibleArea": { + "message": "Widobny wobcerk składowaś" + }, + "saveScreenshotFullPage": { + "message": "Ceły bok składowaś" + }, + "cancelScreenshot": { + "message": "Pśetergnuś" + }, + "downloadScreenshot": { + "message": "Ześěgnuś" + }, + "notificationLinkCopiedTitle": { + "message": "Wótkaz kopěrowany" + }, + "notificationLinkCopiedDetails": { + "message": "Wótkaz k wašomu fotoju wobrazowki jo se do mjazywótkłada kopěrował. Tłocćo $META_KEY$-V, aby jen zasajźił.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Njeźěła." + }, + "requestErrorDetails": { + "message": "Bóžko njejsmy mógli wašo foto wobrazowki składowaś. Pšosym wopytajśo pózdźej hyšći raz." + }, + "connectionErrorTitle": { + "message": "Njamóžomy z wašymi fotami wobrazowki zwězaś." + }, + "connectionErrorDetails": { + "message": "Pšosym pśekontrolěrujśo swój internetny zwisk. Jolic móžośo z internetom zwězaś, dajo snaź nachylny problem ze słužbu Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Njesjmy mógli swójo foto wobrazowki składowaś, dokulaž dajo problem ze słužbu Firefox Screenshots. Pšosym wopytajśo pózdźej hyšći raz." + }, + "unshootablePageErrorTitle": { + "message": "Foto wobrazowki toś togo boka njejo móžne." + }, + "unshootablePageErrorDetails": { + "message": "To njejo standardny webbok, togodla foto wobrazowki wót njeje njejo móžne." + }, + "selfScreenshotErrorTitle": { + "message": "Njamóžośo wobrazowku boka Firefox Screenshots fotografěrowaś!" + }, + "genericErrorTitle": { + "message": "Hopla! Firefox Screenshots njeźěła." + }, + "genericErrorDetails": { + "message": "Njejsmy se wěste, což jo se stało. Cośo hyšći raz wopytaś abo cośo drugi bok fotografěrowaś?" + }, + "tourBodyOne": { + "message": "Gótujśo, składujśo a źělśo fota wobrazowki mimo až Firefox spušćaśo." + }, + "tourHeaderTwo": { + "message": "Fotografěrujśo jadnorje, což cośo" + }, + "tourBodyTwo": { + "message": "Klikniśo a ześěgniśo, aby źěl boka fotografěrował. Móžośo teke špěrku myški nad nim gibaś, aby swój wuběr wuzwignuł." + }, + "tourHeaderThree": { + "message": "Tak, kaž se wam spódoba" + }, + "tourBodyThree": { + "message": "Składujśo swóje pśirězane fota wobrazowki w interneśe, aby je lažcej źělił, abo ześěgniśo je na swójo licadło. Móžośo teke na tłocašk „Móje fota wobrazowki“ kliknuś, abye wšě fota wobrazowki namakał, kótarež sćo gótował." + }, + "tourHeaderFour": { + "message": "Wokna abo cełe boki składowaś" + }, + "tourBodyFour": { + "message": "Wubjeŕśo tłocašk górjejce napšawo, aby widobny wobcerk we woknje abo ceły bok fotografěrowaś." + }, + "tourSkip": { + "message": "Pśeskócyś" + }, + "tourNext": { + "message": "Pśiduce foto" + }, + "tourPrevious": { + "message": "Pjerwjejšne foto" + }, + "tourDone": { + "message": "Gótowo" + }, + "termsAndPrivacyNotice": { + "message": "Pśez wužywanje Firefox ScreenShots, zwolijośo do $TERMSANDPRIVACYNOTICETERMSLINK$ a $TERMSANDPRIVACYNOTICEPRIVACYLINK$ Firefox Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Wuměnjenja" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Powěźeńka priwatnosći" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/el/messages.json b/browser/extensions/screenshots/webextension/_locales/el/messages.json new file mode 100644 index 000000000000..fa5f855f9033 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/el/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Πραγματοποιήστε λήψη στιγμιοτύπων από το Διαδίκτυο και αποθηκεύστε τα προσωρινά ή μόνιμα." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Λήψη στιγμιότυπου" + }, + "myShotsLink": { + "message": "Οι λήψεις μου" + }, + "screenshotInstructions": { + "message": "Σύρετε ή κάντε κλικ στη σελίδα για να επιλέξετε μια περιοχή. Για ακύρωση πιέστε το πλήκτρο ESC." + }, + "saveScreenshotSelectedArea": { + "message": "Αποθήκευση" + }, + "saveScreenshotVisibleArea": { + "message": "Αποθήκευση ορατής περιοχής" + }, + "saveScreenshotFullPage": { + "message": "Αποθήκευση ολόκληρης σελίδας" + }, + "cancelScreenshot": { + "message": "Ακύρωση" + }, + "downloadScreenshot": { + "message": "Λήψη" + }, + "notificationLinkCopiedTitle": { + "message": "Αντιγραφή Συνδέσμου" + }, + "notificationLinkCopiedDetails": { + "message": "Ο σύνδεσμος προς την λήψη σας αντιγράφηκε στο πρόχειρο. Πατήστε $META_KEY$-V για επικόλληση.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Εκτός λειτουργίας." + }, + "requestErrorDetails": { + "message": "Συγνώμη! Δεν μπορέσαμε να αποθηκεύουμε την λήψη σας. Προσπαθήστε ξανά αργότερα." + }, + "connectionErrorTitle": { + "message": "Δεν μπορούμε να συνδεθούμε στις λήψεις σας." + }, + "connectionErrorDetails": { + "message": "Ελέγξτε τη σύνδεσή σας στο Internet. Εάν είστε σε θέση να συνδεθείτε στο Internet, ίσως υπάρχει ένα προσωρινό πρόβλημα με την υπηρεσία Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Δεν μπορέσαμε να αποθηκεύσουμε την λήψη σας γιατί υπάρχει κάποιο πρόβλημα με την υπηρεσία Firefox Screenshots. Προσπαθήστε ξανά αργότερα." + }, + "unshootablePageErrorTitle": { + "message": "Δεν μπορούμε να λάβουμε στιγμιότυπο αυτής της σελίδας." + }, + "unshootablePageErrorDetails": { + "message": "Δεν μπορεί να γίνει λήψη στιγμιότυπου καθώς αυτή δεν είναι μια τυπική σελίδα του Διαδικτύου." + }, + "selfScreenshotErrorTitle": { + "message": "Δεν μπορεί να γίνει λήψη ενός στιγμιότυπου της σελίδας Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "Ωχ! Κάτι πήγε στραβά στην υπηρεσία Firefox Screenshots." + }, + "genericErrorDetails": { + "message": "Δεν είμαστε σίγουροι για το τι ακριβώς συνέβη. Προσπαθήστε ξανά ή κάντε λήψη σε μια άλλη σελίδα." + }, + "tourBodyOne": { + "message": "Λήψη, αποθήκευση και διαμοιρασμός στιγμιοτύπων μέσα από το Firefox." + }, + "tourHeaderTwo": { + "message": "Καταγράψτε αυτό που Εσείς Επιθυμείτε" + }, + "tourBodyTwo": { + "message": "Κάντε κλικ και σύρετε για την καταγραφή ενός τμήματος της σελίδας. Μπορείτε να επισημάνετε την επιλογή σας μετακινώντας τον ποντίκι σας επάνω της." + }, + "tourHeaderThree": { + "message": "Ακριβώς όπως το θέλετε" + }, + "tourBodyThree": { + "message": "Αποθηκεύστε της λήψεις σας στο Διαδίκτυο για ευκολότερο διαμοιρασμό, η λήψη τους στον υπολογιστή σας. Μπορείτε να βρείτε όλες τις λήψεις σας πατώντας στο κουμπί «Οι λήψεις μου»." + }, + "tourHeaderFour": { + "message": "Καταγράψτε Παράθυρα ή Ολόκληρες Σελίδες" + }, + "tourBodyFour": { + "message": "Επιλέξτε τα κουμπιά επάνω δεξιά για να καταγράψετε την ορατή περιοχή του παραθύρου ή να καταγράψετε μια ολόκληρη σελίδα." + }, + "tourSkip": { + "message": "Παράβλεψη" + }, + "tourNext": { + "message": "Επόμενη διαφάνεια" + }, + "tourPrevious": { + "message": "Προηγούμενη διαφάνεια" + }, + "tourDone": { + "message": "Τέλος" + }, + "termsAndPrivacyNotice": { + "message": "Χρησιμοποιώντας το Firefox Screenshots, συμφωνείτε με τους $TERMSANDPRIVACYNOTICETERMSLINK$ Στιγμιότυπων και τη $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Όρους" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Σημείωση απορρήτου" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/en_GB/messages.json b/browser/extensions/screenshots/webextension/_locales/en_GB/messages.json new file mode 100644 index 000000000000..b99a0a965bff --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/en_GB/messages.json @@ -0,0 +1,106 @@ +{ + "addonDescription": { + "message": "Take clips and screenshots from the Web and save them temporarily or permanently." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Take a Screenshot" + }, + "myShotsLink": { + "message": "My Shots" + }, + "screenshotInstructions": { + "message": "Drag or click on the page to select a region. Press ESC to cancel." + }, + "saveScreenshotSelectedArea": { + "message": "Save" + }, + "saveScreenshotVisibleArea": { + "message": "Save visible" + }, + "saveScreenshotFullPage": { + "message": "Save full page" + }, + "cancelScreenshot": { + "message": "Cancel" + }, + "downloadScreenshot": { + "message": "Download" + }, + "notificationLinkCopiedTitle": { + "message": "Link Copied" + }, + "notificationLinkCopiedDetails": { + "message": "The link to your shot has been copied to the clipboard. Press $META_KEY$-V to paste.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Out of order." + }, + "requestErrorDetails": { + "message": "Sorry! We couldn’t save your shot. Please try again later." + }, + "connectionErrorTitle": { + "message": "We can’t connect to your screenshots." + }, + "connectionErrorDetails": { + "message": "Please check your Internet connection. If you are able to connect to the Internet, there may be a temporary problem with the Firefox Screenshots service." + }, + "loginErrorDetails": { + "message": "We couldn’t save your shot because there is a problem with the Firefox Screenshots service. Please try again later." + }, + "unshootablePageErrorTitle": { + "message": "We can’t screenshot this page." + }, + "unshootablePageErrorDetails": { + "message": "This isn’t a standard Web page, so you can’t take a screenshot of it." + }, + "selfScreenshotErrorTitle": { + "message": "You can’t take a shot of a Firefox Screenshots page!" + }, + "genericErrorTitle": { + "message": "Whoa! Firefox Screenshots went haywire." + }, + "genericErrorDetails": { + "message": "We’re not sure what just happened. Care to try again or take a shot of a different page?" + }, + "tourBodyOne": { + "message": "Take, save, and share screenshots without leaving Firefox." + }, + "tourHeaderTwo": { + "message": "Capture Just What You Want" + }, + "tourBodyTwo": { + "message": "Click and drag to capture just a portion of a page. You can also hover to highlight your selection." + }, + "tourHeaderThree": { + "message": "As You Like it" + }, + "tourBodyThree": { + "message": "Save your cropped shots to the Web for easier sharing, or download them to your computer. You also can click on the My Shots button to find all the shots you’ve taken." + }, + "tourHeaderFour": { + "message": "Capture Windows or Entire Pages" + }, + "tourBodyFour": { + "message": "Select the buttons in the upper right to capture the visible area in the window or to capture an entire page." + }, + "tourSkip": { + "message": "SKIP" + }, + "tourNext": { + "message": "Next Slide" + }, + "tourPrevious": { + "message": "Previous Slide" + }, + "tourDone": { + "message": "Done" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/en_US/messages.json b/browser/extensions/screenshots/webextension/_locales/en_US/messages.json new file mode 100644 index 000000000000..3fa9e8639cd2 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/en_US/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Take clips and screenshots from the Web and save them temporarily or permanently." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Take a Screenshot" + }, + "myShotsLink": { + "message": "My Shots" + }, + "screenshotInstructions": { + "message": "Drag or click on the page to select a region. Press ESC to cancel." + }, + "saveScreenshotSelectedArea": { + "message": "Save" + }, + "saveScreenshotVisibleArea": { + "message": "Save visible" + }, + "saveScreenshotFullPage": { + "message": "Save full page" + }, + "cancelScreenshot": { + "message": "Cancel" + }, + "downloadScreenshot": { + "message": "Download" + }, + "notificationLinkCopiedTitle": { + "message": "Link Copied" + }, + "notificationLinkCopiedDetails": { + "message": "The link to your shot has been copied to the clipboard. Press $META_KEY$-V to paste.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Out of order." + }, + "requestErrorDetails": { + "message": "Sorry! We couldn’t save your shot. Please try again later." + }, + "connectionErrorTitle": { + "message": "We can’t connect to your screenshots." + }, + "connectionErrorDetails": { + "message": "Please check your Internet connection. If you are able to connect to the Internet, there may be a temporary problem with the Firefox Screenshots service." + }, + "loginErrorDetails": { + "message": "We couldn’t save your shot because there is a problem with the Firefox Screenshots service. Please try again later." + }, + "unshootablePageErrorTitle": { + "message": "We can’t screenshot this page." + }, + "unshootablePageErrorDetails": { + "message": "This isn’t a standard Web page, so you can’t take a screenshot of it." + }, + "selfScreenshotErrorTitle": { + "message": "You can’t take a shot of a Firefox Screenshots page!" + }, + "genericErrorTitle": { + "message": "Whoa! Firefox Screenshots went haywire." + }, + "genericErrorDetails": { + "message": "We’re not sure what just happened. Care to try again or take a shot of a different page?" + }, + "tourBodyOne": { + "message": "Take, save, and share screenshots without leaving Firefox." + }, + "tourHeaderTwo": { + "message": "Capture Just What You Want" + }, + "tourBodyTwo": { + "message": "Click and drag to capture just a portion of a page. You can also hover to highlight your selection." + }, + "tourHeaderThree": { + "message": "As You Like it" + }, + "tourBodyThree": { + "message": "Save your cropped shots to the Web for easier sharing, or download them to your computer. You also can click on the My Shots button to find all the shots you’ve taken." + }, + "tourHeaderFour": { + "message": "Capture Windows or Entire Pages" + }, + "tourBodyFour": { + "message": "Select the buttons in the upper right to capture the visible area in the window or to capture an entire page." + }, + "tourSkip": { + "message": "SKIP" + }, + "tourNext": { + "message": "Next Slide" + }, + "tourPrevious": { + "message": "Previous Slide" + }, + "tourDone": { + "message": "Done" + }, + "termsAndPrivacyNotice": { + "message": "By using Firefox Screenshots, you agree to the Screenshots $TERMSANDPRIVACYNOTICETERMSLINK$ and $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Terms" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Privacy Notice" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/es_AR/messages.json b/browser/extensions/screenshots/webextension/_locales/es_AR/messages.json new file mode 100644 index 000000000000..47f4e4db90ca --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/es_AR/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Tomá imágenes y capturas de la web y guardalos temporal o permanentemente." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Hacer captura de pantalla" + }, + "myShotsLink": { + "message": "Mis capturas" + }, + "screenshotInstructions": { + "message": "Arrastrá o hacé clic en la página para seleccionar una región. Presioná ESC para cancelar." + }, + "saveScreenshotSelectedArea": { + "message": "Guardar" + }, + "saveScreenshotVisibleArea": { + "message": "Guardar visible" + }, + "saveScreenshotFullPage": { + "message": "Guardar página completa" + }, + "cancelScreenshot": { + "message": "Cancelar" + }, + "downloadScreenshot": { + "message": "Descargar" + }, + "notificationLinkCopiedTitle": { + "message": "Enlace copiado" + }, + "notificationLinkCopiedDetails": { + "message": "EL enlace a la captura ha sido copiado al portapapeles. Presioná $META_KEY$-V para pegar.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "No funciona." + }, + "requestErrorDetails": { + "message": "¡Perdón! No pudimos guardar la captura. Intentá más tarde." + }, + "connectionErrorTitle": { + "message": "No podemos conectar a las capturas de pantalla." + }, + "connectionErrorDetails": { + "message": "Verificá la conexión a Internet. Si te podés conectar a Internet, hay un problema temporal con el servicio de capturas de Firefox." + }, + "loginErrorDetails": { + "message": "No pudimos guardar la captura porque hay un problema con el servicio de capturas de Firefox. Intentá más tarde." + }, + "unshootablePageErrorTitle": { + "message": "No podemos capturar esta página." + }, + "unshootablePageErrorDetails": { + "message": "Esta no es una página web estándar, así que no podemos guardar una captura." + }, + "selfScreenshotErrorTitle": { + "message": "¡No se puede hacer una captura de la página de capturas de Firefox!" + }, + "genericErrorTitle": { + "message": "¡Apa! La capturas de pantalla de Firefox se volvieron locas." + }, + "genericErrorDetails": { + "message": "No estamos seguros de lo que pasó. ¿Querés intenar de nuevo o tomar una captura de una página diferente?" + }, + "tourBodyOne": { + "message": "Hacer, guardar y compartir capturas de pantalla sin dejar Firefox." + }, + "tourHeaderTwo": { + "message": "Capturar sólo lo que querés" + }, + "tourBodyTwo": { + "message": "Hacé clic y arrastrá para capturar una porción de la página. También podés pasar por encima para resaltar la selección." + }, + "tourHeaderThree": { + "message": "Como te guste" + }, + "tourBodyThree": { + "message": "Guardá tus capturas recortadas a la web para compartir o descargarlas más fácilmente a tu computadora. También podés hacer clic en el botón Mis capturas para encontrar todas las capturas hechas." + }, + "tourHeaderFour": { + "message": "Capturar ventanas o páginas enteras" + }, + "tourBodyFour": { + "message": "Seleccioná los botones arriba a la derecha para capturar el área visible en la ventana o la página completa." + }, + "tourSkip": { + "message": "Saltear" + }, + "tourNext": { + "message": "Próxima diapositiva" + }, + "tourPrevious": { + "message": "Diapositiva anterior" + }, + "tourDone": { + "message": "Listo" + }, + "termsAndPrivacyNotice": { + "message": "Al usar Firefox Screenshots, aceptás los $TERMSANDPRIVACYNOTICETERMSLINK$ y $TERMSANDPRIVACYNOTICEPRIVACYLINK$ de Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Términos" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Nota de privacidad" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/es_CL/messages.json b/browser/extensions/screenshots/webextension/_locales/es_CL/messages.json new file mode 100644 index 000000000000..0acc9cadf75c --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/es_CL/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Toma capturas de un sitio Web para guardarlas de forma temporal o permanentemente." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Toma una captura de pantalla" + }, + "myShotsLink": { + "message": "Mis capturas" + }, + "screenshotInstructions": { + "message": "Arrastra o haz clic en la página para seleccionar una región. Presiona ESC para cancelar." + }, + "saveScreenshotSelectedArea": { + "message": "Guardar" + }, + "saveScreenshotVisibleArea": { + "message": "Guardar lo visible" + }, + "saveScreenshotFullPage": { + "message": "Guardar la página completa" + }, + "cancelScreenshot": { + "message": "Cancelar" + }, + "downloadScreenshot": { + "message": "Descargar" + }, + "notificationLinkCopiedTitle": { + "message": "Enlace copiado" + }, + "notificationLinkCopiedDetails": { + "message": "El enlace a tu captura ha sido copiado al portapapeles. Presiona $META_KEY$-V para pegarla.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Fuera de orden." + }, + "requestErrorDetails": { + "message": "¡Lo sentimos! No pudimos guardar tu captura. Por favor, vuelve a intentarlo más tarde." + }, + "connectionErrorTitle": { + "message": "No podemos conectar a tus capturas." + }, + "connectionErrorDetails": { + "message": "Por favor, revisa tu conexión a Internet. Si eres capaz de conectarte a Internet, puede que haya un problema temporal con el servicio de Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "No pudimos guardar tu captura porque hay un problema con el servicio de Firefox Screenshots. Por favor, vuelve a intentarlo más tarde." + }, + "unshootablePageErrorTitle": { + "message": "No podemos capturar esta página." + }, + "unshootablePageErrorDetails": { + "message": "Esta no es una página Web estándar, por lo que no puedes tomar una captura de ella." + }, + "selfScreenshotErrorTitle": { + "message": "¡No puedes tomar una captura de una página de Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "¡Guau! Firefox Screenshots se copetió." + }, + "genericErrorDetails": { + "message": "No estamos seguros de lo que sucedió. ¿Te importaría volver a intentarlo o tomar una captura de una página diferente?" + }, + "tourBodyOne": { + "message": "Toma, guarda y comparte capturas sin salir de Firefox." + }, + "tourHeaderTwo": { + "message": "Captura lo que necesitas" + }, + "tourBodyTwo": { + "message": "Haz clic y arrastra para captura justo una parte de la página. También puedes colocarte sobre una parte para destacar tu selección." + }, + "tourHeaderThree": { + "message": "Como tu quieras" + }, + "tourBodyThree": { + "message": "Guarda tus capturas recortadas en la Web para compartirlas fácilmente o descargarlas a tu computador. También puedes hacer clic en el botón Mis capturas para encontrar todas las que hayas tomado." + }, + "tourHeaderFour": { + "message": "Captura ventanas o páginas completas" + }, + "tourBodyFour": { + "message": "Selecciona los botones en la parte superior derecha para capturar el área visible ne la ventana o para capturar una página completa." + }, + "tourSkip": { + "message": "SALTAR" + }, + "tourNext": { + "message": "Siguiente diapositiva" + }, + "tourPrevious": { + "message": "Diapositiva anterior" + }, + "tourDone": { + "message": "Hecho" + }, + "termsAndPrivacyNotice": { + "message": "Al usar Firefox Screenshots, aceptas los $TERMSANDPRIVACYNOTICETERMSLINK$ y el $TERMSANDPRIVACYNOTICEPRIVACYLINK$ de Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Términos" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Aviso de privacidad" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/es_ES/messages.json b/browser/extensions/screenshots/webextension/_locales/es_ES/messages.json new file mode 100644 index 000000000000..552483a8280f --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/es_ES/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Haz capturas y recortes de la web y guárdalos temporal o permanentemente." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Hacer una captura de pantalla" + }, + "myShotsLink": { + "message": "Mis capturas" + }, + "screenshotInstructions": { + "message": "Arrastra o haz clic en la página para seleccionar una región. Pulsa ESC para cancelar." + }, + "saveScreenshotSelectedArea": { + "message": "Guardar" + }, + "saveScreenshotVisibleArea": { + "message": "Guardar visible" + }, + "saveScreenshotFullPage": { + "message": "Guardar página completa" + }, + "cancelScreenshot": { + "message": "Cancelar" + }, + "downloadScreenshot": { + "message": "Descargar" + }, + "notificationLinkCopiedTitle": { + "message": "Enlace copiado" + }, + "notificationLinkCopiedDetails": { + "message": "Se ha copiado el enlace a la captura en el portapapeles. Pulsa $META_KEY$-V para pegar.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "No funciona." + }, + "requestErrorDetails": { + "message": "¡Lo sentimos! No hemos podido guardar tu captura. Inténtalo más tarde." + }, + "connectionErrorTitle": { + "message": "No podemos acceder a tus capturas de pantalla." + }, + "connectionErrorDetails": { + "message": "Comprueba tu conexión a Internet. Si puedes conectarte, puede que haya un problema temporal con el servicio de capturas de pantalla de Firefox." + }, + "loginErrorDetails": { + "message": "No se pudo guardar la captura porque hay un problema con el servicio de capturas de pantalla de Firefox. Inténtalo más tarde." + }, + "unshootablePageErrorTitle": { + "message": "No podemos hacer una captura de esta página." + }, + "unshootablePageErrorDetails": { + "message": "No es una página web común, por lo que no podemos hacer captura de pantalla." + }, + "selfScreenshotErrorTitle": { + "message": "¡No puedes hacer una captura de la página de capturas de Firefox!" + }, + "genericErrorTitle": { + "message": "¡Vaya! La página de capturas de pantalla de Firefox se ha vuelto loca." + }, + "genericErrorDetails": { + "message": "No estamos seguros de lo que acaba de pasar. ¿Te importa volver a intentarlo o hacer una captura de otra página?" + }, + "tourBodyOne": { + "message": "Hacer, guardar y compartir capturas de pantalla sin salir de Firefox." + }, + "tourHeaderTwo": { + "message": "Haz capturas solo de lo que tú quieras" + }, + "tourBodyTwo": { + "message": "Haz clic y arrastra para capturar solo una parte de la página. También puedes pasar por encima para resaltar tu selección." + }, + "tourHeaderThree": { + "message": "Como más te guste" + }, + "tourBodyThree": { + "message": "Guarda las capturas de la Web recortadas para compartirlas mejor o descárgalas en tu ordenador. También puedes hacer clic en Mis capturas para ver todas las capturas que has hecho." + }, + "tourHeaderFour": { + "message": "Haz capturas de Windows o páginas completas" + }, + "tourBodyFour": { + "message": "Selecciona los botones de la parte superior derecha para capturar el área visible en Windows o la página completa." + }, + "tourSkip": { + "message": "Saltar" + }, + "tourNext": { + "message": "Diapositiva siguiente" + }, + "tourPrevious": { + "message": "Diapositiva anterior" + }, + "tourDone": { + "message": "Hecho" + }, + "termsAndPrivacyNotice": { + "message": "Al usar Firefox Screenshots, aceptas los $TERMSANDPRIVACYNOTICETERMSLINK$ y el $TERMSANDPRIVACYNOTICEPRIVACYLINK$ de Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Términos" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Aviso de privacidad" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/es_MX/messages.json b/browser/extensions/screenshots/webextension/_locales/es_MX/messages.json new file mode 100644 index 000000000000..3a1671577ab9 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/es_MX/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Tomar clips y capturas de pantalla de la web y guardarlos temporalmente o permanentemente." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Tomar captura de pantalla" + }, + "myShotsLink": { + "message": "Mis capturas" + }, + "screenshotInstructions": { + "message": "Arrastra o haz clic en la página para seleccionar la región. Presiona ESC para cancelar." + }, + "saveScreenshotSelectedArea": { + "message": "Guardar" + }, + "saveScreenshotVisibleArea": { + "message": "Guardar visible" + }, + "saveScreenshotFullPage": { + "message": "Guardar página completa" + }, + "cancelScreenshot": { + "message": "Cancelar" + }, + "downloadScreenshot": { + "message": "Descarga" + }, + "notificationLinkCopiedTitle": { + "message": "Enlace copiado" + }, + "notificationLinkCopiedDetails": { + "message": "El enlace que has capturado ha sido copiado al portapapeles. Presiona $META_KEY$-V para pegar.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Fuera de orden." + }, + "requestErrorDetails": { + "message": "¡Lo sentimos! No pudimos guardar tu captura. Por favor, intenta de nuevo más tarde." + }, + "connectionErrorTitle": { + "message": "No podemos conectar a tus capturas de pantalla." + }, + "connectionErrorDetails": { + "message": "Por favor, revisa tu conexión a Internet. Si eres capaz de conectarte a Internet, puede ser que exista un error temporal con el servicio de capturas de pantalla de Firefox." + }, + "loginErrorDetails": { + "message": "No pudimos guardar tu captura porque hay un problema con el servicio de capturas de pantalla de Firefox. Por favor, intenta de nuevo más tarde." + }, + "unshootablePageErrorTitle": { + "message": "No podemos realizar una captura de pantalla a esta página." + }, + "unshootablePageErrorDetails": { + "message": "Esta no es una página web estándar, por lo tanto no podemos tomar una captura de pantalla de ella." + }, + "selfScreenshotErrorTitle": { + "message": "¡No puedes tomar una captura de la página de capturas de pantalla de Firefox!" + }, + "genericErrorTitle": { + "message": "¡Oye! Las capturas de pantalla de Firefox salieron mal." + }, + "genericErrorDetails": { + "message": "No estamos seguros qué pasó. ¿Te importaría intentarlo de nuevo o tomar una captura de una página diferente?" + }, + "tourBodyOne": { + "message": "Toma, guarda y comparte capturas de pantalla sin dejar Firefox." + }, + "tourHeaderTwo": { + "message": "Captura sólo lo que necesitas" + }, + "tourBodyTwo": { + "message": "Haz clic y arrastra para capturas sólo una parte de la página. También puedes desplazarte para resaltar tu selección." + }, + "tourHeaderThree": { + "message": "Como te gusta" + }, + "tourBodyThree": { + "message": "Guarda tus capturas recortadas en la Web para compartirlas más fácilmente o descárgalas en tu computadora. También puedes hacer clic en el botón Mis Capturas para encontrar todas las fotos que has tomado." + }, + "tourHeaderFour": { + "message": "Captura ventanas o páginas enteras" + }, + "tourBodyFour": { + "message": "Selecciona los botones en la parte superior derecha para capturar el área visible en la ventana o para capturar una página completa." + }, + "tourSkip": { + "message": "Ignorar" + }, + "tourNext": { + "message": "Siguiente diapositiva" + }, + "tourPrevious": { + "message": "Diapositiva anterior" + }, + "tourDone": { + "message": "Terminado" + }, + "termsAndPrivacyNotice": { + "message": "Al usar Firefox Screenshots, estás de acuerdo con los $TERMSANDPRIVACYNOTICETERMSLINK$ y con el $TERMSANDPRIVACYNOTICETERMSLINK$ de Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Términos" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Aviso de privacidad" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/et/messages.json b/browser/extensions/screenshots/webextension/_locales/et/messages.json new file mode 100644 index 000000000000..6b7793d53aa4 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/et/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Tee veebist klippe või ekraanipilte ning salvesta need ajutiselt või püsivalt." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Tee ekraanipilt" + }, + "myShotsLink": { + "message": "Minu pildid" + }, + "screenshotInstructions": { + "message": "Ala valimiseks klõpsavõi lohista lehel. Tühistamiseks vajuta ESC." + }, + "saveScreenshotSelectedArea": { + "message": "Salvesta" + }, + "saveScreenshotVisibleArea": { + "message": "Salvesta nähtav" + }, + "saveScreenshotFullPage": { + "message": "Salvesta terve leht" + }, + "cancelScreenshot": { + "message": "Tühista" + }, + "downloadScreenshot": { + "message": "Laadi alla" + }, + "notificationLinkCopiedTitle": { + "message": "Link kopeeriti" + }, + "notificationLinkCopiedDetails": { + "message": "Link sinu pildile kopeeriti lõikepuhvrisse. Asetamiseks vajuta $META_KEY$-V.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Tekkis viga." + }, + "requestErrorDetails": { + "message": "Vabandame! Me ei suutnud su pilti salvestada. Palun proovi hiljem uuesti." + }, + "connectionErrorTitle": { + "message": "Ühendumine sinu ekraanipiltidega ei õnnestunud." + }, + "connectionErrorDetails": { + "message": "Palun kontrolli internetiühenduse toimimist. Kui saad internetiga ühendust, siis võib tegemist olla Firefox Screenshots teenuse ajutise probleemiga." + }, + "loginErrorDetails": { + "message": "Ekraanipildi salvestamine ebaõnnestus Firefox Screenshots teenuse probleemi tõttu. Palun proovi hiljem uuesti." + }, + "unshootablePageErrorTitle": { + "message": "Sellest lehest ei saa ekraanipilti teha." + }, + "unshootablePageErrorDetails": { + "message": "Tegemist pole standardse veebilehega, seetõttu ei saa sellest ekraanipilti teha." + }, + "selfScreenshotErrorTitle": { + "message": "Firefox Screenshots lehest ei saa ekraanipilti teha!" + }, + "genericErrorTitle": { + "message": "Oi-oi! Firefox Screenshots läks sassi." + }, + "genericErrorDetails": { + "message": "Me pole kindlad, mis just juhtus. Proovid ehk uuesti või teed ekraanipildi mõnest teisest lehest?" + }, + "tourBodyOne": { + "message": "Tee, salvesta ja jaga ekraanipilte Firefoxist lahkumata." + }, + "tourHeaderTwo": { + "message": "Salvesta just seda, mida soovid" + }, + "tourBodyTwo": { + "message": "Klõpsa ja lohista lehe osa valimiseks. Samuti võid valiku esile toomiseks kursorit selle kohal hoida." + }, + "tourHeaderThree": { + "message": "Nii, kuidas sulle meeldib" + }, + "tourBodyThree": { + "message": "Salvesta kärbitud pilte lihtsamaks jagamiseks veebi või laadi need alla enda arvutisse. Võid ka klõpsata Minu pildid nupul kõigi tehtud piltide vaatamiseks." + }, + "tourHeaderFour": { + "message": "Salvesta aknaid või terveid lehti" + }, + "tourBodyFour": { + "message": "Kasuta nuppe ülal paremal aknas nähtava ala või terve lehe salvestamiseks." + }, + "tourSkip": { + "message": "Jäta vahele" + }, + "tourNext": { + "message": "Järgmine slaid" + }, + "tourPrevious": { + "message": "Eelmine slaid" + }, + "tourDone": { + "message": "Valmis" + }, + "termsAndPrivacyNotice": { + "message": "Firefox Screenshots kasutamisel nõustud Screenshots $TERMSANDPRIVACYNOTICETERMSLINK$ ja $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "kasutustingimuste" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "privaatsuspoliitikaga" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/fa/messages.json b/browser/extensions/screenshots/webextension/_locales/fa/messages.json new file mode 100644 index 000000000000..89f97ee4e72b --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/fa/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "از وب عکس بگیرید و کلیپ بسازید و به صورت موقت یا دایمی ذخیره کنید." + }, + "addonAuthorsList": { + "message": "موزیلا " + }, + "contextMenuLabel": { + "message": "از صفحه عکس بگیرید" + }, + "myShotsLink": { + "message": "عکس‌های من" + }, + "screenshotInstructions": { + "message": "با کشیدن یا کلیک کردن روی صفحه یک منطقه را انتخاب کنید. برای لغو، ESC را فشار دهید." + }, + "saveScreenshotSelectedArea": { + "message": "ذخیره" + }, + "saveScreenshotVisibleArea": { + "message": "ذخیره ناحیه قابل مشاهده" + }, + "saveScreenshotFullPage": { + "message": "ذخیره صفحه کامل" + }, + "cancelScreenshot": { + "message": "لغو" + }, + "downloadScreenshot": { + "message": "دریافت" + }, + "notificationLinkCopiedTitle": { + "message": "پیوند کپی شد" + }, + "notificationLinkCopiedDetails": { + "message": "لینک عکس شما در کلیپ‌بورد رونوشت شد. $META_KEY$-V را برای جای‌گذاری فشار دهید.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "خارج از سرویس." + }, + "requestErrorDetails": { + "message": "متاسفم! نتوانستیم عکس شما را ذخیره کنیم. لطفاً بعدا دوباره تلاش کنید." + }, + "connectionErrorTitle": { + "message": "نمی‌توانیم به تصاویر صفحه شما متصل شویم." + }, + "connectionErrorDetails": { + "message": "لطفا اتصال اینترنت خود را بررسی کنید. اگر قادر به اتصال به اینترنت هستید، ممکن است مشکلی موقتی در سرویس تصاویر صفحهٔ فایرفاکس وجود داشته باشد." + }, + "loginErrorDetails": { + "message": "به علت وجود مشکل در سرویس تصاویر صفحه فایرفاکس نتوانستیم عکس شما را ذخیره کنیم. لطفاً بعدا دوباره تلاش کنید." + }, + "unshootablePageErrorTitle": { + "message": "نمی‌توانیم از این صفحه تصویر بگیریم." + }, + "unshootablePageErrorDetails": { + "message": "این یک صفحه استاندارد وب نیست، بنابراین شما نمی‌توانید از آن تصویر بگیرید." + }, + "selfScreenshotErrorTitle": { + "message": "نمی‌توانید از صفحهٔ تصاویرِ فایرفاکس عکس بگیرید!" + }, + "genericErrorTitle": { + "message": "اوه! سرویس تصاویر صفحه فایرفاکس قاطی کرده." + }, + "genericErrorDetails": { + "message": "مطمئن نیستیم چه اتفاقی افتاده است. می‌خواهید دوباره امتحان کنید یا از یک صفحهٔ دیگر عکس بگیرید؟" + }, + "tourBodyOne": { + "message": "بدون خارج شدن از فایرفاکس، عکس بگیرید، ذخیره کنید و به اشتراک بگذارید." + }, + "tourHeaderTwo": { + "message": "ضبط آنچه شما می‌خواهید" + }, + "tourBodyTwo": { + "message": "کلیک کنید و بکشید تا فقط از قسمتی از صفحه عکس بگیرید. می‌توانید برای برجسته کردن روی ناحیه انتخاب شده حرکت کنید." + }, + "tourHeaderThree": { + "message": "همانطور که می‌پسندید" + }, + "tourBodyThree": { + "message": "عکس‌های بریده شده خود را برای به اشتراک‌گذاری راحت‌تر روی وب ذخیره کنید، یا آن‌ها را روی رایانه خود دریافت کنید. همچنین برای دیدن همهٔ عکس‌هایی که گرفتید می‌توانید روی دکمه «عکس‌های من» کلیک کنید." + }, + "tourHeaderFour": { + "message": "ضبط پنجره یا کل صفحه‌ها" + }, + "tourBodyFour": { + "message": "برای گرفتن عکس از ناحیه قابل مشاهده در پنجره یا تمام صفحه از دکمه‌های بالا سمت راست استفاده کنید." + }, + "tourSkip": { + "message": "رد کردن" + }, + "tourNext": { + "message": "اسلاید بعدی" + }, + "tourPrevious": { + "message": "اسلاید قبلی" + }, + "tourDone": { + "message": "انجام شد" + }, + "termsAndPrivacyNotice": { + "message": "با استفاده از سرویسِ تصاویرِ صفحهٔ فایرفاکس، شما با $TERMSANDPRIVACYNOTICETERMSLINK$ و $TERMSANDPRIVACYNOTICEPRIVACYLINK$ موافقت می‌کنید.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "شرایط" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "نکات حریم‌خصوصی" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/fr/messages.json b/browser/extensions/screenshots/webextension/_locales/fr/messages.json new file mode 100644 index 000000000000..e1a0893aae8d --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/fr/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Effectuez des captures d’écran sur le Web et sauvegardez-les de manière temporaire ou permanente." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Effectuer une capture d’écran" + }, + "myShotsLink": { + "message": "Mes captures d’écran" + }, + "screenshotInstructions": { + "message": "Sélectionnez une zone de la page par cliquer-glisser ou en cliquant sur l’élément à sélectionner. Appuyez sur Échap pour annuler." + }, + "saveScreenshotSelectedArea": { + "message": "Enregistrer" + }, + "saveScreenshotVisibleArea": { + "message": "Capturer la zone visible" + }, + "saveScreenshotFullPage": { + "message": "Capturer la page complète" + }, + "cancelScreenshot": { + "message": "Annuler" + }, + "downloadScreenshot": { + "message": "Télécharger" + }, + "notificationLinkCopiedTitle": { + "message": "Lien copié" + }, + "notificationLinkCopiedDetails": { + "message": "Le lien de votre capture a été copié dans le presse-papiers. Appuyez sur $META_KEY$-V pour le coller.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Impossible d’effectuer cette action." + }, + "requestErrorDetails": { + "message": "Votre capture d’écran n’a pas pu être enregistrée. Veuillez réessayer plus tard." + }, + "connectionErrorTitle": { + "message": "Nous ne pouvons pas nous connecter à vos captures d’écran." + }, + "connectionErrorDetails": { + "message": "Veuillez vérifier votre connexion à Internet. Si celle-ci fonctionne normalement, il peut y avoir un problème temporaire avec le service de Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Nous n’avons pas pu enregistrer votre capture d’écran, car le service de Firefox Screenshot rencontre des difficultés. Veuillez réessayer plus tard." + }, + "unshootablePageErrorTitle": { + "message": "Impossible d’effectuer une capture d’écran de cette page." + }, + "unshootablePageErrorDetails": { + "message": "Impossible d’effectuer une capture d’écran, car cette page web n’est pas standard." + }, + "selfScreenshotErrorTitle": { + "message": "Vous ne pouvez pas effectuer une capture d’écran d’une page Firefox Screenshots." + }, + "genericErrorTitle": { + "message": "Firefox Screenshots semble avoir un petit problème." + }, + "genericErrorDetails": { + "message": "Un problème non identifié est survenu. Vous pouvez réessayer ou effectuer une capture d’écran d’une autre page." + }, + "tourBodyOne": { + "message": "Effectuez des captures d’écran, enregistrez et partagez-les sans quitter Firefox." + }, + "tourHeaderTwo": { + "message": "Capturez ce que vous voulez" + }, + "tourBodyTwo": { + "message": "Cliquez et glissez pour capturer seulement une partie de la page. Vous pouvez aussi survoler une zone avec votre curseur pour surligner votre sélection." + }, + "tourHeaderThree": { + "message": "À votre guise" + }, + "tourBodyThree": { + "message": "Sauvegardez en ligne vos captures recadrées pour les partager plus facilement, ou téléchargez-les sur votre ordinateur. Vous pouvez aussi cliquer sur « Mes captures d’écran » pour retrouver toutes vos captures." + }, + "tourHeaderFour": { + "message": "Effectuez des captures d’écran de fenêtres ou de pages entières" + }, + "tourBodyFour": { + "message": "Utilisez les boutons en haut à droite pour capturer au choix la zone visible dans la fenêtre ou la page entière." + }, + "tourSkip": { + "message": "IGNORER" + }, + "tourNext": { + "message": "Écran suivant" + }, + "tourPrevious": { + "message": "Écran précédent" + }, + "tourDone": { + "message": "Terminé" + }, + "termsAndPrivacyNotice": { + "message": "En utilisant Firefox Screenshots, vous acceptez les $TERMSANDPRIVACYNOTICETERMSLINK$ et la $TERMSANDPRIVACYNOTICEPRIVACYLINK$ de Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "mentions légales" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "politique de confidentialité" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/fy_NL/messages.json b/browser/extensions/screenshots/webextension/_locales/fy_NL/messages.json new file mode 100644 index 000000000000..5e6394bb6d48 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/fy_NL/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Meitsje skermprintsjes of klips fan it web en bewarje se tydlik of permanint." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Meitsje in skermprintsje" + }, + "myShotsLink": { + "message": "Myn skermprintsjes" + }, + "screenshotInstructions": { + "message": "Sleep of klik op de side om in gebiet te selektearjen. Druk op ESC om te annulearjen." + }, + "saveScreenshotSelectedArea": { + "message": "Bewarje" + }, + "saveScreenshotVisibleArea": { + "message": "Sichtbere bewarje" + }, + "saveScreenshotFullPage": { + "message": "Folsleine side bewarje" + }, + "cancelScreenshot": { + "message": "Annulearje" + }, + "downloadScreenshot": { + "message": "Downloade" + }, + "notificationLinkCopiedTitle": { + "message": "Keppeling kopiearre" + }, + "notificationLinkCopiedDetails": { + "message": "De keppeling nei jo skermprintsje is nei it klamboerd kopiearre. Brûk $META_KEY$-V om te plakken.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Bûten tsjinst." + }, + "requestErrorDetails": { + "message": "Sorry! Wy koene jo skermprintsje net bewarje. Probearje it letter nochris." + }, + "connectionErrorTitle": { + "message": "Wy kinne net ferbine nei jo skermprintsjes." + }, + "connectionErrorDetails": { + "message": "Kontrolearje jo ynternetferbining. As jo wol ferbining meitsje kinne mei it ynternet, kin it wêze dat der tydlik in probleem is mei de tsjinst Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Wy koene jo skermprintsje net bewarje, omdat der in probleem is mei de tsjinst Firefox Screenshots. Probearje it letter nochris." + }, + "unshootablePageErrorTitle": { + "message": "It is net mooglik in skermprintsje fan dizze side te meitsjen." + }, + "unshootablePageErrorDetails": { + "message": "Dit is net in standert webside, dus jo kinne der net in skermprintsje fan meitsje." + }, + "selfScreenshotErrorTitle": { + "message": "Jo kinne net in skermprintsje meitsje fan in Firefox Screenshots-side!" + }, + "genericErrorTitle": { + "message": "Oeps! Firefox Screenshots is yn 'e war." + }, + "genericErrorDetails": { + "message": "Wy binne net wis wat der krekt bard is. Wolle jo it nochris probearje of in skermprintsje fan in oare side meitsje?" + }, + "tourBodyOne": { + "message": "Meitsje, bewarje en diel skermprintsjes sûnder Firefox te ferlitten." + }, + "tourHeaderTwo": { + "message": "Fetsje wat jo wolle" + }, + "tourBodyTwo": { + "message": "Klik en sleep om in part fan in side te fetsjen. Jo kinne ek oer in gebiet gean om jo seleksje út te ljochtsjen." + }, + "tourHeaderThree": { + "message": "Nei jo winsk" + }, + "tourBodyThree": { + "message": "Bewarje jo byknippe skermprintsjes nei it web om se maklik te dielen, of download se nei jo kompjûter. Jo kinne ek op de knop Myn skermprintsjes klikke om al jo makke skermprintsjes te finen." + }, + "tourHeaderFour": { + "message": "Fetsje finsters of folsleine websiden" + }, + "tourBodyFour": { + "message": "Selektearje knoppen rjochts boppe-oan om it sichtbere gebiet yn it finster te fetsjen, of fetsje in folsleine side." + }, + "tourSkip": { + "message": "Oerslaan" + }, + "tourNext": { + "message": "Folgjende ôfbylding" + }, + "tourPrevious": { + "message": "Foarige ôfbylding" + }, + "tourDone": { + "message": "Dien" + }, + "termsAndPrivacyNotice": { + "message": "Troch Firefox Screenshots te brûken, gean jo akkoard mei de $TERMSANDPRIVACYNOTICETERMSLINK$ en $TERMSANDPRIVACYNOTICEPRIVACYLINK$ fan Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Betingsten" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Privacyferklearring" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/gu_IN/messages.json b/browser/extensions/screenshots/webextension/_locales/gu_IN/messages.json new file mode 100644 index 000000000000..590dca9a6235 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/gu_IN/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "વેબમાંથી ક્લિપ્સ અને સ્ક્રીનશૉટ્સ લો અને તેમને કામચલાઉ અથવા કાયમી રીતે સાચવો." + }, + "addonAuthorsList": { + "message": "Mozilla" + }, + "contextMenuLabel": { + "message": "સ્ક્રીનશૉટ લેવા" + }, + "myShotsLink": { + "message": "મારા શોટ્સ" + }, + "screenshotInstructions": { + "message": "ખેંચો અથવા એક પ્રદેશ પસંદ કરવા માટે પાનાં પર ક્લિક કરો. રદ કરવા માટે ESC દબાવો." + }, + "saveScreenshotSelectedArea": { + "message": "સાચવો" + }, + "saveScreenshotVisibleArea": { + "message": "દૃશ્યમાન સાચવો" + }, + "saveScreenshotFullPage": { + "message": "સંપૂર્ણ પૃષ્ઠ સાચવો" + }, + "cancelScreenshot": { + "message": "રદ" + }, + "downloadScreenshot": { + "message": "ડાઉનલોડ" + }, + "notificationLinkCopiedTitle": { + "message": "લિંક કૉપિ" + }, + "notificationLinkCopiedDetails": { + "message": "તમારા શોટ માટે લિંક ક્લિપબોર્ડ પર કૉપિ કરવામાં આવ્યું છે. પ્રેસ $META_KEY$ -V પેસ્ટ કરવા માટે.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "હુકમ બહાર." + }, + "requestErrorDetails": { + "message": "માફ કરશો! અમે તમારા શોટ સાચવી શક્યા નથી. પછીથી ફરી પ્રયત્ન કરો." + }, + "connectionErrorTitle": { + "message": "અમે તમારા સ્ક્રીનશૉટ્સ ને કનેક્ટ થઈ શકતા નથી." + }, + "connectionErrorDetails": { + "message": "તમારું ઇન્ટરનેટ કનેક્શન તપાસો. તમે ઇન્ટરનેટથી કનેક્ટ કરવા માટે સક્ષમ છો, તો ત્યાં ફાયરફોક્સ સ્ક્રીનશોટ્સ સેવા સાથે એક અસ્થાયી સમસ્યા હોઈ શકે છે." + }, + "loginErrorDetails": { + "message": "અમે તમારા શોટ સાચવી શક્યા નથી કારણ કે Firefox સ્ક્રીનશોટ્સ સેવા સાથે એક સમસ્યા છે. પછીથી ફરી પ્રયત્ન કરો." + }, + "unshootablePageErrorTitle": { + "message": "અમે આ પૃષ્ઠ સ્ક્રીનશૉટ ન કરી શકીએ." + }, + "unshootablePageErrorDetails": { + "message": "આ એક પ્રમાણભૂત વેબ પૃષ્ઠ, જેથી તમે તેને એક સ્ક્રીનશૉટ ન લઈ શકો." + }, + "selfScreenshotErrorTitle": { + "message": "તમે પૃષ્ઠના Firefox સ્ક્રીનશોટ્સ શોટ લઇ શકો નહિ!" + }, + "genericErrorTitle": { + "message": "થોભો! Firefox સ્ક્રીનશોટ્સ અવ્યવસ્થિત થઈ ગયા." + }, + "genericErrorDetails": { + "message": "અમે ખાતરી નથીકે શું માત્ર થયું છે . ફરી પ્રયાસ કરો અથવા એક અલગ પૃષ્ઠ એક શોટ લેવા માટે કાળજી કરો?" + }, + "tourBodyOne": { + "message": "લેવા, સાચવેલા, અને વહેંચાયેલ સ્ક્રીનશૉટ્સ Firefox છોડ્યાં વિના." + }, + "tourHeaderTwo": { + "message": "કેદ કરો તમને જોઈએ તે" + }, + "tourBodyTwo": { + "message": "પાનાંના માત્ર એક ભાગ મેળવવા માટે ક્લિક કરો અને ખેંચો. તમે પણ તમારી પસંદગી પ્રકાશિત કરવા માટે હૉવર કરી શકો છો." + }, + "tourHeaderThree": { + "message": "તમને જે ગમે" + }, + "tourBodyThree": { + "message": "સરળ શેરિંગ માટે વેબ પર તમારા કપાઈ શોટ સાચવો, અથવા તેમને તમારા કમ્પ્યુટર પર ડાઉનલોડ કરો. તમે બધા શોટ મેળવવા માટે મારું શોટ્સ બટન પર ક્લિક કરી પણ શકો છો બધા શોટ તમે લીધેલા શોધવા માટે." + }, + "tourHeaderFour": { + "message": "વિન્ડોઝ અથવા સમગ્ર પાના કેદ કરો" + }, + "tourBodyFour": { + "message": "ઉપર જમણા બટનો પસંદ કરો વિન્ડોમાં દૃશ્યમાન વિસ્તાર મેળવવા માટે અથવા આખુ પાનું કેપ્ચર કરવા માટે." + }, + "tourSkip": { + "message": "છોડવા" + }, + "tourNext": { + "message": "આગલી સ્લાઇડ" + }, + "tourPrevious": { + "message": "પહેલાની સ્લાઇડ" + }, + "tourDone": { + "message": "થઈ ગયું" + }, + "termsAndPrivacyNotice": { + "message": "Firefox સ્ક્રીનશોટ્સ વાપરીને, તમે સ્ક્રીનશૉટ્સ થી સંમત છો $TERMSANDPRIVACYNOTICETERMSLINK$ અને $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "શરતો" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "ખાનગી સૂચના" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/he/messages.json b/browser/extensions/screenshots/webextension/_locales/he/messages.json new file mode 100644 index 000000000000..b519b7263d13 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/he/messages.json @@ -0,0 +1,94 @@ +{ + "addonDescription": { + "message": "יצירת צילומי מסך של דפי אינטרנט ושמירה שלהם באופן זמני או קבוע." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "צילום מסך" + }, + "myShotsLink": { + "message": "צילומי המסך שלי" + }, + "screenshotInstructions": { + "message": "נא לגרור או ללחוץ על הדף כדי לבחור תחום או על ESC לביטול." + }, + "saveScreenshotSelectedArea": { + "message": "שמירה" + }, + "saveScreenshotVisibleArea": { + "message": "שמירת התחום המוצג" + }, + "saveScreenshotFullPage": { + "message": "שמירת הדף במלואו" + }, + "cancelScreenshot": { + "message": "ביטול" + }, + "downloadScreenshot": { + "message": "הורדה" + }, + "notificationLinkCopiedTitle": { + "message": "הקישור הועתק" + }, + "notificationLinkCopiedDetails": { + "message": "הקישור לתמונה שלך הועתק ללוח. נא ללחוץ על $META_KEY$-V כדי להדביק.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "לא תקין." + }, + "requestErrorDetails": { + "message": "אנו מצטערים, אך לא ניתן היה לשמור את התמונה. נא לנסות שוב מאוחר יותר." + }, + "connectionErrorTitle": { + "message": "לא ניתן היה להתחבר אל מאגר צילומי המסך שלך." + }, + "connectionErrorDetails": { + "message": "נא לבדוק את החיבור לאינטרנט. אם הצלחת להתחבר לאינטרנט כנראה שקיימת תקלה זמנית עם שירות Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "אין אפשרות לשמור את צילום המסך שלך כיוון שישנה תקלה עם שירות Firefox Screenshots. נא לנסות שוב מאוחר יותר." + }, + "unshootablePageErrorTitle": { + "message": "לא ניתן לצלם דף זה." + }, + "unshootablePageErrorDetails": { + "message": "דף זה אינו דף אינטרנט תקני, ולכן לא ניתן היה לצלם אותו." + }, + "selfScreenshotErrorTitle": { + "message": "לא ניתן לצלם את הדף של Firefox Screenshot עצמו!" + }, + "genericErrorTitle": { + "message": "אויש! Firefox Screenshots ירד מהפסים." + }, + "genericErrorDetails": { + "message": "אנחנו לא בטוחים מה קרה פה הרגע. אכפת לך לנסות שוב או לצלם דף אחר?" + }, + "tourBodyOne": { + "message": "צילום, שמירה ושיתוף של צילומי מסך מבלי לעזוב את Firefox." + }, + "tourHeaderTwo": { + "message": "לצלם רק את מה שנחוץ לך" + }, + "tourBodyTwo": { + "message": "ניתן ללחוץ ולגרור כדי לצלם רק מקטע מהעמוד. ניתן גם לרחף מעל כדי לסמן את הבחירה שלך." + }, + "tourHeaderThree": { + "message": "לפי טעמך" + }, + "tourBodyThree": { + "message": "שמירת הצילומים החתוכים שלך לאחסון מקוון לצורך שיתוף פשוט יותר, או להוריד אותם למחשב שלך. ניתן גם ללחוץ על כפתור הצילומים שלי כדי למצוא את כל הצילומים שצילמת." + }, + "tourHeaderFour": { + "message": "לצלם חלונות או דפים שלמים" + }, + "tourBodyFour": { + "message": "נא לבחור בכפתורים שבחלק העליון כדי לצלם את האזור הגלוי בחלון או לצלם את הדף כולו." + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/hsb/messages.json b/browser/extensions/screenshots/webextension/_locales/hsb/messages.json new file mode 100644 index 000000000000..9f760c49e1cc --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/hsb/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Wzmiće klipy a fota wobrazowki z weba a składujće je nachwilu abo na přeco." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Foto wobrazowki činić" + }, + "myShotsLink": { + "message": "Moje fota wobrazowki" + }, + "screenshotInstructions": { + "message": "Ćehńće abo klikńće na stronu, zo byšće wobwod wubrał. Tłóčće na ESC, zo byšće přetorhnył." + }, + "saveScreenshotSelectedArea": { + "message": "Składować" + }, + "saveScreenshotVisibleArea": { + "message": "Widźomny wobwod składować" + }, + "saveScreenshotFullPage": { + "message": "Cyłu stronu składować" + }, + "cancelScreenshot": { + "message": "Přetorhnyć" + }, + "downloadScreenshot": { + "message": "Sćahnyć" + }, + "notificationLinkCopiedTitle": { + "message": "Wotkaz kopěrowany" + }, + "notificationLinkCopiedDetails": { + "message": "Wotkaz k wašemu fotu wobrazowki je so do mjezyskłada kopěrował. Tłóčće $META_KEY$-V, zo byšće jón zasadźił.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Skóncowany." + }, + "requestErrorDetails": { + "message": "Bohužel njemóžachmy waše foto wobrazowki składować. Prošu spytajće pozdźišo hišće raz." + }, + "connectionErrorTitle": { + "message": "Njemóžemy z wašimi fotami wobrazowki zwjazać." + }, + "connectionErrorDetails": { + "message": "Prošu přepruwujće swój internetny zwisk. Jeli móžeće z internetom zwjazać, je snano nachwilny problem ze słužbu Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Njemóžachmy swoje foto wobrazowki składować, dokelž je problem ze słužbu Firefox Screenshots. Prošu spytajće pozdźišo hišće raz." + }, + "unshootablePageErrorTitle": { + "message": "Foto wobrazowki tuteje strony móžne njeje." + }, + "unshootablePageErrorDetails": { + "message": "To standardna webstrona njeje, tohodla foto wobrazowki wot njeje móžne njeje." + }, + "selfScreenshotErrorTitle": { + "message": "Njemóžeće wobrazowku strony Firefox Screenshots fotografować!" + }, + "genericErrorTitle": { + "message": "Hopla! Firefox Screenshots njefunguje." + }, + "genericErrorDetails": { + "message": "Njejsmy sej wěsći, štož je so stało. Chceće hišće raz spytać abo chceće druhu stronu fotografować?" + }, + "tourBodyOne": { + "message": "Čińće, składujće a dźělće fota wobrazowki bjez toho, zo byšće Firefox wopušćił." + }, + "tourHeaderTwo": { + "message": "Fotografujće prosće, štož chceće" + }, + "tourBodyTwo": { + "message": "Klikńće a ćehńće, zo byšće dźěl strony fotografował. Móžeće tež pokazowak myški nad nim pohibować, zo byšće swój wuběr wuzběhnył." + }, + "tourHeaderThree": { + "message": "Tak, kaž so wam spodoba" + }, + "tourBodyThree": { + "message": "Składujće swoje přitřihane fota wobrazowki w interneće, zo byšće je lóšo dźělił, abo sćehńće je na swój ličak. Móžeće tež na tłóčatko „Moje fota wobrazowki“ kliknyć, zo byšće wšě fota wobrazowki namakał, kotrež sće činił." + }, + "tourHeaderFour": { + "message": "Wokna abo cyłe strony składować" + }, + "tourBodyFour": { + "message": "Wubjerće tłóčatka horjeka naprawo, zo byšće widźomny wobwod we woknje abo cyłu stronu fotografować." + }, + "tourSkip": { + "message": "Přeskočić" + }, + "tourNext": { + "message": "Přichodne foto" + }, + "tourPrevious": { + "message": "Předchadne foto" + }, + "tourDone": { + "message": "Hotowo" + }, + "termsAndPrivacyNotice": { + "message": "Přez wužiwanje Firefox ScreenShots, zwoliće do $TERMSANDPRIVACYNOTICETERMSLINK$ a $TERMSANDPRIVACYNOTICEPRIVACYLINK$ Firefox Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Wuměnjenja" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Pokaz priwatnosće" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/hu/messages.json b/browser/extensions/screenshots/webextension/_locales/hu/messages.json new file mode 100644 index 000000000000..d86cf5071357 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/hu/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Készítsen videoklipeket és képernyőképeket a webről, és mentse őket ideiglenesen vagy véglegesen." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Készítsen képernyőképet" + }, + "myShotsLink": { + "message": "Az Ön képei" + }, + "screenshotInstructions": { + "message": "Húzza, vagy kattintson a lapra a terület kiválasztásához. Nyomjon ESC-t a megszakításhoz." + }, + "saveScreenshotSelectedArea": { + "message": "Mentés" + }, + "saveScreenshotVisibleArea": { + "message": "Láthatóak mentése" + }, + "saveScreenshotFullPage": { + "message": "Teljes oldal mentése" + }, + "cancelScreenshot": { + "message": "Mégse" + }, + "downloadScreenshot": { + "message": "Letöltés" + }, + "notificationLinkCopiedTitle": { + "message": "Hivatkozás másolva" + }, + "notificationLinkCopiedDetails": { + "message": "A képernyőképre mutató hivatkozás a vágólapra lett másolva. Nyomjon $META_KEY$-V-t a beillesztéshez.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Nem működik." + }, + "requestErrorDetails": { + "message": "Bocsánat! Nem tudtuk menteni a képet. Próbálkozzon később." + }, + "connectionErrorTitle": { + "message": "Nem tudunk kapcsolódni a képernyőképekhez." + }, + "connectionErrorDetails": { + "message": "Ellenőrizze az internetkapcsolatot. Ha tud kapcsolódni az internetre, akkor lehet, hogy ideiglenes probléma van a Firefox képernyőképek szolgáltatással." + }, + "loginErrorDetails": { + "message": "Nem tudtuk elmenteni a képét, mert probléma lépett fel a Firefox képernyőképek szolgáltatással. Próbálja újra később." + }, + "unshootablePageErrorTitle": { + "message": "Nem lehet képet készíteni erről a lapról." + }, + "unshootablePageErrorDetails": { + "message": "Ez egy nem szabványos weblap, így nem készíthet róla képernyőképet." + }, + "selfScreenshotErrorTitle": { + "message": "Nem készíthet képet a Firefox képernyőképek oldalról!" + }, + "genericErrorTitle": { + "message": "Húha! A Firefox képernyőképek megkergült." + }, + "genericErrorDetails": { + "message": "Nem vagyunk benne biztosak, hogy mi történt. Próbálja újra, vagy készítsen képet egy másik oldalról." + }, + "tourBodyOne": { + "message": "Készítsen, mentsen és osszon meg képernyőképeket, anélkül, hogy elhagyná a Firefoxot." + }, + "tourHeaderTwo": { + "message": "Csak azt mentse, amit szeretne" + }, + "tourBodyTwo": { + "message": "Kattintson és húzzon, hogy csak a lap egy részét mentse el. Vagy csak rá is mutathat a kijelöléshez." + }, + "tourHeaderThree": { + "message": "Ahogy tetszik" + }, + "tourBodyThree": { + "message": "Mentse a kivágott képeket a webre a könnyebb megosztáshoz, vagy töltse le a számítógépére. Rá is kattinthat a Képernyőképek gombra, hogy megtalálja az összes képét." + }, + "tourHeaderFour": { + "message": "Mentsen ablakokat vagy teljes lapokat" + }, + "tourBodyFour": { + "message": "Válassza a jobb felső sarokban lévő gombokat, hogy egy látható területet mentsen az ablakból, vagy elmentsen egy teljes oldalt." + }, + "tourSkip": { + "message": "Kihagyás" + }, + "tourNext": { + "message": "Következő dia" + }, + "tourPrevious": { + "message": "Előző dia" + }, + "tourDone": { + "message": "Kész" + }, + "termsAndPrivacyNotice": { + "message": "A Firefox képernyőképek használatával, Ön beleegyezik a képernyőképek $TERMSANDPRIVACYNOTICETERMSLINK$ és $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Feltételeibe" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Adatvédelmi nyilatkozatába" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/hy_AM/messages.json b/browser/extensions/screenshots/webextension/_locales/hy_AM/messages.json new file mode 100644 index 000000000000..a85e991157ad --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/hy_AM/messages.json @@ -0,0 +1,106 @@ +{ + "addonDescription": { + "message": "Ստացեք հոլովակներ և էկրանի հանույթներ վեբից և պահպանեք դանք ժամանակավոր կամ մշտապես:" + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Ստանալ էկրանի պատկերը" + }, + "myShotsLink": { + "message": "Իմ պատկերները" + }, + "screenshotInstructions": { + "message": "Քաշեք և սեղմեք էջի վրա՝ ընտրելու տարածքը: Սեղմեք ESC՝ չեղարկելու համար:" + }, + "saveScreenshotSelectedArea": { + "message": "Պահպանել" + }, + "saveScreenshotVisibleArea": { + "message": "Պահպանելի տեսանելի" + }, + "saveScreenshotFullPage": { + "message": "Պահպանել ամբողջ էջը" + }, + "cancelScreenshot": { + "message": "Չեղարկել" + }, + "downloadScreenshot": { + "message": "Ներբեռնել" + }, + "notificationLinkCopiedTitle": { + "message": "Հղումը պատճենվել է" + }, + "notificationLinkCopiedDetails": { + "message": "Ձեր պատկերի հղումը պատճենվել է: Սեղմեք $META_KEY$-V՝ այն տեղադրելու համար:", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Անսարք:" + }, + "requestErrorDetails": { + "message": "Ցավոք մենք չենք կարող պահպանել պատկեր: Կրկին փորձեք ավելի ուշ:" + }, + "connectionErrorTitle": { + "message": "Հնարավոր չէ ապակցել էկրանի ձեր հանույթներին:" + }, + "connectionErrorDetails": { + "message": "Խնդրում ենք ստուգել համացանցային կապակցումը: Եթե մուտք չունեք համացանց՝ հնարավոր է՝ դա Firefox Screenshots ծառայության հետ կապված ժամանակավոր խնդիր է:" + }, + "loginErrorDetails": { + "message": "Մենք չենք կարող պահպանել ձեր պատկերը, քանի որ խնդիր կա Firefox Screenshots ծառայության հետ: Փորձեք ավելի ուշ:" + }, + "unshootablePageErrorTitle": { + "message": "Հնարավոր չէ ստանալ էկրանի պատկերը:" + }, + "unshootablePageErrorDetails": { + "message": "Սա ստանդարտ վեբ էջ չէ, ուստի դուք չեք կարող ստանալ դրա պատկերը:" + }, + "selfScreenshotErrorTitle": { + "message": "Դուք չեք կարող ստանալ Firefox Screenshots-ի էջի պատկերը:" + }, + "genericErrorTitle": { + "message": "Firefox Screenshots-ը գնաց գլխիվայր:" + }, + "genericErrorDetails": { + "message": "Մենք վստահ չենք, թե ինչ է տեղի ունեցնել: Կրկին փորձեք կամ փորձեք ստանալ մեկ այլ էջի պատկերը:" + }, + "tourBodyOne": { + "message": "Ստացեք, պահպանեք և համօգտագործեք էկրանի հանույթները՝ առանց Firefox-ը լքելու: " + }, + "tourHeaderTwo": { + "message": "Ստացեք միայն այն, ինչ Ձեզ պետք է:" + }, + "tourBodyTwo": { + "message": "Սեղմեք և քաշեք՝ ստանալու համար միայն էջի մի մասը: Նաև կարող եք վրայով անցկացնել՝ գունանշելու համար ընտրումը:" + }, + "tourHeaderThree": { + "message": "Ինչպես որ հավանում եք այն" + }, + "tourBodyThree": { + "message": "Պահպանեք ձեր եզրատած որոշ պատկերներ վեբում՝ դրանք հեշտությամբ համօգտագործելու կամ ներբեռնելու համար ձեր համակարգչում: Նաև կարող եք սեղմել Իմ պատկերները՝ գտնելու ձեր բոլոր ֆայլերը:" + }, + "tourHeaderFour": { + "message": "Ստանալ պատուհանը կամ ամբողջ էջեր" + }, + "tourBodyFour": { + "message": "Ընտրեք կոճակները վերևի աջ մասում՝ տեսանելի հատվածը ստանալու համար պատուհանում կամ ամբողջ էջը ստանալու համար:" + }, + "tourSkip": { + "message": "Բաց թողնել" + }, + "tourNext": { + "message": "Հաջորդ սահիկը" + }, + "tourPrevious": { + "message": "Նախորդ սահիկը" + }, + "tourDone": { + "message": "Պատրաստ է" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/id/messages.json b/browser/extensions/screenshots/webextension/_locales/id/messages.json new file mode 100644 index 000000000000..e7d7847974c1 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/id/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Rekam klip dan tangkapan layar dari Web dan simpan untuk sementara atau secara permanen." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Buat Tangkapan layar" + }, + "myShotsLink": { + "message": "Gambar Saya" + }, + "screenshotInstructions": { + "message": "Seret atau klik pada laman untuk memilih area. Tekan ESC untuk membatalkan." + }, + "saveScreenshotSelectedArea": { + "message": "Simpan" + }, + "saveScreenshotVisibleArea": { + "message": "Simpan yang terlihat" + }, + "saveScreenshotFullPage": { + "message": "Simpan laman sepenuhnya" + }, + "cancelScreenshot": { + "message": "Batal" + }, + "downloadScreenshot": { + "message": "Unduh" + }, + "notificationLinkCopiedTitle": { + "message": "Tautan Disalin" + }, + "notificationLinkCopiedDetails": { + "message": "Tautan ke gambar Anda telah disalin ke papan klip. Tekan $META_KEY$-V untuk menempelkan.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Tak dapat digunakan." + }, + "requestErrorDetails": { + "message": "Maaf! Kami tidak dapat menyimpan gambar Anda. Silakan coba lagi." + }, + "connectionErrorTitle": { + "message": "Kami tidak dapat terhubung dengan tangkapan layar Anda." + }, + "connectionErrorDetails": { + "message": "Silakan periksa sambungan Internet Anda. Jika Anda dapat tersambung ke Internet, mungkin terjadi masalah sementara pada layanan Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Kami tidak dapat menyimpan gambar Anda karena ada masalah dengan layanan Firefox Screenshots. Silakan coba kembali nanti." + }, + "unshootablePageErrorTitle": { + "message": "Kami tidak dapat menangkap layar laman ini." + }, + "unshootablePageErrorDetails": { + "message": "Ini bukan laman Web yang standar, sehingga Anda tidak dapat membuat tangkapan dari layar ini." + }, + "selfScreenshotErrorTitle": { + "message": "Anda tidak dapat merekam gambar dari laman Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "Wah! Firefox Screenshots mendadak kacau." + }, + "genericErrorDetails": { + "message": "Kami tidak yakin akan apa yang terjadi. Ingin mencoba lagi atau merekam gambar dari laman yang berbeda?" + }, + "tourBodyOne": { + "message": "Ambil, simpan, dan bagikan tangkapan layar tanpa meninggalkan Firefox." + }, + "tourHeaderTwo": { + "message": "Rekam Bagian Yang Anda Inginkan" + }, + "tourBodyTwo": { + "message": "Klik dan seret untuk merekam sebagian area laman. Anda juga dapat menggeser kursor untuk menyoroti pilihan Anda." + }, + "tourHeaderThree": { + "message": "Sesuka Anda" + }, + "tourBodyThree": { + "message": "Simpan potongan tangkapan Anda ke Web agar mudah dibagikan, atau unduh ke komputer. Anda pun dapat mengeklik pada tombol Gambar Saya untuk menemukan semua tangkapan yang pernah Anda rekam." + }, + "tourHeaderFour": { + "message": "Rekam Jendela atau Seluruh Laman" + }, + "tourBodyFour": { + "message": "Pilih tombol di kanan atas untuk merekam area yang terlihat pada jendela atau rekam seluruh laman." + }, + "tourSkip": { + "message": "Lewati" + }, + "tourNext": { + "message": "Salindia Selanjutnya" + }, + "tourPrevious": { + "message": "Salindia Sebelumnya" + }, + "tourDone": { + "message": "Selesai" + }, + "termsAndPrivacyNotice": { + "message": "Dengan menggunakan Firefox Screenshots, Anda setuju dengan $TERMSANDPRIVACYNOTICETERMSLINK$ dan $TERMSANDPRIVACYNOTICEPRIVACYLINK$ Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Ketentuan" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Kebijakan Privasi" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/it/messages.json b/browser/extensions/screenshots/webextension/_locales/it/messages.json new file mode 100644 index 000000000000..8f2231bc0dd6 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/it/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Crea screenshot di contenuti sul Web e salvali, solo per un periodo di tempo o in modo permanente." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Cattura screenshot" + }, + "myShotsLink": { + "message": "I miei screenshot" + }, + "screenshotInstructions": { + "message": "Trascina o fai clic su una pagina per selezionare una regione. Premi ESC per annullare." + }, + "saveScreenshotSelectedArea": { + "message": "Salva" + }, + "saveScreenshotVisibleArea": { + "message": "Salva l’area visibile" + }, + "saveScreenshotFullPage": { + "message": "Salva l’intera schermata" + }, + "cancelScreenshot": { + "message": "Annulla" + }, + "downloadScreenshot": { + "message": "Scarica" + }, + "notificationLinkCopiedTitle": { + "message": "Link aggiunto agli appunti" + }, + "notificationLinkCopiedDetails": { + "message": "Il link all’immagine è stato copiato negli appunti. Utilizza $META_KEY$-V per incollarlo.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Fuori servizio" + }, + "requestErrorDetails": { + "message": "Siamo spiacenti, non è stato possibile salvare l’immagine. Riprova più tardi." + }, + "connectionErrorTitle": { + "message": "Non è possibile accedere agli screenshot salvati." + }, + "connectionErrorDetails": { + "message": "Verifica che la connessione a Internet stia funzionando correttamente. Se è possibile accedere ad altri siti, potrebbe trattarsi di un problema temporaneo con il servizio Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Non è stato possibile salvare l’immagine in quanto si è verificato un problema con il servizio Firefox Screenshots. Riprova più tardi." + }, + "unshootablePageErrorTitle": { + "message": "Non è possibile salvare uno screenshot di questa pagina." + }, + "unshootablePageErrorDetails": { + "message": "Non è possibile salvare uno screenshot in quanto non si tratta di una normale pagina web." + }, + "selfScreenshotErrorTitle": { + "message": "Non è possibile salvare uno screenshot di una pagina di Firefox Screenshots" + }, + "genericErrorTitle": { + "message": "Wow! Firefox Screenshots è andato in tilt" + }, + "genericErrorDetails": { + "message": "Non sappiamo che cosa sia successo. Riprova, magari con una pagina diversa." + }, + "tourBodyOne": { + "message": "Cattura, salva e condividi screenshot senza mai uscire da Firefox." + }, + "tourHeaderTwo": { + "message": "Cattura solo ciò che ti serve" + }, + "tourBodyTwo": { + "message": "Fai clic e trascina per catturare solo una parte della pagina. Posiziona il mouse sopra all’area selezionata per evidenziarla." + }, + "tourHeaderThree": { + "message": "Come piace a te" + }, + "tourBodyThree": { + "message": "Cattura lo screenshot di una pagina web, ritaglialo e salvalo online per condividerlo in modo più veloce, oppure scaricalo sul tuo computer. Puoi anche utilizzare il pulsante “I miei screenshot” per ritrovare tutte le immagini che hai salvato." + }, + "tourHeaderFour": { + "message": "Cattura una finestra o una pagina intera" + }, + "tourBodyFour": { + "message": "Utilizza i pulsanti in alto a destra per catturare una parte della finestra o l’intera pagina." + }, + "tourSkip": { + "message": "Ignora" + }, + "tourNext": { + "message": "Schermata successiva" + }, + "tourPrevious": { + "message": "Schermata precedente" + }, + "tourDone": { + "message": "Fine" + }, + "termsAndPrivacyNotice": { + "message": "Utilizzando Firefox Screenshots si accettano le $TERMSANDPRIVACYNOTICETERMSLINK$ e l’$TERMSANDPRIVACYNOTICEPRIVACYLINK$ del servizio.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "condizioni di utilizzo" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "informativa sulla privacy" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/ja/messages.json b/browser/extensions/screenshots/webextension/_locales/ja/messages.json new file mode 100644 index 000000000000..c66be5601b50 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/ja/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "ウェブからスクリーンショットを撮って、一時的または永久にそれを保存しましょう。" + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "スクリーンショットを撮る" + }, + "myShotsLink": { + "message": "自分のショット" + }, + "screenshotInstructions": { + "message": "ページをドラッグまたはクリックして範囲を選択してください。ESC キーを押すとキャンセルできます。" + }, + "saveScreenshotSelectedArea": { + "message": "保存" + }, + "saveScreenshotVisibleArea": { + "message": "表示範囲を保存" + }, + "saveScreenshotFullPage": { + "message": "ページ全体を保存" + }, + "cancelScreenshot": { + "message": "キャンセル" + }, + "downloadScreenshot": { + "message": "ダウンロード" + }, + "notificationLinkCopiedTitle": { + "message": "リンクをコピーしました" + }, + "notificationLinkCopiedDetails": { + "message": "ショットへのリンクがクリップボードにコピーされました。$META_KEY$+V キーで貼り付けられます。", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "問題が発生しました。" + }, + "requestErrorDetails": { + "message": "申し訳ありませんが、ショットを保存できませんでした。また後で試してください。" + }, + "connectionErrorTitle": { + "message": "スクリーンショットへ接続できません。" + }, + "connectionErrorDetails": { + "message": "お使いのインターネット接続を確認してください。インターネットへ接続できる場合は、Firefox Screenshots サービスに一時的な問題が発生しているものと思われます。" + }, + "loginErrorDetails": { + "message": "Firefox Screenshots サービスに問題があるため、ショットを保存できませんでした。また後で試してください。" + }, + "unshootablePageErrorTitle": { + "message": "このページはスクリーンショットを撮れません。" + }, + "unshootablePageErrorDetails": { + "message": "これは通常のウェブページでないため、スクリーンショットを撮ることができません。" + }, + "selfScreenshotErrorTitle": { + "message": "Firefox Screenshots ページのショットは撮れません。" + }, + "genericErrorTitle": { + "message": "Firefox Screenshots に問題が発生しました。" + }, + "genericErrorDetails": { + "message": "何か問題が発生したようです。再度試すか、別のページのショットを撮ってみてください。" + }, + "tourBodyOne": { + "message": "Firefox を離れることなく、スクリーンショットを撮影、保存、共有。" + }, + "tourHeaderTwo": { + "message": "必要なものだけをキャプチャー" + }, + "tourBodyTwo": { + "message": "クリック&ドラッグでページの一部だけをキャプチャーできます。また、マウスを当てれば選択範囲が強調表示されます。" + }, + "tourHeaderThree": { + "message": "お好きなように" + }, + "tourBodyThree": { + "message": "切り取ったショットを簡単に共有できるようウェブ上に保存したり、手元へダウンロードしたり。また「自分のショット」ボタンをクリックすれば、これまでに撮ったすべてのショットを見られます。" + }, + "tourHeaderFour": { + "message": "ウィンドウもしくはページ全体をキャプチャー" + }, + "tourBodyFour": { + "message": "右上のボタンを選択して、ウィンドウ内の表示範囲もしくはページ全体をキャプチャーしましょう。" + }, + "tourSkip": { + "message": "スキップ" + }, + "tourNext": { + "message": "次のスライド" + }, + "tourPrevious": { + "message": "前のスライド" + }, + "tourDone": { + "message": "完了" + }, + "termsAndPrivacyNotice": { + "message": "Firefox Screenshots を使うことで、あなたは Screenshots の $TERMSANDPRIVACYNOTICETERMSLINK$ と $TERMSANDPRIVACYNOTICEPRIVACYLINK$ に同意したことになります。", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "利用規約" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "プライバシー通知" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/kab/messages.json b/browser/extensions/screenshots/webextension/_locales/kab/messages.json new file mode 100644 index 000000000000..85bc7dabccc3 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/kab/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Ṭṭef imrayen akked igdilen si Web sakin sekles-iten s wudem askudan neɣ s wudem yezgan." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Ṭṭef agdil" + }, + "myShotsLink": { + "message": "Tuṭṭfiwin-iw" + }, + "screenshotInstructions": { + "message": "Zuγer sakin sit γef af usebter akken ad tferneḍ tamnaṭ. Senned γef ESC akken ad tesfesxeḍ." + }, + "saveScreenshotSelectedArea": { + "message": "Sekles" + }, + "saveScreenshotVisibleArea": { + "message": "Sekles ayen ibanen" + }, + "saveScreenshotFullPage": { + "message": "Sekles asebter meṛṛa" + }, + "cancelScreenshot": { + "message": "Sefsex" + }, + "downloadScreenshot": { + "message": "Sider" + }, + "notificationLinkCopiedTitle": { + "message": "Aseγwen yettwanγel" + }, + "notificationLinkCopiedDetails": { + "message": "Aseγwen ar tuṭṭfa-ik yettwanγel yef afus. Senned yef $META_KEY$-V akken ad tsenṭḍeḍ.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Yeffeγ i talast." + }, + "requestErrorDetails": { + "message": "Suref-aγ! Ur nezmir ara ad nsekles tuṭṭfa-ik. Ɛreḍ tikelt-nniḍen." + }, + "connectionErrorTitle": { + "message": "Ur nezmir ara ad neqqen ar tuṭṭfiwin-ik n ugdil." + }, + "connectionErrorDetails": { + "message": "Senqed tuqqna-ik Internet. Ma yella tzemred ad teqqneḍ ar Internet, ahat d ugur kan meẓẓiyen deg umeẓlu Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "UR nseklas ara tuṭṭfa-ik acku yella ugur akked umezlu Firefox Screenshots. Ma ulac aɣilif ɛreḍ tikelt-nniḍen." + }, + "unshootablePageErrorTitle": { + "message": "Ur nezmir ara ad neṭṭef agdil n usebter-agi." + }, + "unshootablePageErrorDetails": { + "message": "Mačči d asebter Web am iyaḍ, ur tizmireḍ ara ad s-teṭṭfeḍ agdil." + }, + "selfScreenshotErrorTitle": { + "message": "Ur tezmireḍ ar ad teṭṭfeḍ agdil n usebter Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "Ihuh! Firefox Screenshots ur iteddu ara." + }, + "genericErrorDetails": { + "message": "Ur neẓri ara acu yeḍran. Ɛreḍ tikelt-nniḍen neɣ ṭṭef agdil n usebter-nniḍen?" + }, + "tourBodyOne": { + "message": "Ṭṭef, sekles, bḍu igdilen war ma teffɣeḍ si Firefox." + }, + "tourHeaderTwo": { + "message": "Ṭṭef kan ayen tebγiḍ" + }, + "tourBodyTwo": { + "message": "Sit sakin zuɣer akken ad teṭṭfeḍ aḥric seg usebter. Tzemreḍ daɣen ad tesrifgeḍ akken ad tsebṛuṛqeḍ afran-ik." + }, + "tourHeaderThree": { + "message": "Akken tebγiḍ" + }, + "tourBodyThree": { + "message": "Sekles tuṭṭfiwin-ik ar Web i beṭṭu fessusen, neɣ sider-itent-id ar uselkim-ik. Tzemr€d daɣen ad tiseḍ ɣef tqeffalt Tiṭṭfiwin-iw akken ad tafeḍ akk tuṭṭfiwin n ugdil i teggid." + }, + "tourHeaderFour": { + "message": "Ṭṭef isfuyla neγ isebtar meṛṛa" + }, + "tourBodyFour": { + "message": "Fren tiqeffalin s afella ayeffus akken ad teṭṭfeḍ tamnaṭ yettbanen deg usfaylu neɣ asebter i meṛṛa." + }, + "tourSkip": { + "message": "Zgel" + }, + "tourNext": { + "message": "Tigri n zdat" + }, + "tourPrevious": { + "message": "Tigri n deffir" + }, + "tourDone": { + "message": "Immed" + }, + "termsAndPrivacyNotice": { + "message": "S useqdec Firefox Screenshots, ad tqebleḍ $TERMSANDPRIVACYNOTICETERMSLINK$ n Screenshots akked $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Tiwtilin" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Tasertit n tbaḍnit" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/kk/messages.json b/browser/extensions/screenshots/webextension/_locales/kk/messages.json new file mode 100644 index 000000000000..fec28685b423 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/kk/messages.json @@ -0,0 +1,85 @@ +{ + "addonDescription": { + "message": "Интернеттен скриншоттарды түсіріп, оларды уақытша немесе тұрақты түрде сақтаңыз." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Скриншотты түсіру" + }, + "myShotsLink": { + "message": "Менің скриншоттарым" + }, + "screenshotInstructions": { + "message": "Аймақты таңдау үшін бетте шертіңіз. Бас тарту үшін ESC басыңыз." + }, + "saveScreenshotSelectedArea": { + "message": "Сақтау" + }, + "saveScreenshotVisibleArea": { + "message": "Көрінетінді сақтау" + }, + "saveScreenshotFullPage": { + "message": "Толық парақты сақтау" + }, + "cancelScreenshot": { + "message": "Бас тарту" + }, + "downloadScreenshot": { + "message": "Жүктеп алу" + }, + "notificationLinkCopiedTitle": { + "message": "Сілтеме көшірілді" + }, + "notificationLinkCopiedDetails": { + "message": "Сіздің скриншотыңызға сілтеме алмасу буферіне көшірілді. Кірістіру үшін $META_KEY$-V басыңыз.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Жұмыс істемейді." + }, + "requestErrorDetails": { + "message": "Кешіріңіз! Сіздің скриншотыңызды сақтай алмадық. Кейінірек қайталап көріңіз." + }, + "connectionErrorTitle": { + "message": "Скриншоттарыңызға байланыса алмадық." + }, + "unshootablePageErrorTitle": { + "message": "Бұл беттің скриншотын түсіре алмаймыз." + }, + "unshootablePageErrorDetails": { + "message": "Бұл қалыпты веб беті емес, сондықтан оның скриншотын түсіру мүмкін емес." + }, + "selfScreenshotErrorTitle": { + "message": "Firefox скриншоттары бетінің скриншотын түсіру мүмкін емес!" + }, + "genericErrorTitle": { + "message": "Қап! Firefox скриншоттары жасамай қалған сияқты." + }, + "tourBodyOne": { + "message": "Firefox ішінен скриншоттарды түсіру, сақтау және олармен бөлісу." + }, + "tourHeaderTwo": { + "message": "Тек керек нәрсені түсіріңіз" + }, + "tourHeaderThree": { + "message": "Өзіңізге керек түрде" + }, + "tourSkip": { + "message": "Аттап кету" + }, + "tourNext": { + "message": "Келесі слайд" + }, + "tourPrevious": { + "message": "Алдыңғы слайд" + }, + "tourDone": { + "message": "Дайын" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/ko/messages.json b/browser/extensions/screenshots/webextension/_locales/ko/messages.json new file mode 100644 index 000000000000..795c865936e0 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/ko/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "웹 페이지를 찍거나 영상으로 만들어 임시로, 혹은 영구히 보관하세요." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "스크린샷 찍기" + }, + "myShotsLink": { + "message": "내 스크린샷" + }, + "screenshotInstructions": { + "message": "캡처 할 범위를 드래그하거나 클릭하세요. ESC를 누르면 취소됩니다." + }, + "saveScreenshotSelectedArea": { + "message": "저장" + }, + "saveScreenshotVisibleArea": { + "message": "보이는 내용 저장" + }, + "saveScreenshotFullPage": { + "message": "전체 페이지 저장" + }, + "cancelScreenshot": { + "message": "취소" + }, + "downloadScreenshot": { + "message": "다운로드" + }, + "notificationLinkCopiedTitle": { + "message": "링크 복사됨" + }, + "notificationLinkCopiedDetails": { + "message": "방금 찍은 스냅샷의 링크가 클립보드에 저장됐습니다. 붙여넣으려면 $META_KEY$-V를 누르세요.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "문제가 발생했습니다." + }, + "requestErrorDetails": { + "message": "죄송합니다. 스크린샷을 저장하지 못했습니다. 잠시 후에 다시 시도해주세요." + }, + "connectionErrorTitle": { + "message": "스크린샷에 접속할 수 없습니다." + }, + "connectionErrorDetails": { + "message": "인터넷 연결 상태를 확인해주세요. 만약 인터넷이 연결돼있다면, Firefox Screenshots 서비스에 잠깐 문제가 있을 수도 있습니다." + }, + "loginErrorDetails": { + "message": "Firefox Screenshots 서비스에 잠시 문제가 있어 저장에 실패했습니다. 잠시 후에 다시 시도해주세요." + }, + "unshootablePageErrorTitle": { + "message": "이 페이지를 캡처할 수 없습니다." + }, + "unshootablePageErrorDetails": { + "message": "표준 웹 페이지가 아니어서 스크린샷을 찍을 수 없습니다." + }, + "selfScreenshotErrorTitle": { + "message": "Firefox Screenshots 페이지는 캡처할 수 없어요!" + }, + "genericErrorTitle": { + "message": "와우! Firefox Screenshots이 망가졌네요." + }, + "genericErrorDetails": { + "message": "무슨 일이 있었는지 모르겠네요. 다시 시도하시거나 다른 페이지 스크린샷을 찍어 보시겠어요?" + }, + "tourBodyOne": { + "message": "Firefox를 떠나지 않은 채로 찍고, 저장하고, 공유하세요." + }, + "tourHeaderTwo": { + "message": "원하는 것을 캡춰하세요" + }, + "tourBodyTwo": { + "message": "캡춰할 페이지의 부분을 클릭해서 드래그 해 보세요. 마우스를 올려서 선택한 부분을 확인할 수 있습니다." + }, + "tourHeaderThree": { + "message": "내가 원하는 대로" + }, + "tourBodyThree": { + "message": "스크린샷을 공유하거나, 컴퓨터로 다운로드할 수도 있습니다. 내 스크린샷 버튼을 눌러서 지금까지 찍었던 모든 스크린샷을 찾을 수도 있습니다." + }, + "tourHeaderFour": { + "message": "창이나 페이지 전체를 캡춰할 수 있습니다" + }, + "tourBodyFour": { + "message": "우측 위에 있는 버튼을 눌러 창을 캡처하거나 페이지 전체를 캡처할 수 있습니다." + }, + "tourSkip": { + "message": "건너뛰기" + }, + "tourNext": { + "message": "다음 슬라이드" + }, + "tourPrevious": { + "message": "이전 슬라이드" + }, + "tourDone": { + "message": "완료" + }, + "termsAndPrivacyNotice": { + "message": "Firefox Screenshots를 사용하므로써, $TERMSANDPRIVACYNOTICETERMSLINK$와 $TERMSANDPRIVACYNOTICEPRIVACYLINK$에 동의하게 됩니다.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "이용약관" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "개인 정보 취급 방침" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/lij/messages.json b/browser/extensions/screenshots/webextension/_locales/lij/messages.json new file mode 100644 index 000000000000..e975706625cf --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/lij/messages.json @@ -0,0 +1,106 @@ +{ + "addonDescription": { + "message": "Fanni de föto do schermo da-o Web e sarvale in mòddo tenporaneo ò cin mòddo che restan." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Fanni 'na föto do schermo" + }, + "myShotsLink": { + "message": "E mæ föto do schermo" + }, + "screenshotInstructions": { + "message": "Rebela ò sciacca in sciâ pagina pe seleçionâ 'na region. Sciacca ESC pe anulâ." + }, + "saveScreenshotSelectedArea": { + "message": "Sarva" + }, + "saveScreenshotVisibleArea": { + "message": "Sarva o vixibile" + }, + "saveScreenshotFullPage": { + "message": "Sarva tutta a pagina" + }, + "cancelScreenshot": { + "message": "Anulla" + }, + "downloadScreenshot": { + "message": "Descarega" + }, + "notificationLinkCopiedTitle": { + "message": "Colegamento copiou inti aponti" + }, + "notificationLinkCopiedDetails": { + "message": "O colegamento a l'inmagine o l'é stæto copiou inti aponti. Sciacca$META_KEY$-V pe incolalo.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Feua serviçio." + }, + "requestErrorDetails": { + "message": "Ne spiaxe! No poemmo sarvâ l'inmagine. Pe piaxei preuva torna dòppo." + }, + "connectionErrorTitle": { + "message": "No poemmo conetise a-e teu föto do schermo." + }, + "connectionErrorDetails": { + "message": "Pe piaxei contròlla a teu conescion a l'Internet. Se ti riesci a conetite a l'Internet, ghe poeiva ese 'n problema tenporaneo co-o serviçio de Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "No poemmo sarvâ a teu inmagine perché gh'é 'n problema co-o serviçio de Firefox Screenshot. Pe piaxei preuva torna dòppo." + }, + "unshootablePageErrorTitle": { + "message": "No poemmo fâ 'na föto do schermo de sta pagina." + }, + "unshootablePageErrorDetails": { + "message": "Sta chi a no l'é 'na pagina Web standard, coscì no peu faghe 'na föto do schermo." + }, + "selfScreenshotErrorTitle": { + "message": "No ti peu fâ 'na föto do schermo a 'na pagina de Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "Ahime mi! Firefox Screeshot o s'é ciantou." + }, + "genericErrorDetails": { + "message": "Niatri no emmo ben acapio cöse l'é sucesso. Ti peu miga preuvâ co-ina pagina dispægia?" + }, + "tourBodyOne": { + "message": "Fanni, sarva e condividdi föto do schermo sensa sciortî da Firefox." + }, + "tourHeaderTwo": { + "message": "Catua solo quello che t'eu" + }, + "tourBodyTwo": { + "message": "Sciacca e rebela pe catuâ solo 'na porçion de 'na pagina. Ti peu anche anâ co-o ratto sorvia l'area seleçionâ pe evidençiala." + }, + "tourHeaderThree": { + "message": "Comme te piaxe" + }, + "tourBodyThree": { + "message": "Sarva 'n ritaggio de 'na pagina Web pe condividila in mòddo ciù façile ò scaregala into teu computer. Ti peu anche sciacâ into pomello “E mæ föto do schermo pe atrovâ” quello che t'æ za pigiou." + }, + "tourHeaderFour": { + "message": "Catua 'n barcon ò 'na pagina intrega" + }, + "tourBodyFour": { + "message": "Seleçionn-a i pomelli de d'ato a drita pe catuâ l'area vixibile into barcon ò a pagina intrega." + }, + "tourSkip": { + "message": "Ignòra" + }, + "tourNext": { + "message": "Pròscima schermâ" + }, + "tourPrevious": { + "message": "Schermâ de primma" + }, + "tourDone": { + "message": "Fæto" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/lo/messages.json b/browser/extensions/screenshots/webextension/_locales/lo/messages.json new file mode 100644 index 000000000000..c1e0df0f8b40 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/lo/messages.json @@ -0,0 +1,38 @@ +{ + "addonAuthorsList": { + "message": "Mozilla " + }, + "saveScreenshotSelectedArea": { + "message": "ບັນທຶກ" + }, + "saveScreenshotVisibleArea": { + "message": "ບັນທຶກສ່ວນທີ່ເບິງເຫັນໄດ້" + }, + "saveScreenshotFullPage": { + "message": "ບັນທຶກຫມົດຫນ້າ" + }, + "cancelScreenshot": { + "message": "" + }, + "downloadScreenshot": { + "message": "ດາວໂຫລດ" + }, + "notificationLinkCopiedTitle": { + "message": "ໄດ້ສຳເນົາລີ້ງໄວ້ແລ້ວ" + }, + "unshootablePageErrorTitle": { + "message": "ພວກເຮົາບໍ່ສາມາດຖ່າຍຮູບຫນ້າຈໍຂອງຫນ້ານີ້ໄດ້." + }, + "tourSkip": { + "message": "ຂ້າມໄປ" + }, + "tourNext": { + "message": "ສະໄລດ໌ຕໍ່ໄປ" + }, + "tourPrevious": { + "message": "ສະໄລດ໌ກ່ອນຫນ້ານີ້" + }, + "tourDone": { + "message": "ສຳເລັດ" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/lt/messages.json b/browser/extensions/screenshots/webextension/_locales/lt/messages.json new file mode 100644 index 000000000000..9779c6efad33 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/lt/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Darykite iškarpas ir ekrano nuotraukos su interneto turiniu bei saugokite jas laikinai arba visąlaik." + }, + "addonAuthorsList": { + "message": "„Mozilla“ " + }, + "contextMenuLabel": { + "message": "Padaryti ekrano nuotrauką" + }, + "myShotsLink": { + "message": "Mano kadrai" + }, + "screenshotInstructions": { + "message": "Tempkite arba spustelėkite tinklalapyje norėdami pasirinkti regioną. Spustelėkite „ESC“ norėdami atsisakyti." + }, + "saveScreenshotSelectedArea": { + "message": "Įrašyti" + }, + "saveScreenshotVisibleArea": { + "message": "Įrašyti matomą" + }, + "saveScreenshotFullPage": { + "message": "Įrašyti visą tinklalapį" + }, + "cancelScreenshot": { + "message": "Atsisakyti" + }, + "downloadScreenshot": { + "message": "Atsisiųsti" + }, + "notificationLinkCopiedTitle": { + "message": "Saitas nukopijuotas" + }, + "notificationLinkCopiedDetails": { + "message": "Jūsų nuotraukos saitas buvo nukopijuotas į iškarpinę. Spustelėkite „$META_KEY$-V“ norėdami įdėti.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Neveikia." + }, + "requestErrorDetails": { + "message": "Atsiprašome! Mums nepavyko įrašyti jūsų nuotraukos. Prašome pabandyti vėliau." + }, + "connectionErrorTitle": { + "message": "Mums nepavyko prisijungti prie jūsų ekrano nuotraukų." + }, + "connectionErrorDetails": { + "message": "Patikrinkite savo interneto ryšį. Jeigu galite prisijungti prie interneto, gali būti, kad yra laikina problema su „Firefox Screenshots“ paslauga." + }, + "loginErrorDetails": { + "message": "Mums nepavyko įrašyti jūsų nuotraukos, nes yra problema su „Firefox Screenshots“ paslauga. Prašome pabandyti vėliau." + }, + "unshootablePageErrorTitle": { + "message": "Mums nepavyko nufotografuoti šio tinklalapio." + }, + "unshootablePageErrorDetails": { + "message": "Tai nėra įprastas tinklalapis, tad negalite padaryti jo nuotraukos." + }, + "selfScreenshotErrorTitle": { + "message": "Negalite padaryti „Firefox Screenshots“ tinklalapio nuotraukos!" + }, + "genericErrorTitle": { + "message": "Vau! „Firefox Screenshots“ sugedo." + }, + "genericErrorDetails": { + "message": "Mes nesame tikri, kas ką tik nutiko. Norite pabandyti dar kartą arba nufotografuoti kitą tinklalapį?" + }, + "tourBodyOne": { + "message": "Darykite, įrašykite ir dalinkitės ekrano nuotraukomis nepalikdami „Firefox“." + }, + "tourHeaderTwo": { + "message": "Užfiksuokite būtent tai, ką norite" + }, + "tourBodyTwo": { + "message": "Spustelėkite ir tempkite, kad užfiksuotumėte tik dalį tinklalapio. Taip pat galite užvesti pelę, norėdami paryškinti savo pasirinkimą." + }, + "tourHeaderThree": { + "message": "Kaip jums patogiau" + }, + "tourBodyThree": { + "message": "Įrašykite padarytas nuotraukas saityne patogesniam dalinimuisi, arba atsisiųskite jas į savo kompiuterį. Spustelėję mygtuką „Mano kadrai“, matysite visas savo padarytas nuotraukas." + }, + "tourHeaderFour": { + "message": "Fiksuokite langus arba ištisus tinklalapius" + }, + "tourBodyFour": { + "message": "Pasirinkite mygtukus aukščiau dešinėje, norėdami užfiksuoti matomą lango dalį arba visą tinklalapį." + }, + "tourSkip": { + "message": "SKIP" + }, + "tourNext": { + "message": "Kita skaidrė" + }, + "tourPrevious": { + "message": "Buvusi skaidrė" + }, + "tourDone": { + "message": "Baigta" + }, + "termsAndPrivacyNotice": { + "message": "Naudodamiesi „Firefox Screenshots“, sutinkate su jų $TERMSANDPRIVACYNOTICETERMSLINK$ ir $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "sąlygomis" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "privatumo nuostatais" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/ms/messages.json b/browser/extensions/screenshots/webextension/_locales/ms/messages.json new file mode 100644 index 000000000000..e49a0d90841f --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/ms/messages.json @@ -0,0 +1,106 @@ +{ + "addonDescription": { + "message": "Ambil klip dan skrinshot dari Web dan simpan untuk sementara waktu atau kekal." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Ambil skrinshot" + }, + "myShotsLink": { + "message": "Shot Saya" + }, + "screenshotInstructions": { + "message": "Seret atau klik pada halaman untuk memilih kawasan. Tekan ESC untuk batalkan." + }, + "saveScreenshotSelectedArea": { + "message": "Simpan" + }, + "saveScreenshotVisibleArea": { + "message": "Simpan yang terpapar" + }, + "saveScreenshotFullPage": { + "message": "Simpan halaman penuh" + }, + "cancelScreenshot": { + "message": "Batal" + }, + "downloadScreenshot": { + "message": "Muat turun" + }, + "notificationLinkCopiedTitle": { + "message": "Pautan Disalin" + }, + "notificationLinkCopiedDetails": { + "message": "Pautan ke shot anda telah disalin ke klipbod. Tekan $META_KEY$-V untuk tampal.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Tidak berfungsi." + }, + "requestErrorDetails": { + "message": "Maaf! Kita tidak dapat menyimpan shot anda. Sila cuba lagi nanti." + }, + "connectionErrorTitle": { + "message": "Kami tidak dapat menyambungkan ke skrinshot anda." + }, + "connectionErrorDetails": { + "message": "Sila semak sambungan Internet anda. Jika anda boleh dapat sambungan ke Internet, mungkin ada masalah sementara dengan perkhidmatan screenshot di Firefox." + }, + "loginErrorDetails": { + "message": "Kita tidak dapat menyimpan skrinshot anda kerana ada masalah dengan perkhidmatan skrinshot di Firefox. Sila cuba lagi nanti." + }, + "unshootablePageErrorTitle": { + "message": "Halaman ini tidak boleh diskrinshot." + }, + "unshootablePageErrorDetails": { + "message": "Ini bukan halaman Web piawai, jadi anda tidak boleh membuat skrinshot." + }, + "selfScreenshotErrorTitle": { + "message": "Anda tidak boleh mengambil gambar halaman Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "Oh tidak! Firefox Screenshot tidak berfungsi dengan betul." + }, + "genericErrorDetails": { + "message": "Kami tidak pasti apa yang baru berlaku. Anda mahu cuba lagi atau mengambil gambar halaman lain?" + }, + "tourBodyOne": { + "message": "Ambil, simpan, dan kongsi screenshot tanpa meninggalkan pelayar Firefox." + }, + "tourHeaderTwo": { + "message": "Ambil gambar hanya yang anda mahu" + }, + "tourBodyTwo": { + "message": "Klik dan seret untuk mengambil gambar sebahagian daripada halaman. Anda boleh juga serlahkan pilihan anda." + }, + "tourHeaderThree": { + "message": "Seperti Yang Anda Suka" + }, + "tourBodyThree": { + "message": "Simpan rakaman yang dipotong ke Web, cara yang lebih mudah untuk berkongsi, atau memuatturunnya ke komputer anda. Anda juga boleh klik pada butang Shot Saya untuk mencari semua rakaman yang telah diambil." + }, + "tourHeaderFour": { + "message": "Tangkap Tetingkap atau Keseluruhan Halaman" + }, + "tourBodyFour": { + "message": "Pilih butang di bahagian atas kanan untuk merakam kawasan paparan dalam tetingkap atau untuk merakamkan keseluruhan halaman." + }, + "tourSkip": { + "message": "Langkau" + }, + "tourNext": { + "message": "Slaid Seterusnya" + }, + "tourPrevious": { + "message": "Slaid Sebelumnya" + }, + "tourDone": { + "message": "Selesai" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/nb_NO/messages.json b/browser/extensions/screenshots/webextension/_locales/nb_NO/messages.json new file mode 100644 index 000000000000..ed3a00a51e6c --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/nb_NO/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Ta klipp og skjermbilder fra nettet og lagre de midlertidig eller permanent." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Ta et skjermbilde" + }, + "myShotsLink": { + "message": "Mine skjermbilder" + }, + "screenshotInstructions": { + "message": "Dra eller klikk på siden for å velge en region. Trykk på ESC for å avbryte." + }, + "saveScreenshotSelectedArea": { + "message": "Lagre" + }, + "saveScreenshotVisibleArea": { + "message": "Lagre synlig område" + }, + "saveScreenshotFullPage": { + "message": "Lagre hele siden" + }, + "cancelScreenshot": { + "message": "Avbryt" + }, + "downloadScreenshot": { + "message": "Last ned" + }, + "notificationLinkCopiedTitle": { + "message": "Lenke kopiert" + }, + "notificationLinkCopiedDetails": { + "message": "Lenken til skjermbildet ditt er kopiert til utklippstavlen. Trykk på $META_KEY$-V for å lime inn.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "I ustand." + }, + "requestErrorDetails": { + "message": "Beklager! Vi klarte ikke å lagre skjermbildet ditt. Prøv igjen senere." + }, + "connectionErrorTitle": { + "message": "Vi kan ikke koble til dine skjermbilder." + }, + "connectionErrorDetails": { + "message": "Kontroller din internett-tilkopling. Om du kan koble til internett, kan det være et midlertidig problem med tjenesten Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Vi klarte ikke å lagre skjermbildet ditt, fordi det er et problem med tjenesten Firefox Screenshots. Prøv igjen senere." + }, + "unshootablePageErrorTitle": { + "message": "Vi kan ikke ta skjermbilde av siden." + }, + "unshootablePageErrorDetails": { + "message": "Dette er ikke en vanlig nettside, og du kan ikke ta skjermbilde av den." + }, + "selfScreenshotErrorTitle": { + "message": "Du kan ikke ta skjermbilde av siden Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "Oi! Det ser ut til at Firefox Screenshots ikke fungerer korrekt." + }, + "genericErrorDetails": { + "message": "Vi er ikke sikre på hva som hendte. Kan du prøve igjen eller ta et bilde av en annen side?" + }, + "tourBodyOne": { + "message": "Ta, lagre og del skjermbilder uten å forlate Firefox." + }, + "tourHeaderTwo": { + "message": "Ta bilde av akkurat hva du vil" + }, + "tourBodyTwo": { + "message": "Klikk for å dra og ta skjermbilde av bare en del av siden. Du kan også føre musen over for å framheve merket område." + }, + "tourHeaderThree": { + "message": "Som du vil ha det" + }, + "tourBodyThree": { + "message": "Lagre de beskjærte skjermbildene dine på nettet for enklere deling, eller last de ned til din datamaskin. Du kan også klikke på knappen Mine skjermbilde for å finne alle skjermbildene du har tatt." + }, + "tourHeaderFour": { + "message": "Ta skjermbilde av vinduer eller hele sider." + }, + "tourBodyFour": { + "message": "Bruk knappene i det øvre høyre hjørnet for å ta skjermbilde av det synlige området i vinduet eller for å ta skjermbilde av en hel side." + }, + "tourSkip": { + "message": "Hopp over" + }, + "tourNext": { + "message": "Neste slide" + }, + "tourPrevious": { + "message": "Forrige slide" + }, + "tourDone": { + "message": "Ferdig" + }, + "termsAndPrivacyNotice": { + "message": "Ved å bruke Firefox Screenshots, godtar du $TERMSANDPRIVACYNOTICETERMSLINK$ og $TERMSANDPRIVACYNOTICEPRIVACYLINK$ for Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "vilkår" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "personvernbestemmelser" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/nl/messages.json b/browser/extensions/screenshots/webextension/_locales/nl/messages.json new file mode 100644 index 000000000000..f5904e357794 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/nl/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Maak clips en schermafbeeldingen van het web en sla deze tijdelijk of permanent op." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Een schermafbeelding maken" + }, + "myShotsLink": { + "message": "Mijn afbeeldingen" + }, + "screenshotInstructions": { + "message": "Sleep of klik op de pagina om een gebied te selecteren. Druk op ESC om te annuleren." + }, + "saveScreenshotSelectedArea": { + "message": "Opslaan" + }, + "saveScreenshotVisibleArea": { + "message": "Zichtbaar gebied opslaan" + }, + "saveScreenshotFullPage": { + "message": "Volledige pagina opslaan" + }, + "cancelScreenshot": { + "message": "Annuleren" + }, + "downloadScreenshot": { + "message": "Downloaden" + }, + "notificationLinkCopiedTitle": { + "message": "Koppeling gekopieerd" + }, + "notificationLinkCopiedDetails": { + "message": "De koppeling naar uw afbeelding is naar het klembord gekopieerd. Druk op $META_KEY$-V om te plakken.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Buiten werking." + }, + "requestErrorDetails": { + "message": "Sorry! Uw afbeelding kon niet worden opgeslagen. Probeer het later opnieuw." + }, + "connectionErrorTitle": { + "message": "We kunnen geen verbinding met uw schermafdrukken maken." + }, + "connectionErrorDetails": { + "message": "Controleer uw internetverbinding. Als u verbinding met het internet kunt maken, kan er sprake zijn van een tijdelijk probleem met de Firefox Screenshots-service." + }, + "loginErrorDetails": { + "message": "Uw afbeelding kon niet worden opgeslagen, omdat er een probleem is met de Firefox Screenshots-service. Probeer het later opnieuw." + }, + "unshootablePageErrorTitle": { + "message": "Van deze pagina kan geen schermafbeelding worden gemaakt." + }, + "unshootablePageErrorDetails": { + "message": "Dit is geen standaardwebpagina, dus u kunt er geen schermafbeelding van maken." + }, + "selfScreenshotErrorTitle": { + "message": "U kunt geen afbeelding van een Firefox Screenshots-pagina maken!" + }, + "genericErrorTitle": { + "message": "Ho! Er is iets mis met Firefox Screenshots." + }, + "genericErrorDetails": { + "message": "We weten niet precies wat er zonet is gebeurd. Wilt u het nogmaals proberen of een schermafbeelding van een andere pagina maken?" + }, + "tourBodyOne": { + "message": "Maak, bewaar en deel schermafbeeldingen zonder Firefox te verlaten." + }, + "tourHeaderTwo": { + "message": "Leg alleen vast wat u wilt" + }, + "tourBodyTwo": { + "message": "Klik en sleep om alleen een gedeelte van een pagina vast te leggen. U kunt ook de muisaanwijzer boven een gebied houden om uw selectie te accentueren." + }, + "tourHeaderThree": { + "message": "Zoals u wilt" + }, + "tourBodyThree": { + "message": "Sla uw bijgesneden afbeeldingen op op het web voor makkelijker delen, of download ze naar uw computer. U kunt ook op de knop Mijn afbeeldingen klikken om al uw gemaakte afbeeldingen te vinden." + }, + "tourHeaderFour": { + "message": "Leg vensters of hele pagina’s vast" + }, + "tourBodyFour": { + "message": "Selecteer de knoppen rechtsboven om het zichtbare gebied in het venster vast te leggen, of om een hele pagina vast te leggen." + }, + "tourSkip": { + "message": "Overslaan" + }, + "tourNext": { + "message": "Volgende slide" + }, + "tourPrevious": { + "message": "Vorige slide" + }, + "tourDone": { + "message": "Gereed" + }, + "termsAndPrivacyNotice": { + "message": "Door Firefox Screenshots te gebruiken, gaat u akkoord met de $TERMSANDPRIVACYNOTICETERMSLINK$ en $TERMSANDPRIVACYNOTICEPRIVACYLINK$ van Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Voorwaarden" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Privacyverklaring" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/nn_NO/messages.json b/browser/extensions/screenshots/webextension/_locales/nn_NO/messages.json new file mode 100644 index 000000000000..f888721681e8 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/nn_NO/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Ta klipp og skjermbilde frå nettet og lagre dei mellombels eller permanent." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Ta eit skjermbilde" + }, + "myShotsLink": { + "message": "Mine skjermbilde" + }, + "screenshotInstructions": { + "message": "Drag eller klikk på sida for å velje ein region. Trykk på ESC for å avbryte." + }, + "saveScreenshotSelectedArea": { + "message": "Lagre" + }, + "saveScreenshotVisibleArea": { + "message": "Lagre synleg område" + }, + "saveScreenshotFullPage": { + "message": "Lagre heile sida" + }, + "cancelScreenshot": { + "message": "Avbryt" + }, + "downloadScreenshot": { + "message": "Last ned" + }, + "notificationLinkCopiedTitle": { + "message": "Lenke kopiert" + }, + "notificationLinkCopiedDetails": { + "message": "Lenka til skjermbildet ditt er kopiert til utklipp. Trykk på $META_KEY$-V for å lime inn.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "I ustand." + }, + "requestErrorDetails": { + "message": "Beklagar! Vi klarte ikkje å lagre skjermbiildet ditt. Prøv igjen seinare." + }, + "connectionErrorTitle": { + "message": "Vi kan ikkje kople til skjermbilda dine." + }, + "connectionErrorDetails": { + "message": "Kontroller internett-tilkoplinga di. Om du kan kople til internett, kan det vere eit mellombels problem med tenesta Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Vi klarte ikkje å lagre skjermbildet ditt, fordi det er eit problem med tenesta Firefox Screenshots. Prøv igjen seinare." + }, + "unshootablePageErrorTitle": { + "message": "Vi kan ikkje ta skjermbilde av sida." + }, + "unshootablePageErrorDetails": { + "message": "Dette er ikkje ei vanleg nettside, og du kan ikkje ta skjermbilde av henne." + }, + "selfScreenshotErrorTitle": { + "message": "Du kan ikkje ta skjermbilde av sida Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "Oj! Det ser ut til at Firefox Screenshots ikkje fungerer korrekt." + }, + "genericErrorDetails": { + "message": "Vi er ikkje sikre på kva som hende. Kan du prøve igjen eller ta eit bilde på ei anna side?" + }, + "tourBodyOne": { + "message": "Ta, lagre og del skjermbilde utan å forlate Firefox." + }, + "tourHeaderTwo": { + "message": "Knips akkurat det du vil" + }, + "tourBodyTwo": { + "message": "Klikk for å drage og knipse berre ein del av sida. Du kan også føre musa over for å framheve merkt område." + }, + "tourHeaderThree": { + "message": "Som du vil ha det" + }, + "tourBodyThree": { + "message": "Lagre dei tilskjerte bilda dine på nettet for enklare deling, eller last dei ned til datamaskina di. Du kan også klikke på knappen Mine skjermbilde for å finne alle bilda du har tatt." + }, + "tourHeaderFour": { + "message": "Knips vindauge eller heile sider" + }, + "tourBodyFour": { + "message": "Vel knappane i det øvre høgre hjørnet for å knipse det synlege området i vindauget eller for å knipse ei heil side." + }, + "tourSkip": { + "message": "Hopp over" + }, + "tourNext": { + "message": "Neste slide" + }, + "tourPrevious": { + "message": "Føregåande slide" + }, + "tourDone": { + "message": "Ferdig" + }, + "termsAndPrivacyNotice": { + "message": "Ved å bruke Firefox Screenshots, godtar du $TERMSANDPRIVACYNOTICETERMSLINK$ og $TERMSANDPRIVACYNOTICEPRIVACYLINK$ for Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Vilkår" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Personvernmerknad" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/pa_IN/messages.json b/browser/extensions/screenshots/webextension/_locales/pa_IN/messages.json new file mode 100644 index 000000000000..8335eec71ca2 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/pa_IN/messages.json @@ -0,0 +1,47 @@ +{ + "addonAuthorsList": { + "message": "ਮੌਜ਼ੀਲਾ " + }, + "contextMenuLabel": { + "message": "ਸਕਰੀਨ-ਸ਼ਾਟ ਲਵੋ" + }, + "myShotsLink": { + "message": "ਮੇਰੇ ਸ਼ਾਟ" + }, + "screenshotInstructions": { + "message": "ਖੇਤਰ ਨੂੰ ਚੁਣਨ ਵਾਸਤੇ ਖਿੱਚੋ ਜਾਂ ਕਲਿੱਕ ਕਰੋ। ਰੱਦ ਕਰਨ ਵਾਸਤੇ ESC ਦੱਬੋ।" + }, + "saveScreenshotSelectedArea": { + "message": "ਸੰਭਾਲੋ" + }, + "saveScreenshotVisibleArea": { + "message": "ਦਿੱਖ ਨੂੰ ਸੰਭਾਲੋ" + }, + "saveScreenshotFullPage": { + "message": "ਪੂਰੇ ਸਫ਼ੇ ਨੂੰ ਸੰਭਾਲੋ" + }, + "cancelScreenshot": { + "message": "ਰੱਦ ਕਰੋ" + }, + "downloadScreenshot": { + "message": "ਡਾਊਨਲੋਡ ਕਰੋ" + }, + "notificationLinkCopiedTitle": { + "message": "ਲਿੰਕ ਕਾਪੀ ਕੀਤਾ ਗਿਆ" + }, + "requestErrorTitle": { + "message": "ਖ਼ਰਾਬ ਹੈ।" + }, + "tourSkip": { + "message": "ਛੱਡੋ" + }, + "tourNext": { + "message": "ਅਗਲੀ ਸਲਾਈਡ" + }, + "tourPrevious": { + "message": "ਪਿਛਲੀ ਸਲਾਈਡ" + }, + "tourDone": { + "message": "ਮੁਕੰਮਲ" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/pl/messages.json b/browser/extensions/screenshots/webextension/_locales/pl/messages.json new file mode 100644 index 000000000000..8d9fb18638ff --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/pl/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Twórz wycinki i zrzuty stron internetowych i zapisuj je tymczasowo lub trwale." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Wykonaj zrzut ekranu" + }, + "myShotsLink": { + "message": "Moje zrzuty" + }, + "screenshotInstructions": { + "message": "Przeciągnij lub kliknij na stronie, aby wybrać obszar. Naciśnij klawisz Esc, aby anulować." + }, + "saveScreenshotSelectedArea": { + "message": "Zapisz" + }, + "saveScreenshotVisibleArea": { + "message": "Zapisz widoczne" + }, + "saveScreenshotFullPage": { + "message": "Zapisz całą stronę" + }, + "cancelScreenshot": { + "message": "Anuluj" + }, + "downloadScreenshot": { + "message": "Pobierz" + }, + "notificationLinkCopiedTitle": { + "message": "Skopiowano odnośnik" + }, + "notificationLinkCopiedDetails": { + "message": "Odnośnik do zrzutu został skopiowany do schowka. Naciśnij $META_KEY$-V, aby go wkleić.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Awaria." + }, + "requestErrorDetails": { + "message": "Nie można zapisać zrzutu. Spróbuj ponownie później." + }, + "connectionErrorTitle": { + "message": "Nie można połączyć się z zrzutami ekranu." + }, + "connectionErrorDetails": { + "message": "Sprawdź swoje połączenie z Internetem. Jeśli działa ono prawidłowo, to może występować tymczasowy problem z usługą Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Nie można zapisać zrzutu, ponieważ występuje problem z usługą Firefox Screenshots. Spróbuj ponownie później." + }, + "unshootablePageErrorTitle": { + "message": "Nie można wykonać zrzutu tej strony." + }, + "unshootablePageErrorDetails": { + "message": "To nie jest standardowa strona internetowa, więc nie można wykonać jej zrzutu." + }, + "selfScreenshotErrorTitle": { + "message": "Nie można wykonać zrzutu strony Firefox Screenshots." + }, + "genericErrorTitle": { + "message": "Firefox Screenshots wymknęło się spod kontroli." + }, + "genericErrorDetails": { + "message": "Nie bardzo wiemy, co się wydarzyło. Spróbujesz ponownie lub wykonasz zrzut innej strony?" + }, + "tourBodyOne": { + "message": "Wykonuj, zapisuj i udostępniaj zrzuty ekranu bez wychodzenia z Firefoksa." + }, + "tourHeaderTwo": { + "message": "Zapisuj tylko to, co potrzebujesz" + }, + "tourBodyTwo": { + "message": "Kliknij i przeciągnij, aby zapisać tylko część strony. Możesz także najechać, aby wyróżnić zaznaczony obszar." + }, + "tourHeaderThree": { + "message": "Tak, jak lubisz" + }, + "tourBodyThree": { + "message": "Zapisuj przycięte zrzuty w Internecie, aby łatwiej je udostępniać, albo pobierz je na swój komputer. Możesz też kliknąć przycisk „Moje zrzuty”, aby przeglądać wszystkie wykonane zrzuty." + }, + "tourHeaderFour": { + "message": "Zapisuj zrzuty okien lub całych stron" + }, + "tourBodyFour": { + "message": "Kliknij przycisk w górnym prawym rogu, aby zapisać obszar widoczny w oknie lub całą stronę." + }, + "tourSkip": { + "message": "Pomiń" + }, + "tourNext": { + "message": "Dalej" + }, + "tourPrevious": { + "message": "Wstecz" + }, + "tourDone": { + "message": "Zamknij" + }, + "termsAndPrivacyNotice": { + "message": "Używając Firefox Screenshots, zgadzasz się na $TERMSANDPRIVACYNOTICETERMSLINK$ i $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "warunki korzystania z usługi" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "zasady ochrony prywatności" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/pt_BR/messages.json b/browser/extensions/screenshots/webextension/_locales/pt_BR/messages.json new file mode 100644 index 000000000000..2456061379a6 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/pt_BR/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Tire clipes e capturas de tela da Web e guarde-as temporariamente ou permanentemente." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Tirar uma captura de tela" + }, + "myShotsLink": { + "message": "Minhas capturas" + }, + "screenshotInstructions": { + "message": "Arraste ou clique na página para selecionar uma área. Pressione ESC para cancelar." + }, + "saveScreenshotSelectedArea": { + "message": "Salvar" + }, + "saveScreenshotVisibleArea": { + "message": "Salvar área visível" + }, + "saveScreenshotFullPage": { + "message": "Salvar página completa" + }, + "cancelScreenshot": { + "message": "Cancelar" + }, + "downloadScreenshot": { + "message": "Baixar" + }, + "notificationLinkCopiedTitle": { + "message": "Link copiado" + }, + "notificationLinkCopiedDetails": { + "message": "O link da sua captura foi copiado para a área de transferência. Pressione $META_KEY$-V para colar.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Fora de ordem." + }, + "requestErrorDetails": { + "message": "Desculpa! Não pudemos salvar a sua captura de tela. Por favor, tente novamente mais tarde." + }, + "connectionErrorTitle": { + "message": "Não conseguimos conectar suas capturas de tela." + }, + "connectionErrorDetails": { + "message": "Por favor verifique a sua conexão com a Internet. Se consegue conecta-se à Internet, pode existir um problema temporário com o serviço capturas de tela do Firefox." + }, + "loginErrorDetails": { + "message": "Não conseguimos salvar a sua captura porque existe um problema com o serviço de capturas de tela do Firefox. Por favor tente novamente mais tarde." + }, + "unshootablePageErrorTitle": { + "message": "Não conseguimos capturar a tela nesta página." + }, + "unshootablePageErrorDetails": { + "message": "Esta não é uma página web padrão, por isso não podemos tirar uma captura de tela da mesma." + }, + "selfScreenshotErrorTitle": { + "message": "Você não pode tirar uma captura em uma página de capturas de tela do Firefox!" + }, + "genericErrorTitle": { + "message": "Uau! Algo correu mal com a capturas de tela do Firefox." + }, + "genericErrorDetails": { + "message": "Não temos certeza do que acabou de acontecer. Tentar novamente ou fazer uma captura de uma página diferente?" + }, + "tourBodyOne": { + "message": "Capture, salve e compartilhe telas sem sair do Firefox." + }, + "tourHeaderTwo": { + "message": "Capture apenas o que você quer" + }, + "tourBodyTwo": { + "message": "Clique e arraste para capturar apenas uma parte de uma página. Você também pode passar o mouse para realçar sua seleção." + }, + "tourHeaderThree": { + "message": "Como você quiser" + }, + "tourBodyThree": { + "message": "Salve as suas capturas na Web para compartilhar mais facilmente ou baixe-as no seu computador. Você também pode clicar no botão Minhas capturas para encontras todas as capturas que tirou." + }, + "tourHeaderFour": { + "message": "Capture janelas ou páginas inteiras" + }, + "tourBodyFour": { + "message": "Selecione os botões no canto superior direito para capturar a área visível na janela ou capturar uma página inteira." + }, + "tourSkip": { + "message": "Pular" + }, + "tourNext": { + "message": "Próximo slide" + }, + "tourPrevious": { + "message": "Slide anterior" + }, + "tourDone": { + "message": "Concluir" + }, + "termsAndPrivacyNotice": { + "message": "Usando Firefox Screenshots, você concorda com os $TERMSANDPRIVACYNOTICETERMSLINK$ e $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Termos" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Política de privacidade" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/pt_PT/messages.json b/browser/extensions/screenshots/webextension/_locales/pt_PT/messages.json new file mode 100644 index 000000000000..b3a7013143cc --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/pt_PT/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Tire clipes e capturas de ecrã da Web e guarde-as temporariamente ou permanentemente." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Tirar uma captura de ecrã" + }, + "myShotsLink": { + "message": "Minhas capturas" + }, + "screenshotInstructions": { + "message": "Arraste ou clique na página para selecionar uma região. Pressione ESC para cancelar." + }, + "saveScreenshotSelectedArea": { + "message": "Guardar" + }, + "saveScreenshotVisibleArea": { + "message": "Guardar visível" + }, + "saveScreenshotFullPage": { + "message": "Guardar página inteira" + }, + "cancelScreenshot": { + "message": "Cancelar" + }, + "downloadScreenshot": { + "message": "Descarregar" + }, + "notificationLinkCopiedTitle": { + "message": "Ligação copiada" + }, + "notificationLinkCopiedDetails": { + "message": "A ligação à sua captura foi copiada para a área de transferência. Pressione $META_KEY$-V para colar.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Fora de serviço." + }, + "requestErrorDetails": { + "message": "Desculpe! Não conseguimos guardar a sua captura. Por favor tente novamente mais tarde." + }, + "connectionErrorTitle": { + "message": "Não conseguimos ligar às suas capturas de ecrã." + }, + "connectionErrorDetails": { + "message": "Por favor verifique a sua ligação à Internet. Se consegue ligar-se à Internet, pode existir um problema temporário com o serviço Capturas de ecrã Firefox." + }, + "loginErrorDetails": { + "message": "Não conseguimos guardar a sua captura porque existe um problema com o serviço Capturas de ecrã Firefox. Por favor tente novamente mais tarde." + }, + "unshootablePageErrorTitle": { + "message": "Não conseguimos capturar o ecrã nesta página." + }, + "unshootablePageErrorDetails": { + "message": "Esta não é uma página web padrão, por isso não podemos tirar uma captura de ecrã da mesma." + }, + "selfScreenshotErrorTitle": { + "message": "Não pode tirar uma captura duma página Capturas de ecrã Firefox!" + }, + "genericErrorTitle": { + "message": "Uau! Algo correu mal com o Capturas de ecrã Firefox." + }, + "genericErrorDetails": { + "message": "Não temos a certeza do que acabou de acontecer. Tentar novamente ou tirar uma captura de uma página diferente?" + }, + "tourBodyOne": { + "message": "Tire, guarde, e partilhe capturas de ecrã sem sair do Firefox." + }, + "tourHeaderTwo": { + "message": "Capture aquilo mesmo que pretende" + }, + "tourBodyTwo": { + "message": "Clique e arraste para capturar apenas uma porção de uma página. Pode também pairar para destacar a sua seleção." + }, + "tourHeaderThree": { + "message": "Como gosta" + }, + "tourBodyThree": { + "message": "Guarde as suas capturas na Web para partilhar mais facilmente, ou descarregue-as para o seu computador. Pode também clicar no botão Minhas capturas para encontras todas as capturas que tirou." + }, + "tourHeaderFour": { + "message": "Capture janelas ou páginas inteiras" + }, + "tourBodyFour": { + "message": "Selecione os botões no canto superior direito para capturar a área visível na janela ou capturar uma página inteira." + }, + "tourSkip": { + "message": "Saltar" + }, + "tourNext": { + "message": "Diapositivo seguinte" + }, + "tourPrevious": { + "message": "Diapositivo anterior" + }, + "tourDone": { + "message": "Feito" + }, + "termsAndPrivacyNotice": { + "message": "Ao utilizar o Firefox Screenshots, você concorda com os $TERMSANDPRIVACYNOTICETERMSLINK$ e com a $TERMSANDPRIVACYNOTICEPRIVACYLINK$ do Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Termos" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Nota de privacidade" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/rm/messages.json b/browser/extensions/screenshots/webextension/_locales/rm/messages.json new file mode 100644 index 000000000000..e3e95302a8a9 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/rm/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Fai maletgs da visur dal web ed als memorisescha temporarmain u permanentamain." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Far in maletg dal visur" + }, + "myShotsLink": { + "message": "Mes maletgs da visur" + }, + "screenshotInstructions": { + "message": "Tira u clicca sin la pagina per tscherner ina regiun. Smatga ESC per interrumper." + }, + "saveScreenshotSelectedArea": { + "message": "Memorisar" + }, + "saveScreenshotVisibleArea": { + "message": "Memorisar la regiun visibla" + }, + "saveScreenshotFullPage": { + "message": "Memorisar la pagina cumpletta" + }, + "cancelScreenshot": { + "message": "Interrumper" + }, + "downloadScreenshot": { + "message": "Telechargiar" + }, + "notificationLinkCopiedTitle": { + "message": "Copià la colliaziun" + }, + "notificationLinkCopiedDetails": { + "message": "La colliaziun tar tes maletg da visur è vegnida copiada en l'archiv provisoric. Smatga $META_KEY$-V per l'encollar.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Ord funcziun." + }, + "requestErrorDetails": { + "message": "Perstgisa! I è actualmain betg pussibel da memorisar tes maletg da visur. Emprova p.pl. pli tard anc ina giada." + }, + "connectionErrorTitle": { + "message": "Impussibel da connectar a tes maletgs da visur." + }, + "connectionErrorDetails": { + "message": "Controllescha tia connexiun a l'internet. Sche ti has access a l'internet ha il servetsch da Firefox Screenshots forsa temporarmain in problem." + }, + "loginErrorDetails": { + "message": "Impussibel da memorisar tes maletg da virus perquai ch'i dat in problem un il servetsch da Firefox Screenshots. Emprova p.pl. pli tard." + }, + "unshootablePageErrorTitle": { + "message": "Impussibel da far in maletg da visur da questa pagina." + }, + "unshootablePageErrorDetails": { + "message": "Quai n'è betg ina pagina web da standard, perquai n'èsi betg pussaivel da far in maletg da visur dad ella." + }, + "selfScreenshotErrorTitle": { + "message": "Impussibel da far in maletg da visur dad ina pagina da Firefox Screenshots." + }, + "genericErrorTitle": { + "message": "Oh dieu! Firefox Screenshots ha il singlut." + }, + "genericErrorDetails": { + "message": "Nus na savain betg tge ch'è gist capità. Vuls empruvar anc ina giada, forsa cun in'autra pagina?" + }, + "tourBodyOne": { + "message": "Far, memorisar e cundivider maletgs da visur senza bandunar Firefox." + }, + "tourHeaderTwo": { + "message": "Far maletgs da visur da tut che vi vuls" + }, + "tourBodyTwo": { + "message": "Clicca e tira per far in maletg da be ina part da la pagina. Ti pos posiziunar la mieur sur la selecziun per la relevar." + }, + "tourHeaderThree": { + "message": "Co che ti prefereschas" + }, + "tourBodyThree": { + "message": "Memorisescha ils maletgs da visur en il web per als pudair cundivider u telechargiar sin tes computer. Ti pos era cliccar sin il buttun «Mes maletgs da visur» per vesair tut ils maletgs dal visur che ti has fatg." + }, + "tourHeaderFour": { + "message": "Far maletgs da fanestras u paginas cumplettas" + }, + "tourBodyFour": { + "message": "Tscherna il buttun sura dretg per far in maletg da la part visibla en la fanestra u per far in maletg da la pagina cumpletta." + }, + "tourSkip": { + "message": "Sursiglir" + }, + "tourNext": { + "message": "Proxim pass" + }, + "tourPrevious": { + "message": "Ultim pass" + }, + "tourDone": { + "message": "Finì" + }, + "termsAndPrivacyNotice": { + "message": "Cun utilisar Firefox Screenshots accepteschas ti $TERMSANDPRIVACYNOTICETERMSLINK$ e $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "las cundiziuns d'utilisaziun" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "la decleraziun da protecziun da datas" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/ru/messages.json b/browser/extensions/screenshots/webextension/_locales/ru/messages.json new file mode 100644 index 000000000000..4eabd2f0abd7 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/ru/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Делайте вырезки и скриншоты из Интернета и сохраняйте их временно или навсегда." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Сделать скриншот" + }, + "myShotsLink": { + "message": "Мои снимки" + }, + "screenshotInstructions": { + "message": "Потяните мышью или щёлкните по странице, чтобы выбрать область. Нажмите ESC для отмены." + }, + "saveScreenshotSelectedArea": { + "message": "Сохранить" + }, + "saveScreenshotVisibleArea": { + "message": "Сохранить видимую область" + }, + "saveScreenshotFullPage": { + "message": "Сохранить всю страницу" + }, + "cancelScreenshot": { + "message": "Отмена" + }, + "downloadScreenshot": { + "message": "Загрузить" + }, + "notificationLinkCopiedTitle": { + "message": "Ссылка скопирована" + }, + "notificationLinkCopiedDetails": { + "message": "Ссылка на ваш снимок была скопирована в буфер обмена. Нажмите $META_KEY$-V для её вставки.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Произошла ошибка." + }, + "requestErrorDetails": { + "message": "Извините! Мы не смогли сохранить ваш снимок. Пожалуйста, попробуйте позже." + }, + "connectionErrorTitle": { + "message": "Мы не смогли получить доступ к вашим скриншотам." + }, + "connectionErrorDetails": { + "message": "Пожалуйста, проверьте соединение с Интернетом. Если у вам удаётся войти в Интернет, то возможно, возникла временная проблема со службой Скриншотов Firefox." + }, + "loginErrorDetails": { + "message": "Мы не можем сохранить ваш снимок, так как возникла проблема с сервисом Скриншотов Firefox. Пожалуйста, попробуйте позже." + }, + "unshootablePageErrorTitle": { + "message": "Мы не можем сделать скриншот этой страницы." + }, + "unshootablePageErrorDetails": { + "message": "Так как это не обычная веб-страница, мы не сможем сделать её скриншот." + }, + "selfScreenshotErrorTitle": { + "message": "Вы не можете сделать скриншот страницы Скриншотов Firefox." + }, + "genericErrorTitle": { + "message": "Ого! Скриншоты Firefox вышли из строя." + }, + "genericErrorDetails": { + "message": "Мы не уверены, в чём проблема. Попробуете ещё раз или сделаете снимок другой страницы?" + }, + "tourBodyOne": { + "message": "Делайте, сохраняйте и делитесь скриншотами прямо в Firefox." + }, + "tourHeaderTwo": { + "message": "Делайте снимки чего угодно" + }, + "tourBodyTwo": { + "message": "Щелкните и потяните мышью для захвата части страницы. Вы также можете навести курсор мыши для подсветки выбранной области." + }, + "tourHeaderThree": { + "message": "Как вам нравится" + }, + "tourBodyThree": { + "message": "Сохраняйте свои снимки в Интернете, чтобы легко ими делиться, или загружайте их на свой компьютер. Вы также можете просмотреть все сохранённые снимки, нажав на кнопку Мои снимки." + }, + "tourHeaderFour": { + "message": "Захватывайте окна или целые страницы" + }, + "tourBodyFour": { + "message": "С помощью кнопок в верхнем правом углу выбирайте захват видимой области окна или страницы целиком." + }, + "tourSkip": { + "message": "Пропустить" + }, + "tourNext": { + "message": "Следующий слайд" + }, + "tourPrevious": { + "message": "Предыдущий слайд" + }, + "tourDone": { + "message": "Готово" + }, + "termsAndPrivacyNotice": { + "message": "Используя Firefox Screenshots, вы соглашаетесь с его $TERMSANDPRIVACYNOTICETERMSLINK$ и $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Условиями использования" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Уведомлением о приватности" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/sk/messages.json b/browser/extensions/screenshots/webextension/_locales/sk/messages.json new file mode 100644 index 000000000000..b6de0435051c --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/sk/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Vytvorte si snímky obrazovky na webe a uložte si ich dočasne či navždy." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Urobiť snímku obrazovky" + }, + "myShotsLink": { + "message": "Moje snímky" + }, + "screenshotInstructions": { + "message": "Potiahnutím alebo kliknutím si vyberte oblasť, ktorú chcete zachytiť. Výber zrušíte stlačením klávesa ESC." + }, + "saveScreenshotSelectedArea": { + "message": "Uložiť" + }, + "saveScreenshotVisibleArea": { + "message": "Uložiť viditeľnú časť" + }, + "saveScreenshotFullPage": { + "message": "Uložiť celú stránku" + }, + "cancelScreenshot": { + "message": "Zrušiť" + }, + "downloadScreenshot": { + "message": "Prevziať" + }, + "notificationLinkCopiedTitle": { + "message": "Odkaz bol skopírovaný" + }, + "notificationLinkCopiedDetails": { + "message": "Odkaz na vašu snímku bol skopírovaný do schránky. Stlačením $META_KEY$-V ho prilepíte.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Mimo prevádzky." + }, + "requestErrorDetails": { + "message": "Mrzí nás to, no nemôžeme uložiť vašu snímku. Skúste to, prosím, neskôr." + }, + "connectionErrorTitle": { + "message": "Nemôžeme sa spojiť s vašimi snímkami." + }, + "connectionErrorDetails": { + "message": "Prosím, skontrolujte svoje internetové pripojenie. Ak ste pripojení na internet, môže ísť o dočasný problém na strane služby Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Nemohli sme uložiť vašu snímku, pretože nastal problém so službou Firefox Screenshots. Skúste to, prosím, neskôr." + }, + "unshootablePageErrorTitle": { + "message": "Túto stránku nemôžeme zachytiť." + }, + "unshootablePageErrorDetails": { + "message": "Toto nie je štandardná webová stránka, takže z nej nemôžeme vytvoriť snímku obrazovky." + }, + "selfScreenshotErrorTitle": { + "message": "Nemôžete vytvoriť snímku obrazovky stránky Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "Ups! Služba Firefox Screenshots prestala pracovať." + }, + "genericErrorDetails": { + "message": "Nie sme si istí, čo sa práve stalo. Chcete tú skúsiť znova alebo chcete vytvoriť snímku inej stránky?" + }, + "tourBodyOne": { + "message": "Tvorte, ukladajte a zdieľajte snímky obrazovky bez toho, aby ste museli opustiť Firefox." + }, + "tourHeaderTwo": { + "message": "Zachyťte to, čo chcete" + }, + "tourBodyTwo": { + "message": "Ak chcete zachytiť časť stránky, urobíte to kliknutím a potiahnutím. Váš výber zvýrazníte tak, že sa naň presuniete myšou." + }, + "tourHeaderThree": { + "message": "Tak ako to chcete" + }, + "tourBodyThree": { + "message": "Uložte si orezanú snímku na web, aby ste ju mohli ľahšie zdieľať alebo si ju prevziať do počítača. Môžete si taktiež pozrieť všetky vaše snímky, stačí ak kliknete na tlačidlo Moje snímky." + }, + "tourHeaderFour": { + "message": "Zachyťte okná alebo celé webové stránky" + }, + "tourBodyFour": { + "message": "Kliknutím na tlačidlo v pravom hornom rohu môžete zachytiť viditeľnú časť stránky. Pomocou ďalšieho tlačidla zachytíte celú stránku." + }, + "tourSkip": { + "message": "Preskočiť" + }, + "tourNext": { + "message": "Ďalšia snímka" + }, + "tourPrevious": { + "message": "Predchádzajúca snímka" + }, + "tourDone": { + "message": "Hotovo" + }, + "termsAndPrivacyNotice": { + "message": "Používaním služby Firefox Screenshots vyjadrujete súhlas s $TERMSANDPRIVACYNOTICETERMSLINK$ služby Screenshots a so $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "podmienkami" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "zásadami ochrany súkromia" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/sl/messages.json b/browser/extensions/screenshots/webextension/_locales/sl/messages.json new file mode 100644 index 000000000000..c9fb9fe3abb8 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/sl/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Zajemajte posnetke zaslona s spleta ter jih shranite začasno ali trajno." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Zajemi posnetek zaslona" + }, + "myShotsLink": { + "message": "Moji posnetki" + }, + "screenshotInstructions": { + "message": "Povlecite ali kliknite na strani za izbiro območja. Pritisnite ESC za preklic." + }, + "saveScreenshotSelectedArea": { + "message": "Shrani" + }, + "saveScreenshotVisibleArea": { + "message": "Shrani vidno" + }, + "saveScreenshotFullPage": { + "message": "Shrani celotno stran" + }, + "cancelScreenshot": { + "message": "Prekliči" + }, + "downloadScreenshot": { + "message": "Prenesi" + }, + "notificationLinkCopiedTitle": { + "message": "Povezava kopirana" + }, + "notificationLinkCopiedDetails": { + "message": "Povezava do vašega posnetka zaslona je bila kopirana v odložišče. Pritisnite $META_KEY$-V, da jo prilepite.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Ne dela." + }, + "requestErrorDetails": { + "message": "Vašega posnetka nismo uspeli shraniti. Poskusite znova kasneje." + }, + "connectionErrorTitle": { + "message": "Ne moremo vzpostaviti povezave do vaših posnetkov." + }, + "connectionErrorDetails": { + "message": "Preverite svojo internetno povezavo. V kolikor povezava deluje, gre morda za začasno težavo s storitvijo Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Ne moremo shraniti vašega posnetka, ker je prišlo do težave s storitvijo Firefox Screenshots. Poskusite znova kasneje." + }, + "unshootablePageErrorTitle": { + "message": "Ne moremo zajeti posnetka te strani." + }, + "unshootablePageErrorDetails": { + "message": "To ni običajna spletna stran, zato ne morete zajeti njenega zaslonskega posnetka." + }, + "selfScreenshotErrorTitle": { + "message": "Posnetka strani Firefox Screenshots ni mogoče zajeti!" + }, + "genericErrorTitle": { + "message": "Uf! Firefox Screenshots se je pokvaril." + }, + "genericErrorDetails": { + "message": "Ne vemo točno, kaj se je pravkar zgodilo. Bi radi poskusili znova ali pa zajeli posnetek kakšne druge strani?" + }, + "tourBodyOne": { + "message": "Zajemite, shranite in delite zaslonske posnetke, ne da bi zapustili Firefox." + }, + "tourHeaderTwo": { + "message": "Zajemite to, kar hočete" + }, + "tourBodyTwo": { + "message": "Kliknite in povlecite, če želite zajeti samo del strani. Svojo izbiro lahko tudi poudarite, tako da preko nje povlečete miškin kazalec." + }, + "tourHeaderThree": { + "message": "Kot vi želite" + }, + "tourBodyThree": { + "message": "Shranite obrezane posnetke na splet za lažje deljenje ali jih prenesite na svoj računalnik. Vse zajete posnetke lahko najdete s klikom na gumb My Shots." + }, + "tourHeaderFour": { + "message": "Zajemite okna ali celotne strani" + }, + "tourBodyFour": { + "message": "V zgornjem desnem kotu izberite gumb za zajem vidnega območja v oknu ali celotne strani." + }, + "tourSkip": { + "message": "Preskoči" + }, + "tourNext": { + "message": "Naslednji diapozitiv" + }, + "tourPrevious": { + "message": "Prejšnji diapozitiv" + }, + "tourDone": { + "message": "Končano" + }, + "termsAndPrivacyNotice": { + "message": "Z uporabo razširitve Firefox Screenshots se strinjate s $TERMSANDPRIVACYNOTICETERMSLINK$ in $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "pogoji" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "obvestilom o zasebnosti" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/sq/messages.json b/browser/extensions/screenshots/webextension/_locales/sq/messages.json new file mode 100644 index 000000000000..b5a73be9510f --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/sq/messages.json @@ -0,0 +1,14 @@ +{ + "saveScreenshotSelectedArea": { + "message": "Ruaje" + }, + "cancelScreenshot": { + "message": "Anuloje" + }, + "downloadScreenshot": { + "message": "Shkarkoje" + }, + "notificationLinkCopiedTitle": { + "message": "Lidhja u Kopjua" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/sr/messages.json b/browser/extensions/screenshots/webextension/_locales/sr/messages.json new file mode 100644 index 000000000000..dd9d29f1f13e --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/sr/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Бележите снимке екрана са веба и сачувајте их привремено или трајно." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Усликајте екран" + }, + "myShotsLink": { + "message": "Моји снимци" + }, + "screenshotInstructions": { + "message": "Превуците или кликните на страници да изаберете област. Притисните ESC да прекинете." + }, + "saveScreenshotSelectedArea": { + "message": "Сачувај" + }, + "saveScreenshotVisibleArea": { + "message": "Сачувај видљиво" + }, + "saveScreenshotFullPage": { + "message": "Сачувај целу страницу" + }, + "cancelScreenshot": { + "message": "Откажи" + }, + "downloadScreenshot": { + "message": "Преузми" + }, + "notificationLinkCopiedTitle": { + "message": "Веза копирана" + }, + "notificationLinkCopiedDetails": { + "message": "Веза коју сте забележили је копирана у бележницу. Притисните $META_KEY$-V да налепите.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Не ради." + }, + "requestErrorDetails": { + "message": "Жао нам је! Нисмо могли сачувати ваш снимак. Покушајте поново касније." + }, + "connectionErrorTitle": { + "message": "Не можемо се повезати на ваше снимке." + }, + "connectionErrorDetails": { + "message": "Проверите вашу интернет конекцију. Ако можете да се конектујете, онда можда постоји привремени проблем са Firefox Screenshots-ом." + }, + "loginErrorDetails": { + "message": "Нисмо могли сачувати ваш снимак јер постоји проблем са Firefox Screenshots-ом. Покушајте поново касније." + }, + "unshootablePageErrorTitle": { + "message": "Не можемо забележити снимак ове странице." + }, + "unshootablePageErrorDetails": { + "message": "Ово није стандардна веб страница, тако да не можете забележити њен снимак." + }, + "selfScreenshotErrorTitle": { + "message": "Не можете усликати Firefox Screenshots страницу!" + }, + "genericErrorTitle": { + "message": "Ајој! Firefox Screenshots је пошашавио." + }, + "genericErrorDetails": { + "message": "Нисмо сигурни шта се управо догодило. Желите ли покушати поново или да усликате другачију страницу?" + }, + "tourBodyOne": { + "message": "Забележите, сачувајте и поделите снимке екрана без напуштања Firefox-а." + }, + "tourHeaderTwo": { + "message": "Усликајте баш оно што желите" + }, + "tourBodyTwo": { + "message": "Кликните и превуците да усликате само део странице. Такође можете означити вашу селекцију." + }, + "tourHeaderThree": { + "message": "Као што волите" + }, + "tourBodyThree": { + "message": "Сачувајте ваш исечени снимак на веб ради лакшег дељења или преузимања на ваш рачунар. Такође можете кликнути на дугме \"Моји снимци\" да пронађете све ваше снимке." + }, + "tourHeaderFour": { + "message": "Усликајте прозоре или целе странице" + }, + "tourBodyFour": { + "message": "Изаберите дугмад у горњем десном углу да усликате видљиве делове прозора или да усликате целу страницу." + }, + "tourSkip": { + "message": "Прескочи" + }, + "tourNext": { + "message": "Следећи слајд" + }, + "tourPrevious": { + "message": "Претходни слајд" + }, + "tourDone": { + "message": "Готово" + }, + "termsAndPrivacyNotice": { + "message": "Коришћењем услуге Firefox Screenshots, слажете се са $TERMSANDPRIVACYNOTICETERMSLINK$ и $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "условима" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "обавештењем о приватности" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/sv_SE/messages.json b/browser/extensions/screenshots/webextension/_locales/sv_SE/messages.json new file mode 100644 index 000000000000..dd0ee4842784 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/sv_SE/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Ta klipp och skärmbilder från webben och spara dem tillfälligt eller permanent." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Ta en skärmbild" + }, + "myShotsLink": { + "message": "Mina skärmbilder" + }, + "screenshotInstructions": { + "message": "Dra eller klicka på sidan för att välja en region. Tryck på ESC för att avbryta." + }, + "saveScreenshotSelectedArea": { + "message": "Spara" + }, + "saveScreenshotVisibleArea": { + "message": "Spara synligt område" + }, + "saveScreenshotFullPage": { + "message": "Spara hela sidan" + }, + "cancelScreenshot": { + "message": "Avbryt" + }, + "downloadScreenshot": { + "message": "Ladda ner" + }, + "notificationLinkCopiedTitle": { + "message": "Länk kopierad" + }, + "notificationLinkCopiedDetails": { + "message": "Länken till din skärmbild har kopierats till urklipp. Tryck på $META_KEY$-V för att klistra in.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Ur funktion." + }, + "requestErrorDetails": { + "message": "Förlåt! Vi kunde inte spara din skärmbild. Försök igen senare." + }, + "connectionErrorTitle": { + "message": "Vi kan inte ansluta till dina skärmbilder." + }, + "connectionErrorDetails": { + "message": "Kontrollera din internetanslutning. Om du kan ansluta till internet, kan det vara ett tillfälligt problem med tjänsten Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Vi kunde inte spara din skärmbild eftersom det finns ett problem med tjänsten Firefox Screenshots. Försök igen senare." + }, + "unshootablePageErrorTitle": { + "message": "Vi kan inte ta en skärmbild av sidan." + }, + "unshootablePageErrorDetails": { + "message": "Detta är inte en vanlig webbsida, så du kan inte ta en skärmbild av den." + }, + "selfScreenshotErrorTitle": { + "message": "Du kan inte ta en skärmbild av sidan Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "Oj! Firefox Screenshots verkar inte fungera korrekt." + }, + "genericErrorDetails": { + "message": "Vi är inte säkra på vad som just hände. Kan du försöka igen eller ta en bild på en annan sida?" + }, + "tourBodyOne": { + "message": "Ta, spara, och dela skärmbilder utan att lämna Firefox." + }, + "tourHeaderTwo": { + "message": "Fånga precis vad du vill" + }, + "tourBodyTwo": { + "message": "Klicka och dra för att fånga bara en del av en sida. Du kan också hovra för att markera ditt val." + }, + "tourHeaderThree": { + "message": "Som du vill ha det" + }, + "tourBodyThree": { + "message": "Spara dina beskurna bilder till webben för enklare delning, eller hämta dem till datorn. Du kan också klicka på knappen Mina skärmbilder för att hitta alla bilder du tagit." + }, + "tourHeaderFour": { + "message": "Fånga fönster eller hela sidor" + }, + "tourBodyFour": { + "message": "Välj knapparna i det övre högra hörnet för att fånga det synliga området i fönstret eller för att fånga en hel sida." + }, + "tourSkip": { + "message": "Hoppa över" + }, + "tourNext": { + "message": "Nästa sida" + }, + "tourPrevious": { + "message": "Föregående sida" + }, + "tourDone": { + "message": "Färdig" + }, + "termsAndPrivacyNotice": { + "message": "Genom att använda Firefox Screenshots, godkänner du $TERMSANDPRIVACYNOTICETERMSLINK$ och $TERMSANDPRIVACYNOTICEPRIVACYLINK$ för Screenshots.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Villkor" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Sekretesspolicy" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/th/messages.json b/browser/extensions/screenshots/webextension/_locales/th/messages.json new file mode 100644 index 000000000000..6cfff34d86f4 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/th/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "จับภาพหน้าจอจากเว็บและบันทึกไว้ชั่วคราวหรือถาวร" + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "จับภาพหน้าจอ" + }, + "myShotsLink": { + "message": "ภาพของฉัน" + }, + "screenshotInstructions": { + "message": "ลากหรือคลิกหน้าเว็บเพื่อเลือกบริเวณ กด ESC เพื่อยกเลิก" + }, + "saveScreenshotSelectedArea": { + "message": "บันทึก" + }, + "saveScreenshotVisibleArea": { + "message": "บันทึกส่วนที่เห็น" + }, + "saveScreenshotFullPage": { + "message": "บันทึกเต็มหน้า" + }, + "cancelScreenshot": { + "message": "ยกเลิก" + }, + "downloadScreenshot": { + "message": "ดาวน์โหลด" + }, + "notificationLinkCopiedTitle": { + "message": "คัดลอกลิงก์แล้ว" + }, + "notificationLinkCopiedDetails": { + "message": "คัดลอกลิงก์ภาพของไว้ในคลิปบอร์ดแล้ว กด $META_KEY$-V เพื่อวาง", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "ใช้งานไม่ได้" + }, + "requestErrorDetails": { + "message": "ขออภัย! เราไม่สามารถบันทึกภาพของคุณได้ โปรดลองอีกครั้งหลังจากนี้" + }, + "connectionErrorTitle": { + "message": "เราเชื่อมต่อภาพหน้าจอของคุณไม่ได้" + }, + "connectionErrorDetails": { + "message": "กรุณาตรวจสอบการเชื่อมต่ออินเทอร์เน็ต หากคุณสามารถเชื่อมต่อกับอินเทอร์เน็ต บริการ Firefox Screenshots อาจมีปัญหาชั่วคราว " + }, + "loginErrorDetails": { + "message": "เราไม่สามารถบันทึกภาพได้เพราะมีปัญหากับบริการ Firefox Screenshots โปรดลองใหม่ภายหลัง" + }, + "unshootablePageErrorTitle": { + "message": "เราไม่สามารถจับภาพหน้าจอหน้านี้" + }, + "unshootablePageErrorDetails": { + "message": "นี่ไม่ใช่หน้าเว็บมาตรฐานดังนั้นคุณไม่สามารถจับภาพได้" + }, + "selfScreenshotErrorTitle": { + "message": "คุณไม่สามารถจับภาพหน้าจอของหน้า Firefox Screenshots" + }, + "genericErrorTitle": { + "message": "โอ๊ย! Firefox Screenshots รวน" + }, + "genericErrorDetails": { + "message": "เราไม่แน่ใจว่าเกิดอะไรขึ้น โปรดลองอีกครั้งหรือจับภาพของหน้าอื่น" + }, + "tourBodyOne": { + "message": "จับ บันทึกและแบ่งปันภาพหน้าจอโดยที่ไม่ต้องออกจาก Firefox" + }, + "tourHeaderTwo": { + "message": "จับภาพตามที่คุณต้องการ" + }, + "tourBodyTwo": { + "message": "คลิกหรือลากเพื่อจับภาพเฉพาะบางส่วนของหน้าเว็บ คุณสามารถเลื่อนมาชี้เพื่อเน้นภาพส่วนที่คุณเลือก" + }, + "tourHeaderThree": { + "message": "ตามที่คุณโปรด" + }, + "tourBodyThree": { + "message": "บันทึกและครอปภาพลงในเว็บเพื่อให้แบ่งปันได้ง่าย หรือดาวน์โหลดลงคอมพิวเตอร์ของคุณ คุณยังสามารถคลิกที่ปุ่มภาพของฉันเพื่อที่จะหาภาพที่คุณจับไว้" + }, + "tourHeaderFour": { + "message": "จับภาพหน้าต่างหรือทั้งหน้า" + }, + "tourBodyFour": { + "message": "กดปุ่มด้านบนขวาเพื่อจับภาพบริเวณที่มองเห็นในหน้าต่างหรือทั้งหน้าเว็บ" + }, + "tourSkip": { + "message": "ข้าม" + }, + "tourNext": { + "message": "ภาพนิ่งถัดไป" + }, + "tourPrevious": { + "message": "ภาพนิ่งก่อนหน้า" + }, + "tourDone": { + "message": "เสร็จสิ้น" + }, + "termsAndPrivacyNotice": { + "message": "เพื่อใช้ Firefox Screenshots คุณยอมรับ $TERMSANDPRIVACYNOTICETERMSLINK$ และ $TERMSANDPRIVACYNOTICEPRIVACYLINK$ ของ Screenshots", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "ข้อกำหนด" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "ประกาศความเป็นส่วนตัว" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/tl/messages.json b/browser/extensions/screenshots/webextension/_locales/tl/messages.json new file mode 100644 index 000000000000..c49fdd09b5f9 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/tl/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Dalhin ang clip at mga screenshot mula sa Web at i-save ang mga ito pansamantala o permanente." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Kumuha ng Screenshot" + }, + "myShotsLink": { + "message": "Aking Shots" + }, + "screenshotInstructions": { + "message": "I-drag o i-click sa pahina upang pumili ng rehiyon. Pindutin ang ESC upang kanselahin." + }, + "saveScreenshotSelectedArea": { + "message": "I-save" + }, + "saveScreenshotVisibleArea": { + "message": "I-save na nakikita" + }, + "saveScreenshotFullPage": { + "message": "I-save ang buong pahina" + }, + "cancelScreenshot": { + "message": "Kanselahin" + }, + "downloadScreenshot": { + "message": "Download" + }, + "notificationLinkCopiedTitle": { + "message": "Kinopya ang Link" + }, + "notificationLinkCopiedDetails": { + "message": "Ang link na ito sa iyong shot ay kinopya sa clipboard. Pindutin $META_KEY$-V i-paste.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Mula sa pagkakasunod-sunod." + }, + "requestErrorDetails": { + "message": "Paumanhin! Hindi namin mai-save ang iyong mga shot. Subukang muli mamaya." + }, + "connectionErrorTitle": { + "message": "Hindi namin maaaring i-kunekta sa iyong mga screenshot." + }, + "connectionErrorDetails": { + "message": "Mangyaring suriin ang iyong koneksyon sa Internet. Kung ikaw ay kumonekta sa Internet, maaaring may isang pansamantalang problema sa serbisyo Firefox screenshot." + }, + "loginErrorDetails": { + "message": "Hindi namin mai-save ang iyong mga shot dahil may problema sa serbisyo Firefox screenshot. Subukang muli mamaya." + }, + "unshootablePageErrorTitle": { + "message": "Hindi namin maaaring screenshot pahinang ito." + }, + "unshootablePageErrorDetails": { + "message": "Ito ay hindi isang standard na Web page, kaya hindi ka maaaring kumuha ng isang screenshot ng mga ito." + }, + "selfScreenshotErrorTitle": { + "message": "Hindi ka maaaring kumuha ng isang shot ng isang pahina ng Firefox screenshot!" + }, + "genericErrorTitle": { + "message": "Whoa! Nagiging magulo ang Firefox screenshot." + }, + "genericErrorDetails": { + "message": "Hindi kami sigurado kung ano ang nangyari. Pag-aalaga upang subukang muli o kumuha ng isang shot ng isang iba't ibang mga pahina?" + }, + "tourBodyOne": { + "message": "Dumaan, i-save, at ibahagi ang mga screenshot nang hindi umaalis sa Firefox." + }, + "tourHeaderTwo": { + "message": "Kunan Kung Ano Ang Gusto Mo" + }, + "tourBodyTwo": { + "message": "I-click at i-drag upang makuha lamang ang isang bahagi ng isang pahina. Maaari mo ring i-hover upang i-highlight ang iyong pagpili." + }, + "tourHeaderThree": { + "message": "Bilang Nagustuhan Mo ito" + }, + "tourBodyThree": { + "message": "I-save ang iyong crop shot sa Web para sa madaling pagbabahagi, o i-download ito sa iyong computer. Maaari mo ring i-click sa pindutan ng My Shots upang mahanap ang lahat ng mga pag-shot na kinunan mo." + }, + "tourHeaderFour": { + "message": "I-capture ang Windows o Buong Pahina" + }, + "tourBodyFour": { + "message": "Piliin ang pindutan sa kanang itaas upang makuha ang nakikitang lugar sa window o upang makuha ang isang buong pahina." + }, + "tourSkip": { + "message": "Laktawan" + }, + "tourNext": { + "message": "Susunod na Slide" + }, + "tourPrevious": { + "message": "Nakaraan na Slide" + }, + "tourDone": { + "message": "Tapos" + }, + "termsAndPrivacyNotice": { + "message": "Sa pamamagitan ng paggamit ng Firefox screenshot, sumasang-ayon ka sa mga screenshot $TERMSANDPRIVACYNOTICETERMSLINK$ at $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Mga tuntunin" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Abiso sa Privacy" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/tr/messages.json b/browser/extensions/screenshots/webextension/_locales/tr/messages.json new file mode 100644 index 000000000000..ea5f9e773094 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/tr/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Web sayfalarının ekran görüntülerini alın, ister geçici ister kalıcı olarak kaydedin." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Ekran görüntüsü al" + }, + "myShotsLink": { + "message": "Ekran görüntülerim" + }, + "screenshotInstructions": { + "message": "Bir bölgeyi seçmek için işaretçiyi sürükleyin veya tıklayın. İptal etmek için ESC tuşuna basın." + }, + "saveScreenshotSelectedArea": { + "message": "Kaydet" + }, + "saveScreenshotVisibleArea": { + "message": "Görünür alanı kaydet" + }, + "saveScreenshotFullPage": { + "message": "Tüm sayfayı kaydet" + }, + "cancelScreenshot": { + "message": "Vazgeç" + }, + "downloadScreenshot": { + "message": "İndir" + }, + "notificationLinkCopiedTitle": { + "message": "Bağlantı kopyalandı" + }, + "notificationLinkCopiedDetails": { + "message": "Ekran görüntünüzün bağlantısı panoya kopyalandı. Yapıştırmak için $META_KEY$-V tuşlarına basabilirsiniz.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Arıza var." + }, + "requestErrorDetails": { + "message": "Ekran görüntünüzü kaydedemedik. Lütfen daha sonra yeniden deneyin." + }, + "connectionErrorTitle": { + "message": "Ekran görüntülerinize bağlanamadık." + }, + "connectionErrorDetails": { + "message": "Lütfen internet bağlantınızı kontrol edin. İnternete bağlanabiliyorsanız Firefox Screenhosts hizmeti ile ilgili geçici bir sorun olabilir." + }, + "loginErrorDetails": { + "message": "Firefox Screenshosts hizmetinde bir sorun yaşandığı için ekran görüntünüzü kaydedemedik. Lütfen daha sonra yeniden deneyin." + }, + "unshootablePageErrorTitle": { + "message": "Bu sayfanın ekran görüntüsü alınamıyor." + }, + "unshootablePageErrorDetails": { + "message": "Bu sayfa standart bir web sayfası olmadığı için ekran görüntüsünü alamazsınız." + }, + "selfScreenshotErrorTitle": { + "message": "Firefox Screenshots sayfalarının ekran görüntüsünü alamazsınz." + }, + "genericErrorTitle": { + "message": "Firefox Screenshosts kafayı yedi!" + }, + "genericErrorDetails": { + "message": "Ne olduğunu biz de anlamadık. Bir daha denemeye veya başka bir sayfanın ekran görüntüsünü almaya ne dersiniz?" + }, + "tourBodyOne": { + "message": "Firefox'tan çıkmadan ekran görüntüleri alın, kaydedin ve paylaşın." + }, + "tourHeaderTwo": { + "message": "İstediğini yakala" + }, + "tourBodyTwo": { + "message": "Sayfanın belli bir kısmını yakalamak için işaretçiyi tıklayıp sürükleyin. Seçiminizi vurgulamak için fareyle üzerine gelebilirsiniz." + }, + "tourHeaderThree": { + "message": "İstediğin gibi yakala" + }, + "tourBodyThree": { + "message": "Ekran görüntülerinizi daha kolay paylşamak veya bilgisayarınıza indirmek için web'e kaydedin. Kaydettiğiniz tüm görüntüleri bulmak için \"Ekran görüntülerim\" düğmesine tıklayabilirsiniz." + }, + "tourHeaderFour": { + "message": "Pencereleri veya sayfaların tamamını yakala" + }, + "tourBodyFour": { + "message": "Yalnızda pencerede gördüğünüz alanı veya sayfanın tamamını yakalamak için sağ üstteki düğmelerden uygun olanı seçin." + }, + "tourSkip": { + "message": "GEÇ" + }, + "tourNext": { + "message": "Sonraki slayt" + }, + "tourPrevious": { + "message": "Önceki slayt" + }, + "tourDone": { + "message": "Tamam" + }, + "termsAndPrivacyNotice": { + "message": "Firefox Screenshots'ı kullandığınızda Screenshosts $TERMSANDPRIVACYNOTICETERMSLINK$ ve $TERMSANDPRIVACYNOTICEPRIVACYLINK$ kabul etmiş sayılırsınız.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Koşullarını" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Gizlilik Bildirimini" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/uk/messages.json b/browser/extensions/screenshots/webextension/_locales/uk/messages.json new file mode 100644 index 000000000000..48cc768e639c --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/uk/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "Робіть вирізки та знімки екрану в Інтернеті та зберігайте їх для подальшої роботи." + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "Зробити знімок екрану" + }, + "myShotsLink": { + "message": "Мої знімки" + }, + "screenshotInstructions": { + "message": "Потягніть або клацніть на сторінці для вибору області. Натисніть ESC для скасування." + }, + "saveScreenshotSelectedArea": { + "message": "Зберегти" + }, + "saveScreenshotVisibleArea": { + "message": "Зберегти видиму область" + }, + "saveScreenshotFullPage": { + "message": "Зберегти всю сторінку" + }, + "cancelScreenshot": { + "message": "Скасувати" + }, + "downloadScreenshot": { + "message": "Завантажити" + }, + "notificationLinkCopiedTitle": { + "message": "Посилання скопійовано" + }, + "notificationLinkCopiedDetails": { + "message": "Посилання на ваш знімок було скопійоване до буфера обміну. Натисніть $META_KEY$-V для вставлення.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Сталася помилка." + }, + "requestErrorDetails": { + "message": "Вибачте! Нам не вдалося зберегти ваш знімок. Спробуйте знову пізніше." + }, + "connectionErrorTitle": { + "message": "Ми не можемо отримати доступ до ваших знімків." + }, + "connectionErrorDetails": { + "message": "Будь ласка, перевірте ваше підключення до Інтернету. Якщо у вас все в порядку з Інтернетом, можливо, виникли тимчасові проблеми зі службою Firefox Screenshots." + }, + "loginErrorDetails": { + "message": "Нам не вдалося зберегти ваш знімок, тому що виникли проблеми зі службою Firefox Screenshots. Спробуйте знову пізніше." + }, + "unshootablePageErrorTitle": { + "message": "Ми не можемо зробити знімок цієї сторінки." + }, + "unshootablePageErrorDetails": { + "message": "Це не стандартна веб-сторінка, тому ви не можете зробити її знімок." + }, + "selfScreenshotErrorTitle": { + "message": "Ви не можете зробити знімок сторінки Firefox Screenshots!" + }, + "genericErrorTitle": { + "message": "Оу! З Firefox Screenshots щось негаразд." + }, + "genericErrorDetails": { + "message": "Ми не впевнені, в чому проблема. Спробувати ще раз, або ж зробити знімок іншої сторінки?" + }, + "tourBodyOne": { + "message": "Робіть знімки екрану, зберігайте та діліться ними прямо в Firefox." + }, + "tourHeaderTwo": { + "message": "Робіть знімки чого завгодно" + }, + "tourBodyTwo": { + "message": "Клацніть і потягніть мишею для захоплення частини сторінки. Ви також можете навести курсор миші для підсвічення вибраної області." + }, + "tourHeaderThree": { + "message": "Як вам подобається" + }, + "tourBodyThree": { + "message": "Зберігайте свої знімки в Інтернеті, щоб легко ними ділитися, або завантажуйте їх на свій комп'ютер. Ви також можете переглянути всі збережені знімки, натиснувши на кнопку Мої знімки." + }, + "tourHeaderFour": { + "message": "Захоплюйте вікна або цілі сторінки" + }, + "tourBodyFour": { + "message": "За допомогою кнопок у верхній правій частині обирайте захоплення видимої області вікна, або сторінки повністю." + }, + "tourSkip": { + "message": "Пропустити" + }, + "tourNext": { + "message": "Наступний слайд" + }, + "tourPrevious": { + "message": "Попередній слайд" + }, + "tourDone": { + "message": "Готово" + }, + "termsAndPrivacyNotice": { + "message": "Використовуючи Firefox Screenshots, ви погоджуєтеся з його $TERMSANDPRIVACYNOTICETERMSLINK$ та $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Умовами використання" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Повідомленням про приватність" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/ur/messages.json b/browser/extensions/screenshots/webextension/_locales/ur/messages.json new file mode 100644 index 000000000000..566eefe5446b --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/ur/messages.json @@ -0,0 +1,103 @@ +{ + "addonDescription": { + "message": "ویب سے کللبس یاا اسکرین شاٹیں لیں اور ان کو عارظی یا مستقل طور پر محفوظ کریں۔" + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "ایک سکرین شاٹ لیں" + }, + "myShotsLink": { + "message": "میری شاٹس" + }, + "screenshotInstructions": { + "message": "علاقہ منتخب کرنے کے لیئے گھسیٹیں یا صفحہ پر کلک کریں۔ منسوخ کرنے کے لیئے ESC دبائیں۔" + }, + "saveScreenshotSelectedArea": { + "message": "محفوظ کریں" + }, + "saveScreenshotVisibleArea": { + "message": "مرئی محفوظ کریں" + }, + "saveScreenshotFullPage": { + "message": "پورا صفحہ محفوظ کریں" + }, + "cancelScreenshot": { + "message": "منسوخ کریں" + }, + "downloadScreenshot": { + "message": "ڈاؤن لوڈ" + }, + "notificationLinkCopiedTitle": { + "message": "تبط نقل کر دیا گیا" + }, + "notificationLinkCopiedDetails": { + "message": "آُپ کی شاٹس کا ربط و تختہ تراشہ پر نقل کر دیا گیا ہے۔ چسپاں کرنے کے لیئے $META_KEY$-V دبائِں۔", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "خراب ہے۔" + }, + "requestErrorDetails": { + "message": "معاف کیجیئے گا! ہم آپ کی شاٹ محفوظ نہیں کر سکے۔ براہ مہربانی کچھ دیر بعد کوشش کریں۔" + }, + "connectionErrorTitle": { + "message": "ہم آپ کی اسکرین شاٹس سے نہیں جڑ سکتے۔" + }, + "connectionErrorDetails": { + "message": "براہ مہربانی اپنے انٹرنیٹ کنکشن کی پڑتال کریں۔ اگر آپ انٹرنیٹ سے جڑنے کے قابل ہیں، تو شاید Firefox اسکرین شاٹ خدمات کے ساتھ عارظی مسلہ ہو۔" + }, + "loginErrorDetails": { + "message": "ہم آُپ کی شاٹ محفوظ نہیں کر سکے کیونکہ Firefox اسکرین شاٹ خدمت کے ساتھ مسلہ ہے۔ براہ مہربانی کچھ دیربعد کوشش کیجیئے۔ " + }, + "unshootablePageErrorTitle": { + "message": "ہم اس صفحہ کی اسکرین شاٹ نہیں کر سکتے۔" + }, + "unshootablePageErrorDetails": { + "message": "یہ ایک میعاری صفحہ نہہیں، تو آپ اسکی اسکرین شاٹ نہیں لے سکتے۔" + }, + "selfScreenshotErrorTitle": { + "message": "آپ Firefox اسکرین شاٹس صفحے! کی ایک شاٹ نہیں لے سکت" + }, + "genericErrorDetails": { + "message": "ہمیں یقین نہیں کہ کیا ہوا تھا۔ خیال رکھ کر پھر کوشش کریں یا بھر مختلف صفحہ کی تصویرلیں؟" + }, + "tourBodyOne": { + "message": "۔Firefox کو چھوڑے بغیر اسکرینشاٹس لیں، محفوظ کریں اور شیئر کریں۔" + }, + "tourHeaderTwo": { + "message": "جو آپ چاہتے ہیں وہ گرفت کریں" + }, + "tourBodyTwo": { + "message": "صفحہ کا ایک حصہ گرفت کرنے کے لیئے گھسیتیں اور کلک کریں.آُپ اپنے انتخاب کو نمایاں کرنے کے لیئے منڈلا سکتے ہیں۔" + }, + "tourHeaderThree": { + "message": "جس طرح آپ کو پسند ہے" + }, + "tourBodyThree": { + "message": "اپنے کمپیوٹڑ میں ڈائونلوڈ کرنے یا ویب پر آسانی سے شیئر کرنے کے لیئےاپنی کتری ہوئی شاٹس محفوظ کریں۔ آپ میری شاٹس کے بٹن پ کلک کر کے بھی اتمام پنی لی گئی شاٹس ڈھّونڈ سکتےہیں۔" + }, + "tourHeaderFour": { + "message": "دریچہ ہا مکمل صفحہ گرفت کریں" + }, + "tourBodyFour": { + "message": "دریچہ میں نظر آنے والے علاقے یا مکمل صفحہ کو گرفت کرنے کے لیئے بالائی دائیں جانب بٹن کا انتخاب کریں۔" + }, + "tourSkip": { + "message": "اچٹیں\t " + }, + "tourNext": { + "message": "اگلى سلائيڈ" + }, + "tourPrevious": { + "message": "پچھلی سلائڈ" + }, + "tourDone": { + "message": "ہوگیا" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/zh_CN/messages.json b/browser/extensions/screenshots/webextension/_locales/zh_CN/messages.json new file mode 100644 index 000000000000..1a030a5d0354 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/zh_CN/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "剪辑和拍摄 Web 截图,临时或永久保存它们。" + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "拍摄截图" + }, + "myShotsLink": { + "message": "我的截图" + }, + "screenshotInstructions": { + "message": "在页面上拖动或点击以选择范围。按 ESC 取消。" + }, + "saveScreenshotSelectedArea": { + "message": "保存" + }, + "saveScreenshotVisibleArea": { + "message": "保存可见范围" + }, + "saveScreenshotFullPage": { + "message": "保存整个页面" + }, + "cancelScreenshot": { + "message": "取消" + }, + "downloadScreenshot": { + "message": "下载" + }, + "notificationLinkCopiedTitle": { + "message": "链接已复制" + }, + "notificationLinkCopiedDetails": { + "message": "您的截图的链接已复制到剪贴板。按 $META_KEY$-V 粘贴。", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "出故障了。" + }, + "requestErrorDetails": { + "message": "很抱歉,我们无法为您保存截图。请稍后再试。" + }, + "connectionErrorTitle": { + "message": "我们无法连接到您的截图。" + }, + "connectionErrorDetails": { + "message": "请检查您的互联网连接。如果您正常连接到互联网,Firefox Screenshots 的服务器可能遇到了问题。" + }, + "loginErrorDetails": { + "message": "Firefox Screenshots 服务遇到问题,我们现在无法保存您的截图。请稍后再试。" + }, + "unshootablePageErrorTitle": { + "message": "我们无法截图此页面。" + }, + "unshootablePageErrorDetails": { + "message": "这不是一个标准的网页,所以无法截图。" + }, + "selfScreenshotErrorTitle": { + "message": "您不能拍摄 Firefox Screenshots 的页面!" + }, + "genericErrorTitle": { + "message": "哎呀,Firefox Screenshots 遇到问题。" + }, + "genericErrorDetails": { + "message": "我们不确定发生了什么。您可以再试一次或者试试另一个页面。" + }, + "tourBodyOne": { + "message": "拍摄、保存和分享屏幕截图,无需 Firefox 以外的工具。" + }, + "tourHeaderTwo": { + "message": "只拍摄想要的部分" + }, + "tourBodyTwo": { + "message": "单击并拖动以只拍摄页面某个区域。您也可以悬停以高亮您的选择范围。" + }, + "tourHeaderThree": { + "message": "做你所想" + }, + "tourBodyThree": { + "message": "将您裁剪后的截图保存到网上以便共享,或者下载到您的计算机。您也可以点击“我的截图”按钮找到您拍摄的所有截图。" + }, + "tourHeaderFour": { + "message": "拍摄窗口或整个页面" + }, + "tourBodyFour": { + "message": "选择右上角的按钮可以拍摄窗口中的可见区域或者整个页面。" + }, + "tourSkip": { + "message": "跳过" + }, + "tourNext": { + "message": "下一页" + }, + "tourPrevious": { + "message": "上一页" + }, + "tourDone": { + "message": "完成" + }, + "termsAndPrivacyNotice": { + "message": "使用 Firefox Screenshots 即代表您同意 Screenshots 的$TERMSANDPRIVACYNOTICETERMSLINK$和$TERMSANDPRIVACYNOTICEPRIVACYLINK$。", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "条款" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "隐私声明" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/_locales/zh_TW/messages.json b/browser/extensions/screenshots/webextension/_locales/zh_TW/messages.json new file mode 100644 index 000000000000..fa059cc5be35 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/zh_TW/messages.json @@ -0,0 +1,123 @@ +{ + "addonDescription": { + "message": "拍攝網頁的擷圖,可暫時儲存或永久儲存。" + }, + "addonAuthorsList": { + "message": "Mozilla " + }, + "contextMenuLabel": { + "message": "拍攝畫面擷圖" + }, + "myShotsLink": { + "message": "我的擷圖" + }, + "screenshotInstructions": { + "message": "拖曳或點擊頁面來選擇區域,按下 ESC 取消。" + }, + "saveScreenshotSelectedArea": { + "message": "儲存" + }, + "saveScreenshotVisibleArea": { + "message": "儲存可見範圍" + }, + "saveScreenshotFullPage": { + "message": "儲存完整頁面" + }, + "cancelScreenshot": { + "message": "取消" + }, + "downloadScreenshot": { + "message": "下載" + }, + "notificationLinkCopiedTitle": { + "message": "已複製鏈結" + }, + "notificationLinkCopiedDetails": { + "message": "已將您拍攝的圖片鏈結複製到剪貼簿,按下 $META_KEY$+V 即可貼上。", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "系統維護中。" + }, + "requestErrorDetails": { + "message": "抱歉!無法儲存您拍攝的圖片,請稍候再試一次。" + }, + "connectionErrorTitle": { + "message": "無法連線至您的畫面擷圖。" + }, + "connectionErrorDetails": { + "message": "請檢查您的網路連線。若您可以正常上網,可能是 Firefox Screenshots 臨時出了問題。" + }, + "loginErrorDetails": { + "message": "Firefox Screenshots 服務發生問題,我們無法儲存您拍攝的擷圖。請稍候再試。" + }, + "unshootablePageErrorTitle": { + "message": "無法幫此頁面拍照。" + }, + "unshootablePageErrorDetails": { + "message": "這不是標準的網頁,無法拍照。" + }, + "selfScreenshotErrorTitle": { + "message": "您不能幫 Firefox Screenshots 的頁面拍照!" + }, + "genericErrorTitle": { + "message": "唉呀,Firefox Screenshots 有點秀逗了。" + }, + "genericErrorDetails": { + "message": "我們不確定剛剛發生了什麼事,您可以再試一次,或拍攝其他頁面的擷圖嗎?" + }, + "tourBodyOne": { + "message": "不用離開 Firefox 就可以拍攝、儲存、分享畫面擷圖。" + }, + "tourHeaderTwo": { + "message": "只拍你想拍的" + }, + "tourBodyTwo": { + "message": "點擊並拖曳出頁面當中的一部份,您也可以停留下來,強調選擇範圍。" + }, + "tourHeaderThree": { + "message": "用您想要的方式分享" + }, + "tourBodyThree": { + "message": "直接將裁切過的擷圖傳到網路上方便分享,或者下載到電腦上。您也可以點擊「我的擷圖」按鈕,尋找您拍過的所有擷圖。" + }, + "tourHeaderFour": { + "message": "拍攝視窗或整張網頁" + }, + "tourBodyFour": { + "message": "透過右上角的不同按鈕來選擇只拍攝視窗中的可見區域,或是整張網頁。" + }, + "tourSkip": { + "message": "略過" + }, + "tourNext": { + "message": "下一頁" + }, + "tourPrevious": { + "message": "上一頁" + }, + "tourDone": { + "message": "完成" + }, + "termsAndPrivacyNotice": { + "message": "使用 Firefox Screenshots,代表您同意 Screenshots 的 $TERMSANDPRIVACYNOTICETERMSLINK$ 及 $TERMSANDPRIVACYNOTICEPRIVACYLINK$。", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "使用條款" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "隱私權保護政策" + } +} \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/assertIsTrusted.js b/browser/extensions/screenshots/webextension/assertIsTrusted.js new file mode 100644 index 000000000000..d80e83ac80a1 --- /dev/null +++ b/browser/extensions/screenshots/webextension/assertIsTrusted.js @@ -0,0 +1,20 @@ +/** For use with addEventListener, assures that any events have event.isTrusted set to true + https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted + Should be applied *inside* catcher.watchFunction +*/ +this.assertIsTrusted = function assertIsTrusted(handlerFunction) { + return function (event) { + if (! event) { + let exc = new Error("assertIsTrusted did not get an event"); + exc.noPopup = true; + throw exc; + } + if (! event.isTrusted) { + let exc = new Error(`Received untrusted event (type: ${event.type})`); + exc.noPopup = true; + throw exc; + } + return handlerFunction.call(this, event); + }; +} +null; diff --git a/browser/extensions/screenshots/webextension/background/analytics.js b/browser/extensions/screenshots/webextension/background/analytics.js new file mode 100644 index 000000000000..7be6cd06677c --- /dev/null +++ b/browser/extensions/screenshots/webextension/background/analytics.js @@ -0,0 +1,81 @@ +/* globals main, auth, catcher, deviceInfo, communication, log */ + +"use strict"; + +this.analytics = (function () { + let exports = {}; + + let telemetryPrefKnown = false; + let telemetryPref; + + exports.sendEvent = function (action, label, options) { + let eventCategory = "addon"; + if (! telemetryPrefKnown) { + log.warn("sendEvent called before we were able to refresh"); + return Promise.resolve(); + } + if (! telemetryPref) { + log.info(`Cancelled sendEvent ${eventCategory}/${action}/${label || 'none'} ${JSON.stringify(options)}`); + return Promise.resolve(); + } + if (typeof label == "object" && (! options)) { + options = label; + label = undefined; + } + options = options || {}; + let di = deviceInfo(); + return new Promise((resolve, reject) => { + let url = main.getBackend() + "/event"; + let req = new XMLHttpRequest(); + req.open("POST", url); + req.setRequestHeader("content-type", "application/json"); + req.onload = catcher.watchFunction(() => { + if (req.status >= 300) { + let exc = new Error("Bad response from POST /event"); + exc.status = req.status; + exc.statusText = req.statusText; + reject(exc); + } else { + resolve(); + } + }); + options.applicationName = di.appName; + options.applicationVersion = di.addonVersion; + let abTests = auth.getAbTests(); + for (let [gaField, value] of Object.entries(abTests)) { + options[gaField] = value; + } + log.info(`sendEvent ${eventCategory}/${action}/${label || 'none'} ${JSON.stringify(options)}`); + req.send(JSON.stringify({ + deviceId: auth.getDeviceId(), + event: eventCategory, + action, + label, + options + })); + }); + }; + + exports.refreshTelemetryPref = function () { + return communication.sendToBootstrap("getTelemetryPref").then((result) => { + telemetryPrefKnown = true; + if (result === communication.NO_BOOTSTRAP) { + telemetryPref = true; + } else { + telemetryPref = result; + } + }, (error) => { + // If there's an error reading the pref, we should assume that we shouldn't send data + telemetryPrefKnown = true; + telemetryPref = false; + throw error; + }); + }; + + exports.getTelemetryPrefSync = function() { + catcher.watchPromise(exports.refreshTelemetryPref()); + return !!telemetryPref; + }; + + return exports; +})(); diff --git a/browser/extensions/screenshots/webextension/background/auth.js b/browser/extensions/screenshots/webextension/background/auth.js new file mode 100644 index 000000000000..466ae0c9e3a4 --- /dev/null +++ b/browser/extensions/screenshots/webextension/background/auth.js @@ -0,0 +1,216 @@ +/* globals browser, log */ +/* globals main, makeUuid, deviceInfo, analytics, catcher, buildSettings, communication */ + +"use strict"; + +this.auth = (function () { + let exports = {}; + + let registrationInfo; + let initialized = false; + let authHeader = null; + let sentryPublicDSN = null; + let abTests = {}; + + catcher.watchPromise(browser.storage.local.get(["registrationInfo", "abTests"]).then((result) => { + if (result.abTests) { + abTests = result.abTests; + } + if (result.registrationInfo) { + registrationInfo = result.registrationInfo; + } else { + registrationInfo = generateRegistrationInfo(); + log.info("Generating new device authentication ID", registrationInfo); + return browser.storage.local.set({registrationInfo}); + } + })); + + exports.getDeviceId = function () { + return registrationInfo && registrationInfo.deviceId; + }; + + function generateRegistrationInfo() { + let info = { + deviceId: `anon${makeUuid()}`, + secret: makeUuid(), + registered: false + }; + return info; + } + + function register() { + return new Promise((resolve, reject) => { + let registerUrl = main.getBackend() + "/api/register"; + // TODO: replace xhr with Fetch #2261 + let req = new XMLHttpRequest(); + req.open("POST", registerUrl); + req.setRequestHeader("content-type", "application/json"); + req.onload = catcher.watchFunction(() => { + if (req.status == 200) { + log.info("Registered login"); + initialized = true; + saveAuthInfo(JSON.parse(req.responseText)); + resolve(true); + analytics.sendEvent("registered"); + } else { + analytics.sendEvent("register-failed", `bad-response-${req.status}`); + log.warn("Error in response:", req.responseText); + let exc = new Error("Bad response: " + req.status); + exc.popupMessage = "LOGIN_ERROR"; + reject(exc); + } + }); + req.onerror = catcher.watchFunction(() => { + analytics.sendEvent("register-failed", "connection-error"); + let exc = new Error("Error contacting server"); + exc.popupMessage = "LOGIN_CONNECTION_ERROR"; + reject(exc); + }); + req.send(JSON.stringify({ + deviceId: registrationInfo.deviceId, + secret: registrationInfo.secret, + deviceInfo: JSON.stringify(deviceInfo()) + })); + }); + } + + function login(options) { + let { ownershipCheck, noRegister } = options || {}; + return new Promise((resolve, reject) => { + let loginUrl = main.getBackend() + "/api/login"; + // TODO: replace xhr with Fetch #2261 + let req = new XMLHttpRequest(); + req.open("POST", loginUrl); + req.onload = catcher.watchFunction(() => { + if (req.status == 404) { + if (noRegister) { + resolve(false); + } else { + resolve(register()); + } + } else if (req.status >= 300) { + log.warn("Error in response:", req.responseText); + let exc = new Error("Could not log in: " + req.status); + exc.popupMessage = "LOGIN_ERROR"; + analytics.sendEvent("login-failed", `bad-response-${req.status}`); + reject(exc); + } else if (req.status === 0) { + let error = new Error("Could not log in, server unavailable"); + error.popupMessage = "LOGIN_CONNECTION_ERROR"; + analytics.sendEvent("login-failed", "connection-error"); + reject(error); + } else { + initialized = true; + let jsonResponse = JSON.parse(req.responseText); + log.info("Screenshots logged in"); + analytics.sendEvent("login"); + saveAuthInfo(jsonResponse); + if (ownershipCheck) { + resolve({isOwner: jsonResponse.isOwner}); + } else { + resolve(true); + } + } + }); + req.onerror = catcher.watchFunction(() => { + analytics.sendEvent("login-failed", "connection-error"); + let exc = new Error("Connection failed"); + exc.url = loginUrl; + exc.popupMessage = "CONNECTION_ERROR"; + reject(exc); + }); + req.setRequestHeader("content-type", "application/json"); + req.send(JSON.stringify({ + deviceId: registrationInfo.deviceId, + secret: registrationInfo.secret, + deviceInfo: JSON.stringify(deviceInfo()), + ownershipCheck + })); + }); + } + + function saveAuthInfo(responseJson) { + if (responseJson.sentryPublicDSN) { + sentryPublicDSN = responseJson.sentryPublicDSN; + } + if (responseJson.authHeader) { + authHeader = responseJson.authHeader; + if (!registrationInfo.registered) { + registrationInfo.registered = true; + catcher.watchPromise(browser.storage.local.set({registrationInfo})); + } + } + if (responseJson.abTests) { + abTests = responseJson.abTests; + catcher.watchPromise(browser.storage.local.set({abTests})); + } + } + + exports.getDeviceId = function () { + return registrationInfo.deviceId; + }; + + exports.authHeaders = function () { + let initPromise = Promise.resolve(); + if (! initialized) { + initPromise = login(); + } + return initPromise.then(() => { + if (authHeader) { + return {"x-screenshots-auth": authHeader}; + } else { + log.warn("No auth header available"); + return {}; + } + }); + }; + + exports.getSentryPublicDSN = function () { + return sentryPublicDSN || buildSettings.defaultSentryDsn; + }; + + exports.getAbTests = function () { + return abTests; + }; + + exports.isRegistered = function () { + return registrationInfo.registered; + }; + + exports.setDeviceInfoFromOldAddon = function (newDeviceInfo) { + if (! (newDeviceInfo.deviceId && newDeviceInfo.secret)) { + throw new Error("Bad deviceInfo"); + } + if (registrationInfo.deviceId === newDeviceInfo.deviceId && + registrationInfo.secret === newDeviceInfo.secret) { + // Probably we already imported the information + return Promise.resolve(false); + } + registrationInfo = { + deviceId: newDeviceInfo.deviceId, + secret: newDeviceInfo.secret, + registered: true + }; + initialized = false; + return browser.storage.local.set({registrationInfo}).then(() => { + return true; + }); + }; + + communication.register("getAuthInfo", (sender, ownershipCheck) => { + let info = registrationInfo; + let done = Promise.resolve(); + if (info.registered) { + done = login({ownershipCheck}).then((result) => { + if (result && result.isOwner) { + info.isOwner = true; + } + }); + } + return done.then(() => { + return info; + }); + }); + + return exports; +})(); diff --git a/browser/extensions/screenshots/webextension/background/communication.js b/browser/extensions/screenshots/webextension/background/communication.js new file mode 100644 index 000000000000..a6951aebdc4a --- /dev/null +++ b/browser/extensions/screenshots/webextension/background/communication.js @@ -0,0 +1,80 @@ +/* globals browser, catcher, log */ + +"use strict"; + +this.communication = (function () { + let exports = {}; + + let registeredFunctions = {}; + + browser.runtime.onMessage.addListener(catcher.watchFunction((req, sender, sendResponse) => { + if (! (req.funcName in registeredFunctions)) { + log.error(`Received unknown internal message type ${req.funcName}`); + sendResponse({type: "error", name: "Unknown message type"}); + return; + } + if (! Array.isArray(req.args)) { + log.error("Received message with no .args list"); + sendResponse({type: "error", name: "No .args"}); + return; + } + let func = registeredFunctions[req.funcName]; + let result; + try { + req.args.unshift(sender); + result = func.apply(null, req.args); + } catch (e) { + log.error(`Error in ${req.funcName}:`, e, e.stack); + // FIXME: should consider using makeError from catcher here: + sendResponse({type: "error", message: e+""}); + return; + } + if (result && result.then) { + result.then((concreteResult) => { + sendResponse({type: "success", value: concreteResult}); + }).catch((errorResult) => { + log.error(`Promise error in ${req.funcName}:`, errorResult, errorResult && errorResult.stack); + sendResponse({type: "error", message: errorResult+""}); + }); + return true; + } else { + sendResponse({type: "success", value: result}); + } + })); + + exports.register = function (name, func) { + registeredFunctions[name] = func; + }; + + /** Send a message to bootstrap.js + Technically any worker can listen to this. If the bootstrap wrapper is not in place, then this + will *not* fail, and will return a value of exports.NO_BOOTSTRAP */ + exports.sendToBootstrap = function (funcName, ...args) { + return browser.runtime.sendMessage({funcName, args}).then((result) => { + if (result.type === "success") { + return result.value; + } else { + throw new Error(`Error in ${funcName}: ${result.name || 'unknown'}`); + } + }, (error) => { + if (isBootstrapMissingError(error)) { + return exports.NO_BOOTSTRAP; + } + throw error; + }); + }; + + function isBootstrapMissingError(error) { + if (! error) { + return false; + } + return error.errorCode === "NO_RECEIVING_END" || + (! error.errorCode && error.message === "Could not establish connection. Receiving end does not exist."); + } + + + // A singleton/sentinal (with a name): + exports.NO_BOOTSTRAP = {name: "communication.NO_BOOTSTRAP"}; + + return exports; +})(); diff --git a/browser/extensions/screenshots/webextension/background/deviceInfo.js b/browser/extensions/screenshots/webextension/background/deviceInfo.js new file mode 100644 index 000000000000..9ab5bb00c2e1 --- /dev/null +++ b/browser/extensions/screenshots/webextension/background/deviceInfo.js @@ -0,0 +1,34 @@ +/* globals browser, catcher */ + +"use strict"; + +this.deviceInfo = (function () { + let manifest = browser.runtime.getManifest(); + + let platformInfo = {}; + catcher.watchPromise(browser.runtime.getPlatformInfo().then((info) => { + platformInfo = info; + })); + + return function deviceInfo() { + let match = navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9\.]+)/); + let chromeVersion = match ? match[1] : null; + match = navigator.userAgent.match(/Firefox\/([0-9\.]+)/); + let firefoxVersion = match ? match[1] : null; + let appName = chromeVersion ? "chrome" : "firefox"; + + return { + addonVersion: manifest.version, + platform: platformInfo.os, + architecture: platformInfo.arch, + version: firefoxVersion || chromeVersion, + // These don't seem to apply to Chrome: + //build: system.build, + //platformVersion: system.platformVersion, + userAgent: navigator.userAgent, + appVendor: appName, + appName + }; + }; + +})(); diff --git a/browser/extensions/screenshots/webextension/background/main.js b/browser/extensions/screenshots/webextension/background/main.js new file mode 100644 index 000000000000..82e9bb7186f2 --- /dev/null +++ b/browser/extensions/screenshots/webextension/background/main.js @@ -0,0 +1,276 @@ +/* globals browser, console, XMLHttpRequest, Image, document, setTimeout, navigator */ +/* globals selectorLoader, analytics, communication, catcher, makeUuid, auth, senderror */ + +"use strict"; + +this.main = (function () { + let exports = {}; + + const pasteSymbol = (window.navigator.platform.match(/Mac/i)) ? "\u2318" : "Ctrl"; + const { sendEvent } = analytics; + + let manifest = browser.runtime.getManifest(); + let backend; + + let hasSeenOnboarding; + + browser.storage.local.get(["hasSeenOnboarding"]).then((result) => { + hasSeenOnboarding = !! result.hasSeenOnboarding; + if (! hasSeenOnboarding) { + setIconActive(false, null); + // Note that the branded name 'Firefox Screenshots' is not localized: + browser.browserAction.setTitle({ + title: "Firefox Screenshots" + }); + } + }).catch((error) => { + log.error("Error getting hasSeenOnboarding:", error); + }); + + exports.setBackend = function (newBackend) { + backend = newBackend; + backend = backend.replace(/\/*$/, ""); + }; + + exports.getBackend = function () { + return backend; + }; + + communication.register("getBackend", () => { + return backend; + }); + + function getOnboardingUrl() { + return backend + "/#hello"; + } + + for (let permission of manifest.permissions) { + if (/^https?:\/\//.test(permission)) { + exports.setBackend(permission); + break; + } + } + + function setIconActive(active, tabId) { + let path = active ? "icons/icon-highlight-38.png" : "icons/icon-38.png"; + if ((! hasSeenOnboarding) && ! active) { + path = "icons/icon-starred-38.png"; + } + browser.browserAction.setIcon({path, tabId}); + } + + function toggleSelector(tab) { + return analytics.refreshTelemetryPref() + .then(() => selectorLoader.toggle(tab.id, hasSeenOnboarding)) + .then(active => { + setIconActive(active, tab.id); + return active; + }) + .catch((error) => { + error.popupMessage = "UNSHOOTABLE_PAGE"; + throw error; + }); + } + + function shouldOpenMyShots(url) { + return /^about:(?:newtab|blank)/i.test(url) || /^resource:\/\/activity-streams\//i.test(url); + } + + browser.browserAction.onClicked.addListener(catcher.watchFunction((tab) => { + if (shouldOpenMyShots(tab.url)) { + if (! hasSeenOnboarding) { + catcher.watchPromise(analytics.refreshTelemetryPref().then(() => { + sendEvent("goto-onboarding", "selection-button"); + return forceOnboarding(); + })); + return; + } + catcher.watchPromise(analytics.refreshTelemetryPref().then(() => { + sendEvent("goto-myshots", "about-newtab"); + })); + catcher.watchPromise( + auth.authHeaders() + .then(() => browser.tabs.update({url: backend + "/shots"}))); + } else { + catcher.watchPromise( + toggleSelector(tab) + .then(active => { + const event = active ? "start-shot" : "cancel-shot"; + sendEvent(event, "toolbar-button"); + }, (error) => { + if ((! hasSeenOnboarding) && error.popupMessage == "UNSHOOTABLE_PAGE") { + sendEvent("goto-onboarding", "selection-button"); + return forceOnboarding(); + } + throw error; + })); + } + })); + + function forceOnboarding() { + return browser.tabs.create({url: getOnboardingUrl()}).then((tab) => { + return toggleSelector(tab); + }); + } + + browser.contextMenus.create({ + id: "create-screenshot", + title: browser.i18n.getMessage("contextMenuLabel"), + contexts: ["page"], + documentUrlPatterns: [""] + }, () => { + // Note: unlike most browser.* functions this one does not return a promise + if (browser.runtime.lastError) { + catcher.unhandled(new Error(browser.runtime.lastError.message)); + } + }); + + browser.contextMenus.onClicked.addListener(catcher.watchFunction((info, tab) => { + if (! tab) { + // Not in a page/tab context, ignore + return; + } + catcher.watchPromise( + toggleSelector(tab) + .then(() => sendEvent("start-shot", "context-menu"))); + })); + + function urlEnabled(url) { + if (shouldOpenMyShots(url)) { + return true; + } + if (isShotOrMyShotPage(url) || /^(?:about|data|moz-extension):/i.test(url) || isBlacklistedUrl(url)) { + return false; + } + return true; + } + + function isShotOrMyShotPage(url) { + // It's okay to take a shot of any pages except shot pages and My Shots + if (! url.startsWith(backend)) { + return false; + } + let path = url.substr(backend.length).replace(/^\/*/, "").replace(/#.*/, "").replace(/\?.*/, ""); + if (path == "shots") { + return true; + } + if (/^[^/]+\/[^/]+$/.test(url)) { + // Blocks {:id}/{:domain}, but not /, /privacy, etc + return true; + } + return false; + } + + function isBlacklistedUrl(url) { + // These specific domains are not allowed for general WebExtension permission reasons + // Discussion: https://bugzilla.mozilla.org/show_bug.cgi?id=1310082 + // List of domains copied from: https://dxr.mozilla.org/mozilla-central/source/browser/app/permissions#18-19 + // Note we disable it here to be informative, the security check is done in WebExtension code + const badDomains = ["addons.mozilla.org", "testpilot.firefox.com"]; + let domain = url.replace(/^https?:\/\//i, ""); + domain = domain.replace(/\/.*/, "").replace(/:.*/, ""); + domain = domain.toLowerCase(); + return badDomains.includes(domain); + } + + browser.tabs.onUpdated.addListener(catcher.watchFunction((id, info, tab) => { + if (info.url && tab.active) { + if (urlEnabled(info.url)) { + browser.browserAction.enable(tab.id); + } else if (hasSeenOnboarding) { + browser.browserAction.disable(tab.id); + } + } + })); + + browser.tabs.onActivated.addListener(catcher.watchFunction(({tabId, windowId}) => { + catcher.watchPromise(browser.tabs.get(tabId).then((tab) => { + // onActivated may fire before the url is set + if (!tab.url) { + return; + } + if (urlEnabled(tab.url)) { + browser.browserAction.enable(tabId); + } else if (hasSeenOnboarding) { + browser.browserAction.disable(tabId); + } + })); + })); + + communication.register("sendEvent", (sender, ...args) => { + catcher.watchPromise(sendEvent(...args)); + // We don't wait for it to complete: + return null; + }); + + communication.register("openMyShots", (sender) => { + return catcher.watchPromise( + auth.authHeaders() + .then(() => browser.tabs.create({url: backend + "/shots"}))); + }); + + communication.register("openShot", (sender, {url, copied}) => { + if (copied) { + const id = makeUuid(); + return browser.notifications.create(id, { + type: "basic", + iconUrl: "../icons/copy.png", + title: browser.i18n.getMessage("notificationLinkCopiedTitle"), + message: browser.i18n.getMessage("notificationLinkCopiedDetails", pasteSymbol) + }); + } + }); + + communication.register("downloadShot", (sender, info) => { + // 'data:' urls don't work directly, let's use a Blob + // see http://stackoverflow.com/questions/40269862/save-data-uri-as-file-using-downloads-download-api + const binary = atob(info.url.split(',')[1]); // just the base64 data + const data = Uint8Array.from(binary, char => char.charCodeAt(0)) + const blob = new Blob([data], {type: "image/png"}) + return browser.downloads.download({ + url: URL.createObjectURL(blob), + filename: info.filename + }); + }); + + communication.register("closeSelector", (sender) => { + setIconActive(false, sender.tab.id) + }); + + catcher.watchPromise(communication.sendToBootstrap("getOldDeviceInfo").then((deviceInfo) => { + if (deviceInfo === communication.NO_BOOTSTRAP || ! deviceInfo) { + return; + } + deviceInfo = JSON.parse(deviceInfo); + if (deviceInfo && typeof deviceInfo == "object") { + return auth.setDeviceInfoFromOldAddon(deviceInfo).then((updated) => { + if (updated === communication.NO_BOOTSTRAP) { + throw new Error("bootstrap.js disappeared unexpectedly"); + } + if (updated) { + return communication.sendToBootstrap("removeOldAddon"); + } + }); + } + })); + + communication.register("hasSeenOnboarding", () => { + hasSeenOnboarding = true; + catcher.watchPromise(browser.storage.local.set({hasSeenOnboarding})); + setIconActive(false, null); + browser.browserAction.setTitle({ + title: browser.i18n.getMessage("contextMenuLabel") + }); + }); + + communication.register("abortFrameset", () => { + sendEvent("abort-start-shot", "frame-page"); + // Note, we only show the error but don't report it, as we know that we can't + // take shots of these pages: + senderror.showError({ + popupMessage: "UNSHOOTABLE_PAGE" + }); + }); + + return exports; +})(); diff --git a/browser/extensions/screenshots/webextension/background/selectorLoader.js b/browser/extensions/screenshots/webextension/background/selectorLoader.js new file mode 100644 index 000000000000..e09342804002 --- /dev/null +++ b/browser/extensions/screenshots/webextension/background/selectorLoader.js @@ -0,0 +1,114 @@ +/* globals browser, catcher, log */ + +"use strict"; + +this.selectorLoader = (function () { + const exports = {}; + + // These modules are loaded in order, first standardScripts, then optionally onboardingScripts, and then selectorScripts + // The order is important due to dependencies + const standardScripts = [ + "build/buildSettings.js", + "log.js", + "catcher.js", + "assertIsTrusted.js", + "background/selectorLoader.js", + "selector/callBackground.js", + "selector/util.js" + ]; + + const selectorScripts = [ + "clipboard.js", + "makeUuid.js", + "build/shot.js", + "randomString.js", + "domainFromUrl.js", + "build/inlineSelectionCss.js", + "selector/documentMetadata.js", + "selector/ui.js", + "selector/shooter.js", + "selector/uicontrol.js" + ]; + + // These are loaded on request (by the selector worker) to activate the onboarding: + const onboardingScripts = [ + "build/onboardingCss.js", + "build/onboardingHtml.js", + "onboarding/slides.js" + ]; + + exports.unloadIfLoaded = function (tabId) { + return browser.tabs.executeScript(tabId, { + code: "this.selectorLoader && this.selectorLoader.unloadModules()", + runAt: "document_start" + }).then(result => { + return result && result[0]; + }); + }; + + exports.loadModules = function (tabId, hasSeenOnboarding) { + if (hasSeenOnboarding) { + return executeModules(tabId, standardScripts.concat(selectorScripts)); + } else { + return executeModules(tabId, standardScripts.concat(onboardingScripts).concat(selectorScripts)); + } + }; + + function executeModules(tabId, scripts) { + let lastPromise = Promise.resolve(null); + scripts.forEach((file) => { + lastPromise = lastPromise.then(() => { + return browser.tabs.executeScript(tabId, { + file, + runAt: "document_end" + }) + .catch((error) => { + log.error("error in script:", file, error); + error.scriptName = file; + throw error; + }) + }) + }); + return lastPromise.then(() => { + log.debug("finished loading scripts:", scripts.join(" ")); + }, + (error) => { + exports.unloadIfLoaded(tabId); + catcher.unhandled(error); + throw error; + }); + } + + exports.unloadModules = function () { + const watchFunction = catcher.watchFunction; + let allScripts = standardScripts.concat(onboardingScripts).concat(selectorScripts); + const moduleNames = allScripts.map((filename) => + filename.replace(/^.*\//, "").replace(/\.js$/, "")); + moduleNames.reverse(); + for (let moduleName of moduleNames) { + let moduleObj = global[moduleName]; + if (moduleObj && moduleObj.unload) { + try { + watchFunction(moduleObj.unload)(); + } catch (e) { + // ignore (watchFunction handles it) + } + } + delete global[moduleName]; + } + return true; + }; + + exports.toggle = function (tabId, hasSeenOnboarding) { + return exports.unloadIfLoaded(tabId) + .then(wasLoaded => { + if (!wasLoaded) { + exports.loadModules(tabId, hasSeenOnboarding); + } + return !wasLoaded; + }) + }; + + return exports; +})(); +null; diff --git a/browser/extensions/screenshots/webextension/background/senderror.js b/browser/extensions/screenshots/webextension/background/senderror.js new file mode 100644 index 000000000000..d79ee3b6ad52 --- /dev/null +++ b/browser/extensions/screenshots/webextension/background/senderror.js @@ -0,0 +1,117 @@ +/* globals analytics, browser, communication, makeUuid, Raven, catcher, auth, log */ + +"use strict"; + +this.senderror = (function () { + let exports = {}; + + // Do not show an error more than every ERROR_TIME_LIMIT milliseconds: + const ERROR_TIME_LIMIT = 3000; + + let messages = { + REQUEST_ERROR: { + title: browser.i18n.getMessage("requestErrorTitle"), + info: browser.i18n.getMessage("requestErrorDetails") + }, + CONNECTION_ERROR: { + title: browser.i18n.getMessage("connectionErrorTitle"), + info: browser.i18n.getMessage("connectionErrorDetails") + }, + LOGIN_ERROR: { + title: browser.i18n.getMessage("requestErrorTitle"), + info: browser.i18n.getMessage("loginErrorDetails") + }, + LOGIN_CONNECTION_ERROR: { + title: browser.i18n.getMessage("connectionErrorTitle"), + info: browser.i18n.getMessage("connectionErrorDetails") + }, + UNSHOOTABLE_PAGE: { + title: browser.i18n.getMessage("unshootablePageErrorTitle"), + info: browser.i18n.getMessage("unshootablePageErrorDetails") + }, + SHOT_PAGE: { + title: browser.i18n.getMessage("selfScreenshotErrorTitle") + }, + MY_SHOTS: { + title: browser.i18n.getMessage("selfScreenshotErrorTitle") + }, + generic: { + title: browser.i18n.getMessage("genericErrorTitle"), + info: browser.i18n.getMessage("genericErrorDetails"), + showMessage: true + } + }; + + communication.register("reportError", (sender, error) => { + catcher.unhandled(error); + }); + + let lastErrorTime; + + exports.showError = function (error) { + if (lastErrorTime && (Date.now() - lastErrorTime) < ERROR_TIME_LIMIT) { + return; + } + lastErrorTime = Date.now(); + let id = makeUuid(); + let popupMessage = error.popupMessage || "generic"; + if (! messages[popupMessage]) { + popupMessage = "generic"; + } + let title = messages[popupMessage].title; + let message = messages[popupMessage].info || ''; + let showMessage = messages[popupMessage].showMessage; + if (error.message && showMessage) { + if (message) { + message += "\n" + error.message; + } else { + message = error.message; + } + } + browser.notifications.create(id, { + type: "basic", + // FIXME: need iconUrl for an image, see #2239 + title, + message + }); + }; + + exports.reportError = function (e) { + if (!analytics.getTelemetryPrefSync()) { + log.error("Telemetry disabled. Not sending critical error:", e); + return; + } + let dsn = auth.getSentryPublicDSN(); + if (! dsn) { + log.warn("Error:", e); + return; + } + if (! Raven.isSetup()) { + Raven.config(dsn).install(); + } + let exception = new Error(e.message); + exception.stack = e.multilineStack || e.stack || undefined; + let rest = {}; + for (let attr in e) { + if (! ["name", "message", "stack", "multilineStack", "popupMessage", "version", "sentryPublicDSN", "help"].includes(attr)) { + rest[attr] = e[attr]; + } + } + rest.stack = e.multilineStack || e.stack; + Raven.captureException(exception, { + logger: 'addon', + tags: {version: e.version, category: e.popupMessage}, + message: exception.message, + extra: rest + }); + }; + + catcher.registerHandler((errorObj) => { + if (! errorObj.noPopup) { + exports.showError(errorObj); + } + exports.reportError(errorObj); + }); + + return exports; +})(); diff --git a/browser/extensions/screenshots/webextension/background/takeshot.js b/browser/extensions/screenshots/webextension/background/takeshot.js new file mode 100644 index 000000000000..3d7c09028272 --- /dev/null +++ b/browser/extensions/screenshots/webextension/background/takeshot.js @@ -0,0 +1,128 @@ +/* globals communication, shot, main, auth, catcher, analytics, browser */ + +"use strict"; + +this.takeshot = (function () { + let exports = {}; + const Shot = shot.AbstractShot; + const { sendEvent } = analytics; + + communication.register("takeShot", catcher.watchFunction((sender, options) => { + let { captureType, captureText, scroll, selectedPos, shotId, shot } = options; + shot = new Shot(main.getBackend(), shotId, shot); + let capturePromise = Promise.resolve(); + let openedTab; + if (! shot.clipNames().length) { + // canvas.drawWindow isn't available, so we fall back to captureVisibleTab + capturePromise = screenshotPage(selectedPos, scroll).then((dataUrl) => { + shot.addClip({ + createdDate: Date.now(), + image: { + url: dataUrl, + captureType, + text: captureText, + location: selectedPos, + dimensions: { + x: selectedPos.right - selectedPos.left, + y: selectedPos.bottom - selectedPos.top + } + } + }); + }); + } + let shotAbTests = {}; + let abTests = auth.getAbTests(); + for (let testName of Object.keys(abTests)) { + if (abTests[testName].shotField) { + shotAbTests[testName] = abTests[testName].value; + } + } + if (Object.keys(shotAbTests).length) { + shot.abTests = shotAbTests; + } + return catcher.watchPromise(capturePromise.then(() => { + return browser.tabs.create({url: shot.creatingUrl}) + }).then((tab) => { + openedTab = tab; + return uploadShot(shot); + }).then(() => { + return browser.tabs.update(openedTab.id, {url: shot.viewUrl}); + }).then(() => { + return shot.viewUrl; + }).catch((error) => { + browser.tabs.remove(openedTab.id); + throw error; + })); + })); + + communication.register("screenshotPage", (sender, selectedPos, scroll) => { + return screenshotPage(selectedPos, scroll); + }); + + function screenshotPage(pos, scroll) { + pos = { + top: pos.top - scroll.scrollY, + left: pos.left - scroll.scrollX, + bottom: pos.bottom - scroll.scrollY, + right: pos.right - scroll.scrollX + }; + pos.width = pos.right - pos.left; + pos.height = pos.bottom - pos.top; + return catcher.watchPromise(browser.tabs.captureVisibleTab( + null, + {format: "png"} + ).then((dataUrl) => { + let image = new Image(); + image.src = dataUrl; + return new Promise((resolve, reject) => { + image.onload = catcher.watchFunction(() => { + let xScale = image.width / scroll.innerWidth; + let yScale = image.height / scroll.innerHeight; + let canvas = document.createElement("canvas"); + canvas.height = pos.height * yScale; + canvas.width = pos.width * xScale; + let context = canvas.getContext("2d"); + context.drawImage( + image, + pos.left * xScale, pos.top * yScale, + pos.width * xScale, pos.height * yScale, + 0, 0, + pos.width * xScale, pos.height * yScale + ); + let result = canvas.toDataURL(); + resolve(result); + }); + }); + })); + } + + function uploadShot(shot) { + return auth.authHeaders().then((headers) => { + headers["content-type"] = "application/json"; + let body = JSON.stringify(shot.asJson()); + let req = new Request(shot.jsonUrl, { + method: "PUT", + mode: "cors", + headers, + body + }); + sendEvent("upload", "started", {eventValue: Math.floor(body.length / 1000)}); + return fetch(req); + }).then((resp) => { + if (! resp.ok) { + sendEvent("upload-failed", `status-${resp.status}`); + let exc = new Error(`Response failed with status ${resp.status}`); + exc.popupMessage = "REQUEST_ERROR"; + throw exc; + } else { + sendEvent("upload", "success"); + } + }, (error) => { + // FIXME: I'm not sure what exceptions we can expect + sendEvent("upload-failed", "connection"); + throw error; + }); + } + + return exports; +})(); diff --git a/browser/extensions/screenshots/webextension/blank.html b/browser/extensions/screenshots/webextension/blank.html new file mode 100644 index 000000000000..18ecdcb795c3 --- /dev/null +++ b/browser/extensions/screenshots/webextension/blank.html @@ -0,0 +1 @@ + diff --git a/browser/extensions/screenshots/webextension/build/buildSettings.js b/browser/extensions/screenshots/webextension/build/buildSettings.js new file mode 100644 index 000000000000..37740afd0756 --- /dev/null +++ b/browser/extensions/screenshots/webextension/build/buildSettings.js @@ -0,0 +1,6 @@ +window.buildSettings = { + defaultSentryDsn: "", + logLevel: "" || "warn" +}; +null; + diff --git a/browser/extensions/screenshots/webextension/build/inlineSelectionCss.js b/browser/extensions/screenshots/webextension/build/inlineSelectionCss.js new file mode 100644 index 000000000000..155703a91a02 --- /dev/null +++ b/browser/extensions/screenshots/webextension/build/inlineSelectionCss.js @@ -0,0 +1,438 @@ +/* Created from build/server/static/css/inline-selection.css */ +window.inlineSelectionCss = ` +.button, .highlight-button-cancel, .highlight-button-save, .highlight-button-download { + display: flex; + align-items: center; + justify-content: center; + border: 0; + border-radius: 1px; + cursor: pointer; + font-size: 16px; + font-weight: 400; + height: 40px; + min-width: 40px; + outline: none; + padding: 0 10px; + position: relative; + text-align: center; + text-decoration: none; + transition: background 150ms; + user-select: none; + white-space: nowrap; } + .button.small, .small.highlight-button-cancel, .small.highlight-button-save, .small.highlight-button-download { + height: 32px; + line-height: 32px; + padding: 0 8px; } + .button.tiny, .tiny.highlight-button-cancel, .tiny.highlight-button-save, .tiny.highlight-button-download { + font-size: 12px; + height: 22px; + line-height: 12px; + padding: 2px 6px; } + .button.set-width--medium, .set-width--medium.highlight-button-cancel, .set-width--medium.highlight-button-save, .set-width--medium.highlight-button-download { + max-width: 200px; } + .button.inline, .inline.highlight-button-cancel, .inline.highlight-button-save, .inline.highlight-button-download { + display: inline-block; } + .button.block-button, .block-button.highlight-button-cancel, .block-button.highlight-button-save, .block-button.highlight-button-download { + display: flex; + align-items: center; + justify-content: center; + border: none; + border-right: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: none; + border-radius: 0; + height: 100%; + line-height: 100%; + padding: 0 20px; + margin-right: 20px; + flex: 0 0 155px; } + .button .arrow-icon, .highlight-button-cancel .arrow-icon, .highlight-button-save .arrow-icon, .highlight-button-download .arrow-icon { + display: inline-block; + position: relative; + top: 1px; + flex: 0 0 18px; + height: 16px; + opacity: .6; + background-image: url(../img/arrow-page-right-16.svg); + background-position: right center; + background-repeat: no-repeat; } + +.inverse-color-scheme { + background: #383E49; + color: #FFF; } + .inverse-color-scheme a { + color: #0996F8; } + .inverse-color-scheme .large-icon { + filter: invert(100%); } + +.default-color-scheme { + background: #f2f2f2; + color: #000; } + .default-color-scheme a { + color: #0996F8; } + +.button.primary, .primary.highlight-button-cancel, .highlight-button-save, .primary.highlight-button-download { + background-color: #0996F8; + color: #FFF; } + .button.primary:hover, .primary.highlight-button-cancel:hover, .highlight-button-save:hover, .primary.highlight-button-download:hover, .button.primary:focus, .primary.highlight-button-cancel:focus, .highlight-button-save:focus, .primary.highlight-button-download:focus { + background-color: #0681d7; } + .button.primary:active, .primary.highlight-button-cancel:active, .highlight-button-save:active, .primary.highlight-button-download:active { + background-color: #0573be; } + +.button.secondary, .highlight-button-cancel, .secondary.highlight-button-save, .highlight-button-download { + background: #EDEDED; + color: #000; } + .button.secondary:hover, .highlight-button-cancel:hover, .secondary.highlight-button-save:hover, .highlight-button-download:hover, .button.secondary:focus, .highlight-button-cancel:focus, .secondary.highlight-button-save:focus, .highlight-button-download:focus { + background-color: #dbdbdb; } + .button.secondary:active, .highlight-button-cancel:active, .secondary.highlight-button-save:active, .highlight-button-download:active { + background-color: #cecece; } + +.button.warning, .warning.highlight-button-cancel, .warning.highlight-button-save, .warning.highlight-button-download { + color: #FFF; + background: #d92215; } + .button.warning:hover, .warning.highlight-button-cancel:hover, .warning.highlight-button-save:hover, .warning.highlight-button-download:hover, .button.warning:focus, .warning.highlight-button-cancel:focus, .warning.highlight-button-save:focus, .warning.highlight-button-download:focus { + background: #b81d12; } + .button.warning:active, .warning.highlight-button-cancel:active, .warning.highlight-button-save:active, .warning.highlight-button-download:active { + background: #a11910; } + +.subtitle-link { + color: #0996F8; } + +@keyframes fade-in { + 0% { + opacity: 0; } + 100% { + opacity: 1; } } + +@keyframes pop { + 0% { + transform: scale(1); } + 97% { + transform: scale(1.04); } + 100% { + transform: scale(1); } } + +@keyframes pulse { + 0% { + opacity: .3; + transform: scale(1); } + 70% { + opacity: .25; + transform: scale(1.04); } + 100% { + opacity: .3; + transform: scale(1); } } + +@keyframes slide-left { + 0% { + opacity: 0; + transform: translate3d(160px, 0, 0); } + 100% { + opacity: 1; + transform: translate3d(0, 0, 0); } } + +.mover-target { + display: flex; + align-items: center; + justify-content: center; + pointer-events: auto; + position: absolute; + z-index: 5; } + +.highlight, +.mover-target { + background-color: transparent; + background-image: none; } + +.mover-target, +.bghighlight { + border: 0; } + +.hover-highlight { + animation: fade-in 125ms forwards cubic-bezier(0.07, 0.95, 0, 1); + background: rgba(255, 255, 255, 0.1); + border-radius: 1px; + pointer-events: none; + position: absolute; + z-index: 10000000000; } + +.mover-target.direction-topLeft { + cursor: nwse-resize; + height: 60px; + left: -30px; + top: -30px; + width: 60px; } + +.mover-target.direction-top { + cursor: ns-resize; + height: 60px; + left: 0; + top: -30px; + width: 100%; + z-index: 4; } + +.mover-target.direction-topRight { + cursor: nesw-resize; + height: 60px; + right: -30px; + top: -30px; + width: 60px; } + +.mover-target.direction-left { + cursor: ew-resize; + height: 100%; + left: -30px; + top: 0; + width: 60px; + z-index: 4; } + +.mover-target.direction-right { + cursor: ew-resize; + height: 100%; + right: -30px; + top: 0; + width: 60px; + z-index: 4; } + +.mover-target.direction-bottomLeft { + bottom: -30px; + cursor: nesw-resize; + height: 60px; + left: -30px; + width: 60px; } + +.mover-target.direction-bottom { + bottom: -30px; + cursor: ns-resize; + height: 60px; + left: 0; + width: 100%; + z-index: 4; } + +.mover-target.direction-bottomRight { + bottom: -30px; + cursor: nwse-resize; + height: 60px; + right: -30px; + width: 60px; } + +.mover-target:hover .mover { + transform: scale(1.05); } + +.mover { + background-color: #FFF; + border-radius: 50%; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.5); + height: 16px; + opacity: 1; + position: relative; + transition: transform 125ms cubic-bezier(0.07, 0.95, 0, 1); + width: 16px; } + .small-selection .mover { + height: 10px; + width: 10px; } + +.direction-topLeft .mover, +.direction-left .mover, +.direction-bottomLeft .mover { + left: -1px; } + +.direction-topLeft .mover, +.direction-top .mover, +.direction-topRight .mover { + top: -1px; } + +.direction-topRight .mover, +.direction-right .mover, +.direction-bottomRight .mover { + right: -1px; } + +.direction-bottomRight .mover, +.direction-bottom .mover, +.direction-bottomLeft .mover { + bottom: -1px; } + +.bghighlight { + background-color: rgba(0, 0, 0, 0.7); + position: absolute; + z-index: 9999999999; } + +.preview-overlay { + align-items: center; + background-color: rgba(0, 0, 0, 0.7); + display: flex; + height: 100%; + justify-content: center; + left: 0; + margin: 0; + padding: 0; + position: fixed; + top: 0; + width: 100%; + z-index: 9999999999; } + +.highlight { + border-radius: 2px; + border: 2px dashed rgba(255, 255, 255, 0.8); + box-sizing: border-box; + cursor: move; + position: absolute; + z-index: 9999999999; } + +.highlight-buttons { + display: flex; + align-items: center; + justify-content: center; + bottom: -55px; + position: absolute; + right: 5px; + z-index: 6; } + .bottom-selection .highlight-buttons { + bottom: 5px; } + +.highlight-button-cancel { + background-color: #ededed; + background-image: url("MOZ_EXTENSION/icons/cancel.svg"); + background-position: center center; + background-repeat: no-repeat; + background-size: 18px 18px; + margin: 5px; + width: 40px; } + +.highlight-button-save { + font-size: 18px; + margin: 5px; + min-width: 80px; } + +.highlight-button-download { + background-color: #ededed; + background-image: url("MOZ_EXTENSION/icons/download.svg"); + background-position: center center; + background-repeat: no-repeat; + background-size: 18px 18px; + display: block; + margin: 5px; + width: 40px; } + +.highlight-button-cancel, +.highlight-button-download { + transition: background-color cubic-bezier(0.07, 0.95, 0, 1) 250ms; } + .highlight-button-cancel:hover, .highlight-button-cancel:focus, .highlight-button-cancel:active, + .highlight-button-download:hover, + .highlight-button-download:focus, + .highlight-button-download:active { + background-color: #dbdbdb; } + +.pixel-dimensions { + position: absolute; + pointer-events: none; + font-weight: bold; + font-family: sans-serif; + font-size: 70%; + color: #000; + text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff; } + +.fixed-container { + align-items: center; + display: flex; + height: 100%; + justify-content: center; + left: 0; + margin: 0; + padding: 0; + pointer-events: none; + position: absolute; + top: 0; + width: 100%; } + +.preview-instructions { + display: flex; + align-items: center; + justify-content: center; + animation: pulse 125mm cubic-bezier(0.07, 0.95, 0, 1); + color: #fff; + font-family: -apple-system, BlinkMacSystemFont, sans-serif; + font-size: 24px; + line-height: 32px; + text-align: center; + width: 400px; } + +.myshots-all-buttons-container { + display: flex; + flex-direction: row-reverse; + background: #f5f5f5; + border-radius: 1px; + box-sizing: border-box; + height: 80px; + padding: 8px; + position: absolute; + right: 5px; + top: 5px; } + .myshots-all-buttons-container .spacer { + background-color: #c9c9c9; + flex: 0 0 1px; + height: 80px; + margin: 0 10px; + position: relative; + top: -8px; } + .myshots-all-buttons-container button { + display: flex; + align-items: center; + flex-direction: column; + justify-content: flex-end; + background-color: #f5f5f5; + background-position: center top; + background-repeat: no-repeat; + background-size: 46px 46px; + border: 1px solid transparent; + cursor: pointer; + height: 100%; + min-width: 90px; + padding: 46px 5px 5px; + pointer-events: all; + transition: border 150ms cubic-bezier(0.07, 0.95, 0, 1), background-color 150ms cubic-bezier(0.07, 0.95, 0, 1); + white-space: nowrap; } + .myshots-all-buttons-container button:hover { + background-color: #ebebeb; + border: 1px solid #c7c7c7; } + .myshots-all-buttons-container button:active { + background-color: #dedede; + border: 1px solid #989898; } + .myshots-all-buttons-container .myshots-button { + background-image: url("MOZ_EXTENSION/icons/menu-myshot.svg"); } + .myshots-all-buttons-container .full-page { + background-image: url("MOZ_EXTENSION/icons/menu-fullpage.svg"); } + .myshots-all-buttons-container .visible { + background-image: url("MOZ_EXTENSION/icons/menu-visible.svg"); } + +.myshots-button-container { + display: flex; + align-items: center; + justify-content: center; } + +/* styleMyShotsButton test: */ +.styleMyShotsButton-bright .myshots-button { + color: #fff; + background: #0996F8; } + +.styleMyShotsButton-bright .myshots-text-pre, +.styleMyShotsButton-bright .myshots-text-post { + filter: brightness(20); } + +/* end styleMyShotsButton test */ +@keyframes pulse { + 0% { + transform: scale(1); } + 50% { + transform: scale(1.06); } + 100% { + transform: scale(1); } } + +@keyframes fade-in { + 0% { + opacity: 0; } + 100% { + opacity: 1; } } + +`; +null; + diff --git a/browser/extensions/screenshots/webextension/build/onboardingCss.js b/browser/extensions/screenshots/webextension/build/onboardingCss.js new file mode 100644 index 000000000000..793b00cd4954 --- /dev/null +++ b/browser/extensions/screenshots/webextension/build/onboardingCss.js @@ -0,0 +1,244 @@ +/* Created from build/server/static/css/onboarding.css */ +window.onboardingCss = ` +@keyframes fade-in { + 0% { + opacity: 0; } + 100% { + opacity: 1; } } + +@keyframes pop { + 0% { + transform: scale(1); } + 97% { + transform: scale(1.04); } + 100% { + transform: scale(1); } } + +@keyframes pulse { + 0% { + opacity: .3; + transform: scale(1); } + 70% { + opacity: .25; + transform: scale(1.04); } + 100% { + opacity: .3; + transform: scale(1); } } + +@keyframes slide-left { + 0% { + opacity: 0; + transform: translate3d(160px, 0, 0); } + 100% { + opacity: 1; + transform: translate3d(0, 0, 0); } } + +html, +body { + box-sizing: border-box; + font-family: -apple-system, BlinkMacSystemFont, sans-serif; + height: 100%; + margin: 0; + width: 100%; } + +#slide-overlay { + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; + animation: fade-in 250ms forwards cubic-bezier(0.07, 0.95, 0, 1); + background: rgba(0, 0, 0, 0.8); + height: 100%; + opacity: 0; + width: 100%; } + +#slide-container { + animation-delay: 50ms; + animation: fade-in 250ms forwards cubic-bezier(0.07, 0.95, 0, 1); + opacity: 0; } + +.slide { + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; + background-color: #f2f2f2; + border-radius: 5px; + height: 520px; + overflow: hidden; + width: 700px; } + .slide .slide-image { + background-size: 700px 378px; + flex: 0 0 360px; + font-size: 16px; + width: 100%; } + .slide .slide-content { + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; + box-sizing: border-box; + flex: 0 0 160px; + padding: 5px; + text-align: center; } + .slide h1 { + font-size: 30px; + font-weight: 400; + margin: 0 0 10px; } + .slide h1 sup { + background: #00d1e6; + border-radius: 2px; + color: #fff; + font-size: 16px; + margin-left: 5px; + padding: 2px; + text-transform: uppercase; } + .slide p { + animation-duration: 350ms; + font-size: 16px; + line-height: 23px; + margin: 0; + width: 75%; } + .slide .slide-content-aligner h1 { + font-size: 34px; } + .slide .slide-content-aligner p { + margin: 0 auto; } + .slide .onboarding-legal-notice { + font-size: 12px; + color: #858585; } + .slide .onboarding-legal-notice a { + color: #009ec0; + text-decoration: none; } + .slide:not(.slide-1) h1 { + opacity: 0; + transform: translate3d(160px, 0, 0); + animation: slide-left 500ms forwards cubic-bezier(0.07, 0.95, 0, 1); } + .slide:not(.slide-1) p { + opacity: 0; + transform: translate3d(160px, 0, 0); + animation: slide-left 600ms forwards cubic-bezier(0.07, 0.95, 0, 1); } + .slide:not(.slide-1) .slide-image { + background-color: #00d1e6; } + .slide.slide-1 { + background: #fff; } + .slide.slide-1 .slide-content { + justify-content: space-between; + width: 100%; } + +.slide-1, +.slide-2, +.slide-3, +.slide-4, +.slide-5 { + display: none; } + +.active-slide-1 .slide-1, +.active-slide-2 .slide-2, +.active-slide-3 .slide-3, +.active-slide-4 .slide-4 { + display: flex; } + +#slide-status-container { + display: flex; + align-items: center; + justify-content: center; + padding-top: 15px; } + +.goto-slide { + background: transparent; + background-color: #f2f2f2; + border-radius: 50%; + border: 0; + flex: 0 0 9px; + height: 9px; + margin: 0 4px; + opacity: 0.7; + padding: 0; + transition: height 100ms cubic-bezier(0.07, 0.95, 0, 1), opacity 100ms cubic-bezier(0.07, 0.95, 0, 1); } + +.goto-slide:hover { + opacity: 1; } + +.active-slide-1 .goto-slide-1, +.active-slide-2 .goto-slide-2, +.active-slide-3 .goto-slide-3, +.active-slide-4 .goto-slide-4 { + opacity: 1; + transform: scale(1.1); } + +#prev, #next, +#done { + background-color: #f0f0f0; + border-radius: 50%; + border: 0; + box-shadow: 0 0 12px rgba(0, 0, 0, 0.2); + display: inline-block; + height: 70px; + margin-top: -70px; + position: absolute; + text-align: center; + top: 50%; + transition: background-color 150ms cubic-bezier(0.07, 0.95, 0, 1), background-size 250ms cubic-bezier(0.07, 0.95, 0, 1); + width: 70px; } + +#prev { + left: 50%; + margin-left: -385px; } + +#next, +#done { + left: 50%; + margin-left: 315px; } + +#prev, +#next, +#done { + background-position: center center; + background-repeat: no-repeat; + background-size: 20px 20px; } + +#next { + transform: rotate(180deg); } + +#skip { + background: none; + border: 0; + color: #fff; + font-size: 16px; + left: 50%; + margin-left: -330px; + margin-top: 257px; + opacity: 0.7; + position: absolute; + top: 50%; + transition: opacity 100ms cubic-bezier(0.07, 0.95, 0, 1); + z-index: 10; } + +#prev:hover, +#next:hover, +#done:hover { + background-color: #fff; + background-size: 22px 22px; } + +#prev:active, +#next:active, +#done:active { + background-color: #fff; + background-size: 24px 24px; } + +#skip:hover { + opacity: 1; } + +.active-slide-1 #prev, +.active-slide-4 #next { + display: none; } + +#done { + display: none; } + +.active-slide-4 #done { + display: inline-block; } + +`; +null; + diff --git a/browser/extensions/screenshots/webextension/build/onboardingHtml.js b/browser/extensions/screenshots/webextension/build/onboardingHtml.js new file mode 100644 index 000000000000..4af0ae5d1291 --- /dev/null +++ b/browser/extensions/screenshots/webextension/build/onboardingHtml.js @@ -0,0 +1,66 @@ +/* Created from addon/webextension/onboarding/slides.html */ +window.onboardingHtml = ` + + + + + + + + +
+ +
+
+ +
+
+
+

Firefox ScreenshotsBeta

+

+
+ +
+
+
+
+
+

+

+
+
+
+
+
+

+

+
+
+
+
+
+

+

+
+
+ + + + + + +
+ + + + +
+ +
+
+ + + +`; +null; + diff --git a/browser/extensions/screenshots/webextension/build/raven.js b/browser/extensions/screenshots/webextension/build/raven.js new file mode 100644 index 000000000000..4d3bf5cfad14 --- /dev/null +++ b/browser/extensions/screenshots/webextension/build/raven.js @@ -0,0 +1,2833 @@ +/*! Raven.js 3.14.0 (6b817d7) | github.com/getsentry/raven-js */ + +/* + * Includes TraceKit + * https://github.com/getsentry/TraceKit + * + * Copyright 2017 Matt Robenolt and other contributors + * Released under the BSD license + * https://github.com/getsentry/raven-js/blob/master/LICENSE + * + */ + +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Raven = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o this._globalOptions.maxBreadcrumbs) { + this._breadcrumbs.shift(); + } + return this; + }, + + addPlugin: function(plugin /*arg1, arg2, ... argN*/) { + var pluginArgs = [].slice.call(arguments, 1); + + this._plugins.push([plugin, pluginArgs]); + if (this._isRavenInstalled) { + this._drainPlugins(); + } + + return this; + }, + + /* + * Set/clear a user to be sent along with the payload. + * + * @param {object} user An object representing user data [optional] + * @return {Raven} + */ + setUserContext: function(user) { + // Intentionally do not merge here since that's an unexpected behavior. + this._globalContext.user = user; + + return this; + }, + + /* + * Merge extra attributes to be sent along with the payload. + * + * @param {object} extra An object representing extra data [optional] + * @return {Raven} + */ + setExtraContext: function(extra) { + this._mergeContext('extra', extra); + + return this; + }, + + /* + * Merge tags to be sent along with the payload. + * + * @param {object} tags An object representing tags [optional] + * @return {Raven} + */ + setTagsContext: function(tags) { + this._mergeContext('tags', tags); + + return this; + }, + + /* + * Clear all of the context. + * + * @return {Raven} + */ + clearContext: function() { + this._globalContext = {}; + + return this; + }, + + /* + * Get a copy of the current context. This cannot be mutated. + * + * @return {object} copy of context + */ + getContext: function() { + // lol javascript + return JSON.parse(stringify(this._globalContext)); + }, + + + /* + * Set environment of application + * + * @param {string} environment Typically something like 'production'. + * @return {Raven} + */ + setEnvironment: function(environment) { + this._globalOptions.environment = environment; + + return this; + }, + + /* + * Set release version of application + * + * @param {string} release Typically something like a git SHA to identify version + * @return {Raven} + */ + setRelease: function(release) { + this._globalOptions.release = release; + + return this; + }, + + /* + * Set the dataCallback option + * + * @param {function} callback The callback to run which allows the + * data blob to be mutated before sending + * @return {Raven} + */ + setDataCallback: function(callback) { + var original = this._globalOptions.dataCallback; + this._globalOptions.dataCallback = isFunction(callback) + ? function (data) { return callback(data, original); } + : callback; + + return this; + }, + + /* + * Set the breadcrumbCallback option + * + * @param {function} callback The callback to run which allows filtering + * or mutating breadcrumbs + * @return {Raven} + */ + setBreadcrumbCallback: function(callback) { + var original = this._globalOptions.breadcrumbCallback; + this._globalOptions.breadcrumbCallback = isFunction(callback) + ? function (data) { return callback(data, original); } + : callback; + + return this; + }, + + /* + * Set the shouldSendCallback option + * + * @param {function} callback The callback to run which allows + * introspecting the blob before sending + * @return {Raven} + */ + setShouldSendCallback: function(callback) { + var original = this._globalOptions.shouldSendCallback; + this._globalOptions.shouldSendCallback = isFunction(callback) + ? function (data) { return callback(data, original); } + : callback; + + return this; + }, + + /** + * Override the default HTTP transport mechanism that transmits data + * to the Sentry server. + * + * @param {function} transport Function invoked instead of the default + * `makeRequest` handler. + * + * @return {Raven} + */ + setTransport: function(transport) { + this._globalOptions.transport = transport; + + return this; + }, + + /* + * Get the latest raw exception that was captured by Raven. + * + * @return {error} + */ + lastException: function() { + return this._lastCapturedException; + }, + + /* + * Get the last event id + * + * @return {string} + */ + lastEventId: function() { + return this._lastEventId; + }, + + /* + * Determine if Raven is setup and ready to go. + * + * @return {boolean} + */ + isSetup: function() { + if (!this._hasJSON) return false; // needs JSON support + if (!this._globalServer) { + if (!this.ravenNotConfiguredError) { + this.ravenNotConfiguredError = true; + this._logDebug('error', 'Error: Raven has not been configured.'); + } + return false; + } + return true; + }, + + afterLoad: function () { + // TODO: remove window dependence? + + // Attempt to initialize Raven on load + var RavenConfig = _window.RavenConfig; + if (RavenConfig) { + this.config(RavenConfig.dsn, RavenConfig.config).install(); + } + }, + + showReportDialog: function (options) { + if (!_document) // doesn't work without a document (React native) + return; + + options = options || {}; + + var lastEventId = options.eventId || this.lastEventId(); + if (!lastEventId) { + throw new RavenConfigError('Missing eventId'); + } + + var dsn = options.dsn || this._dsn; + if (!dsn) { + throw new RavenConfigError('Missing DSN'); + } + + var encode = encodeURIComponent; + var qs = ''; + qs += '?eventId=' + encode(lastEventId); + qs += '&dsn=' + encode(dsn); + + var user = options.user || this._globalContext.user; + if (user) { + if (user.name) qs += '&name=' + encode(user.name); + if (user.email) qs += '&email=' + encode(user.email); + } + + var globalServer = this._getGlobalServer(this._parseDSN(dsn)); + + var script = _document.createElement('script'); + script.async = true; + script.src = globalServer + '/api/embed/error-page/' + qs; + (_document.head || _document.body).appendChild(script); + }, + + /**** Private functions ****/ + _ignoreNextOnError: function () { + var self = this; + this._ignoreOnError += 1; + setTimeout(function () { + // onerror should trigger before setTimeout + self._ignoreOnError -= 1; + }); + }, + + _triggerEvent: function(eventType, options) { + // NOTE: `event` is a native browser thing, so let's avoid conflicting wiht it + var evt, key; + + if (!this._hasDocument) + return; + + options = options || {}; + + eventType = 'raven' + eventType.substr(0,1).toUpperCase() + eventType.substr(1); + + if (_document.createEvent) { + evt = _document.createEvent('HTMLEvents'); + evt.initEvent(eventType, true, true); + } else { + evt = _document.createEventObject(); + evt.eventType = eventType; + } + + for (key in options) if (hasKey(options, key)) { + evt[key] = options[key]; + } + + if (_document.createEvent) { + // IE9 if standards + _document.dispatchEvent(evt); + } else { + // IE8 regardless of Quirks or Standards + // IE9 if quirks + try { + _document.fireEvent('on' + evt.eventType.toLowerCase(), evt); + } catch(e) { + // Do nothing + } + } + }, + + /** + * Wraps addEventListener to capture UI breadcrumbs + * @param evtName the event name (e.g. "click") + * @returns {Function} + * @private + */ + _breadcrumbEventHandler: function(evtName) { + var self = this; + return function (evt) { + // reset keypress timeout; e.g. triggering a 'click' after + // a 'keypress' will reset the keypress debounce so that a new + // set of keypresses can be recorded + self._keypressTimeout = null; + + // It's possible this handler might trigger multiple times for the same + // event (e.g. event propagation through node ancestors). Ignore if we've + // already captured the event. + if (self._lastCapturedEvent === evt) + return; + + self._lastCapturedEvent = evt; + + // try/catch both: + // - accessing evt.target (see getsentry/raven-js#838, #768) + // - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly + // can throw an exception in some circumstances. + var target; + try { + target = htmlTreeAsString(evt.target); + } catch (e) { + target = ''; + } + + self.captureBreadcrumb({ + category: 'ui.' + evtName, // e.g. ui.click, ui.input + message: target + }); + }; + }, + + /** + * Wraps addEventListener to capture keypress UI events + * @returns {Function} + * @private + */ + _keypressEventHandler: function() { + var self = this, + debounceDuration = 1000; // milliseconds + + // TODO: if somehow user switches keypress target before + // debounce timeout is triggered, we will only capture + // a single breadcrumb from the FIRST target (acceptable?) + return function (evt) { + var target; + try { + target = evt.target; + } catch (e) { + // just accessing event properties can throw an exception in some rare circumstances + // see: https://github.com/getsentry/raven-js/issues/838 + return; + } + var tagName = target && target.tagName; + + // only consider keypress events on actual input elements + // this will disregard keypresses targeting body (e.g. tabbing + // through elements, hotkeys, etc) + if (!tagName || tagName !== 'INPUT' && tagName !== 'TEXTAREA' && !target.isContentEditable) + return; + + // record first keypress in a series, but ignore subsequent + // keypresses until debounce clears + var timeout = self._keypressTimeout; + if (!timeout) { + self._breadcrumbEventHandler('input')(evt); + } + clearTimeout(timeout); + self._keypressTimeout = setTimeout(function () { + self._keypressTimeout = null; + }, debounceDuration); + }; + }, + + /** + * Captures a breadcrumb of type "navigation", normalizing input URLs + * @param to the originating URL + * @param from the target URL + * @private + */ + _captureUrlChange: function(from, to) { + var parsedLoc = parseUrl(this._location.href); + var parsedTo = parseUrl(to); + var parsedFrom = parseUrl(from); + + // because onpopstate only tells you the "new" (to) value of location.href, and + // not the previous (from) value, we need to track the value of the current URL + // state ourselves + this._lastHref = to; + + // Use only the path component of the URL if the URL matches the current + // document (almost all the time when using pushState) + if (parsedLoc.protocol === parsedTo.protocol && parsedLoc.host === parsedTo.host) + to = parsedTo.relative; + if (parsedLoc.protocol === parsedFrom.protocol && parsedLoc.host === parsedFrom.host) + from = parsedFrom.relative; + + this.captureBreadcrumb({ + category: 'navigation', + data: { + to: to, + from: from + } + }); + }, + + /** + * Install any queued plugins + */ + _instrumentTryCatch: function() { + var self = this; + + var wrappedBuiltIns = self._wrappedBuiltIns; + + function wrapTimeFn(orig) { + return function (fn, t) { // preserve arity + // Make a copy of the arguments to prevent deoptimization + // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments + var args = new Array(arguments.length); + for(var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + var originalCallback = args[0]; + if (isFunction(originalCallback)) { + args[0] = self.wrap(originalCallback); + } + + // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it + // also supports only two arguments and doesn't care what this is, so we + // can just call the original function directly. + if (orig.apply) { + return orig.apply(this, args); + } else { + return orig(args[0], args[1]); + } + }; + } + + var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs; + + function wrapEventTarget(global) { + var proto = _window[global] && _window[global].prototype; + if (proto && proto.hasOwnProperty && proto.hasOwnProperty('addEventListener')) { + fill(proto, 'addEventListener', function(orig) { + return function (evtName, fn, capture, secure) { // preserve arity + try { + if (fn && fn.handleEvent) { + fn.handleEvent = self.wrap(fn.handleEvent); + } + } catch (err) { + // can sometimes get 'Permission denied to access property "handle Event' + } + + // More breadcrumb DOM capture ... done here and not in `_instrumentBreadcrumbs` + // so that we don't have more than one wrapper function + var before, + clickHandler, + keypressHandler; + + if (autoBreadcrumbs && autoBreadcrumbs.dom && (global === 'EventTarget' || global === 'Node')) { + // NOTE: generating multiple handlers per addEventListener invocation, should + // revisit and verify we can just use one (almost certainly) + clickHandler = self._breadcrumbEventHandler('click'); + keypressHandler = self._keypressEventHandler(); + before = function (evt) { + // need to intercept every DOM event in `before` argument, in case that + // same wrapped method is re-used for different events (e.g. mousemove THEN click) + // see #724 + if (!evt) return; + + var eventType; + try { + eventType = evt.type + } catch (e) { + // just accessing event properties can throw an exception in some rare circumstances + // see: https://github.com/getsentry/raven-js/issues/838 + return; + } + if (eventType === 'click') + return clickHandler(evt); + else if (eventType === 'keypress') + return keypressHandler(evt); + }; + } + return orig.call(this, evtName, self.wrap(fn, undefined, before), capture, secure); + }; + }, wrappedBuiltIns); + fill(proto, 'removeEventListener', function (orig) { + return function (evt, fn, capture, secure) { + try { + fn = fn && (fn.__raven_wrapper__ ? fn.__raven_wrapper__ : fn); + } catch (e) { + // ignore, accessing __raven_wrapper__ will throw in some Selenium environments + } + return orig.call(this, evt, fn, capture, secure); + }; + }, wrappedBuiltIns); + } + } + + fill(_window, 'setTimeout', wrapTimeFn, wrappedBuiltIns); + fill(_window, 'setInterval', wrapTimeFn, wrappedBuiltIns); + if (_window.requestAnimationFrame) { + fill(_window, 'requestAnimationFrame', function (orig) { + return function (cb) { + return orig(self.wrap(cb)); + }; + }, wrappedBuiltIns); + } + + // event targets borrowed from bugsnag-js: + // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L666 + var eventTargets = ['EventTarget', 'Window', 'Node', 'ApplicationCache', 'AudioTrackList', 'ChannelMergerNode', 'CryptoOperation', 'EventSource', 'FileReader', 'HTMLUnknownElement', 'IDBDatabase', 'IDBRequest', 'IDBTransaction', 'KeyOperation', 'MediaController', 'MessagePort', 'ModalWindow', 'Notification', 'SVGElementInstance', 'Screen', 'TextTrack', 'TextTrackCue', 'TextTrackList', 'WebSocket', 'WebSocketWorker', 'Worker', 'XMLHttpRequest', 'XMLHttpRequestEventTarget', 'XMLHttpRequestUpload']; + for (var i = 0; i < eventTargets.length; i++) { + wrapEventTarget(eventTargets[i]); + } + }, + + + /** + * Instrument browser built-ins w/ breadcrumb capturing + * - XMLHttpRequests + * - DOM interactions (click/typing) + * - window.location changes + * - console + * + * Can be disabled or individually configured via the `autoBreadcrumbs` config option + */ + _instrumentBreadcrumbs: function () { + var self = this; + var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs; + + var wrappedBuiltIns = self._wrappedBuiltIns; + + function wrapProp(prop, xhr) { + if (prop in xhr && isFunction(xhr[prop])) { + fill(xhr, prop, function (orig) { + return self.wrap(orig); + }); // intentionally don't track filled methods on XHR instances + } + } + + if (autoBreadcrumbs.xhr && 'XMLHttpRequest' in _window) { + var xhrproto = XMLHttpRequest.prototype; + fill(xhrproto, 'open', function(origOpen) { + return function (method, url) { // preserve arity + + // if Sentry key appears in URL, don't capture + if (isString(url) && url.indexOf(self._globalKey) === -1) { + this.__raven_xhr = { + method: method, + url: url, + status_code: null + }; + } + + return origOpen.apply(this, arguments); + }; + }, wrappedBuiltIns); + + fill(xhrproto, 'send', function(origSend) { + return function (data) { // preserve arity + var xhr = this; + + function onreadystatechangeHandler() { + if (xhr.__raven_xhr && (xhr.readyState === 1 || xhr.readyState === 4)) { + try { + // touching statusCode in some platforms throws + // an exception + xhr.__raven_xhr.status_code = xhr.status; + } catch (e) { /* do nothing */ } + self.captureBreadcrumb({ + type: 'http', + category: 'xhr', + data: xhr.__raven_xhr + }); + } + } + + var props = ['onload', 'onerror', 'onprogress']; + for (var j = 0; j < props.length; j++) { + wrapProp(props[j], xhr); + } + + if ('onreadystatechange' in xhr && isFunction(xhr.onreadystatechange)) { + fill(xhr, 'onreadystatechange', function (orig) { + return self.wrap(orig, undefined, onreadystatechangeHandler); + } /* intentionally don't track this instrumentation */); + } else { + // if onreadystatechange wasn't actually set by the page on this xhr, we + // are free to set our own and capture the breadcrumb + xhr.onreadystatechange = onreadystatechangeHandler; + } + + return origSend.apply(this, arguments); + }; + }, wrappedBuiltIns); + } + + if (autoBreadcrumbs.xhr && 'fetch' in _window) { + fill(_window, 'fetch', function(origFetch) { + return function (fn, t) { // preserve arity + // Make a copy of the arguments to prevent deoptimization + // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments + var args = new Array(arguments.length); + for(var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + + var method = 'GET'; + + if (args[1] && args[1].method) { + method = args[1].method; + } + + var fetchData = { + method: method, + url: args[0], + status_code: null + }; + + self.captureBreadcrumb({ + type: 'http', + category: 'fetch', + data: fetchData + }); + + return origFetch.apply(this, args).then(function (response) { + fetchData.status_code = response.status; + + return response; + }); + }; + }, wrappedBuiltIns); + } + + // Capture breadcrumbs from any click that is unhandled / bubbled up all the way + // to the document. Do this before we instrument addEventListener. + if (autoBreadcrumbs.dom && this._hasDocument) { + if (_document.addEventListener) { + _document.addEventListener('click', self._breadcrumbEventHandler('click'), false); + _document.addEventListener('keypress', self._keypressEventHandler(), false); + } + else { + // IE8 Compatibility + _document.attachEvent('onclick', self._breadcrumbEventHandler('click')); + _document.attachEvent('onkeypress', self._keypressEventHandler()); + } + } + + // record navigation (URL) changes + // NOTE: in Chrome App environment, touching history.pushState, *even inside + // a try/catch block*, will cause Chrome to output an error to console.error + // borrowed from: https://github.com/angular/angular.js/pull/13945/files + var chrome = _window.chrome; + var isChromePackagedApp = chrome && chrome.app && chrome.app.runtime; + var hasPushState = !isChromePackagedApp && _window.history && history.pushState; + if (autoBreadcrumbs.location && hasPushState) { + // TODO: remove onpopstate handler on uninstall() + var oldOnPopState = _window.onpopstate; + _window.onpopstate = function () { + var currentHref = self._location.href; + self._captureUrlChange(self._lastHref, currentHref); + + if (oldOnPopState) { + return oldOnPopState.apply(this, arguments); + } + }; + + fill(history, 'pushState', function (origPushState) { + // note history.pushState.length is 0; intentionally not declaring + // params to preserve 0 arity + return function (/* state, title, url */) { + var url = arguments.length > 2 ? arguments[2] : undefined; + + // url argument is optional + if (url) { + // coerce to string (this is what pushState does) + self._captureUrlChange(self._lastHref, url + ''); + } + + return origPushState.apply(this, arguments); + }; + }, wrappedBuiltIns); + } + + if (autoBreadcrumbs.console && 'console' in _window && console.log) { + // console + var consoleMethodCallback = function (msg, data) { + self.captureBreadcrumb({ + message: msg, + level: data.level, + category: 'console' + }); + }; + + each(['debug', 'info', 'warn', 'error', 'log'], function (_, level) { + wrapConsoleMethod(console, level, consoleMethodCallback); + }); + } + + }, + + _restoreBuiltIns: function () { + // restore any wrapped builtins + var builtin; + while (this._wrappedBuiltIns.length) { + builtin = this._wrappedBuiltIns.shift(); + + var obj = builtin[0], + name = builtin[1], + orig = builtin[2]; + + obj[name] = orig; + } + }, + + _drainPlugins: function() { + var self = this; + + // FIX ME TODO + each(this._plugins, function(_, plugin) { + var installer = plugin[0]; + var args = plugin[1]; + installer.apply(self, [self].concat(args)); + }); + }, + + _parseDSN: function(str) { + var m = dsnPattern.exec(str), + dsn = {}, + i = 7; + + try { + while (i--) dsn[dsnKeys[i]] = m[i] || ''; + } catch(e) { + throw new RavenConfigError('Invalid DSN: ' + str); + } + + if (dsn.pass && !this._globalOptions.allowSecretKey) { + throw new RavenConfigError('Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key'); + } + + return dsn; + }, + + _getGlobalServer: function(uri) { + // assemble the endpoint from the uri pieces + var globalServer = '//' + uri.host + + (uri.port ? ':' + uri.port : ''); + + if (uri.protocol) { + globalServer = uri.protocol + ':' + globalServer; + } + return globalServer; + }, + + _handleOnErrorStackInfo: function() { + // if we are intentionally ignoring errors via onerror, bail out + if (!this._ignoreOnError) { + this._handleStackInfo.apply(this, arguments); + } + }, + + _handleStackInfo: function(stackInfo, options) { + var frames = this._prepareFrames(stackInfo, options); + + this._triggerEvent('handle', { + stackInfo: stackInfo, + options: options + }); + + this._processException( + stackInfo.name, + stackInfo.message, + stackInfo.url, + stackInfo.lineno, + frames, + options + ); + }, + + _prepareFrames: function(stackInfo, options) { + var self = this; + var frames = []; + if (stackInfo.stack && stackInfo.stack.length) { + each(stackInfo.stack, function(i, stack) { + var frame = self._normalizeFrame(stack); + if (frame) { + frames.push(frame); + } + }); + + // e.g. frames captured via captureMessage throw + if (options && options.trimHeadFrames) { + for (var j = 0; j < options.trimHeadFrames && j < frames.length; j++) { + frames[j].in_app = false; + } + } + } + frames = frames.slice(0, this._globalOptions.stackTraceLimit); + return frames; + }, + + + _normalizeFrame: function(frame) { + if (!frame.url) return; + + // normalize the frames data + var normalized = { + filename: frame.url, + lineno: frame.line, + colno: frame.column, + 'function': frame.func || '?' + }; + + normalized.in_app = !( // determine if an exception came from outside of our app + // first we check the global includePaths list. + !!this._globalOptions.includePaths.test && !this._globalOptions.includePaths.test(normalized.filename) || + // Now we check for fun, if the function name is Raven or TraceKit + /(Raven|TraceKit)\./.test(normalized['function']) || + // finally, we do a last ditch effort and check for raven.min.js + /raven\.(min\.)?js$/.test(normalized.filename) + ); + + return normalized; + }, + + _processException: function(type, message, fileurl, lineno, frames, options) { + var stacktrace; + if (!!this._globalOptions.ignoreErrors.test && this._globalOptions.ignoreErrors.test(message)) return; + + message += ''; + + if (frames && frames.length) { + fileurl = frames[0].filename || fileurl; + // Sentry expects frames oldest to newest + // and JS sends them as newest to oldest + frames.reverse(); + stacktrace = {frames: frames}; + } else if (fileurl) { + stacktrace = { + frames: [{ + filename: fileurl, + lineno: lineno, + in_app: true + }] + }; + } + + if (!!this._globalOptions.ignoreUrls.test && this._globalOptions.ignoreUrls.test(fileurl)) return; + if (!!this._globalOptions.whitelistUrls.test && !this._globalOptions.whitelistUrls.test(fileurl)) return; + + var data = objectMerge({ + // sentry.interfaces.Exception + exception: { + values: [{ + type: type, + value: message, + stacktrace: stacktrace + }] + }, + culprit: fileurl + }, options); + + // Fire away! + this._send(data); + }, + + _trimPacket: function(data) { + // For now, we only want to truncate the two different messages + // but this could/should be expanded to just trim everything + var max = this._globalOptions.maxMessageLength; + if (data.message) { + data.message = truncate(data.message, max); + } + if (data.exception) { + var exception = data.exception.values[0]; + exception.value = truncate(exception.value, max); + } + + var request = data.request; + if (request) { + if (request.url) { + request.url = truncate(request.url, this._globalOptions.maxUrlLength); + } + if (request.Referer) { + request.Referer = truncate(request.Referer, this._globalOptions.maxUrlLength); + } + } + + if (data.breadcrumbs && data.breadcrumbs.values) + this._trimBreadcrumbs(data.breadcrumbs); + + return data; + }, + + /** + * Truncate breadcrumb values (right now just URLs) + */ + _trimBreadcrumbs: function (breadcrumbs) { + // known breadcrumb properties with urls + // TODO: also consider arbitrary prop values that start with (https?)?:// + var urlprops = {to: 1, from: 1, url: 1}, + crumb, + data; + + for (var i = 0; i < breadcrumbs.values.length; i++) { + crumb = breadcrumbs.values[i]; + if (!crumb.hasOwnProperty('data')) + continue; + + data = crumb.data; + for (var prop in urlprops) { + if (data.hasOwnProperty(prop)) { + data[prop] = truncate(data[prop], this._globalOptions.maxUrlLength); + } + } + } + }, + + _getHttpData: function() { + if (!this._hasNavigator && !this._hasDocument) return; + var httpData = {}; + + if (this._hasNavigator && _navigator.userAgent) { + httpData.headers = { + 'User-Agent': navigator.userAgent + }; + } + + if (this._hasDocument) { + if (_document.location && _document.location.href) { + httpData.url = _document.location.href; + } + if (_document.referrer) { + if (!httpData.headers) httpData.headers = {}; + httpData.headers.Referer = _document.referrer; + } + } + + return httpData; + }, + + _resetBackoff: function() { + this._backoffDuration = 0; + this._backoffStart = null; + }, + + _shouldBackoff: function() { + return this._backoffDuration && now() - this._backoffStart < this._backoffDuration; + }, + + /** + * Returns true if the in-process data payload matches the signature + * of the previously-sent data + * + * NOTE: This has to be done at this level because TraceKit can generate + * data from window.onerror WITHOUT an exception object (IE8, IE9, + * other old browsers). This can take the form of an "exception" + * data object with a single frame (derived from the onerror args). + */ + _isRepeatData: function (current) { + var last = this._lastData; + + if (!last || + current.message !== last.message || // defined for captureMessage + current.culprit !== last.culprit) // defined for captureException/onerror + return false; + + // Stacktrace interface (i.e. from captureMessage) + if (current.stacktrace || last.stacktrace) { + return isSameStacktrace(current.stacktrace, last.stacktrace); + } + // Exception interface (i.e. from captureException/onerror) + else if (current.exception || last.exception) { + return isSameException(current.exception, last.exception); + } + + return true; + }, + + _setBackoffState: function(request) { + // If we are already in a backoff state, don't change anything + if (this._shouldBackoff()) { + return; + } + + var status = request.status; + + // 400 - project_id doesn't exist or some other fatal + // 401 - invalid/revoked dsn + // 429 - too many requests + if (!(status === 400 || status === 401 || status === 429)) + return; + + var retry; + try { + // If Retry-After is not in Access-Control-Expose-Headers, most + // browsers will throw an exception trying to access it + retry = request.getResponseHeader('Retry-After'); + retry = parseInt(retry, 10) * 1000; // Retry-After is returned in seconds + } catch (e) { + /* eslint no-empty:0 */ + } + + + this._backoffDuration = retry + // If Sentry server returned a Retry-After value, use it + ? retry + // Otherwise, double the last backoff duration (starts at 1 sec) + : this._backoffDuration * 2 || 1000; + + this._backoffStart = now(); + }, + + _send: function(data) { + var globalOptions = this._globalOptions; + + var baseData = { + project: this._globalProject, + logger: globalOptions.logger, + platform: 'javascript' + }, httpData = this._getHttpData(); + + if (httpData) { + baseData.request = httpData; + } + + // HACK: delete `trimHeadFrames` to prevent from appearing in outbound payload + if (data.trimHeadFrames) delete data.trimHeadFrames; + + data = objectMerge(baseData, data); + + // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge + data.tags = objectMerge(objectMerge({}, this._globalContext.tags), data.tags); + data.extra = objectMerge(objectMerge({}, this._globalContext.extra), data.extra); + + // Send along our own collected metadata with extra + data.extra['session:duration'] = now() - this._startTime; + + if (this._breadcrumbs && this._breadcrumbs.length > 0) { + // intentionally make shallow copy so that additions + // to breadcrumbs aren't accidentally sent in this request + data.breadcrumbs = { + values: [].slice.call(this._breadcrumbs, 0) + }; + } + + // If there are no tags/extra, strip the key from the payload alltogther. + if (isEmptyObject(data.tags)) delete data.tags; + + if (this._globalContext.user) { + // sentry.interfaces.User + data.user = this._globalContext.user; + } + + // Include the environment if it's defined in globalOptions + if (globalOptions.environment) data.environment = globalOptions.environment; + + // Include the release if it's defined in globalOptions + if (globalOptions.release) data.release = globalOptions.release; + + // Include server_name if it's defined in globalOptions + if (globalOptions.serverName) data.server_name = globalOptions.serverName; + + if (isFunction(globalOptions.dataCallback)) { + data = globalOptions.dataCallback(data) || data; + } + + // Why?????????? + if (!data || isEmptyObject(data)) { + return; + } + + // Check if the request should be filtered or not + if (isFunction(globalOptions.shouldSendCallback) && !globalOptions.shouldSendCallback(data)) { + return; + } + + // Backoff state: Sentry server previously responded w/ an error (e.g. 429 - too many requests), + // so drop requests until "cool-off" period has elapsed. + if (this._shouldBackoff()) { + this._logDebug('warn', 'Raven dropped error due to backoff: ', data); + return; + } + + if (typeof globalOptions.sampleRate === 'number') { + if (Math.random() < globalOptions.sampleRate) { + this._sendProcessedPayload(data); + } + } else { + this._sendProcessedPayload(data); + } + }, + + _getUuid: function () { + return uuid4(); + }, + + _sendProcessedPayload: function(data, callback) { + var self = this; + var globalOptions = this._globalOptions; + + if (!this.isSetup()) return; + + // Send along an event_id if not explicitly passed. + // This event_id can be used to reference the error within Sentry itself. + // Set lastEventId after we know the error should actually be sent + this._lastEventId = data.event_id || (data.event_id = this._getUuid()); + + // Try and clean up the packet before sending by truncating long values + data = this._trimPacket(data); + + // ideally duplicate error testing should occur *before* dataCallback/shouldSendCallback, + // but this would require copying an un-truncated copy of the data packet, which can be + // arbitrarily deep (extra_data) -- could be worthwhile? will revisit + if (!this._globalOptions.allowDuplicates && this._isRepeatData(data)) { + this._logDebug('warn', 'Raven dropped repeat event: ', data); + return; + } + + // Store outbound payload after trim + this._lastData = data; + + this._logDebug('debug', 'Raven about to send:', data); + + var auth = { + sentry_version: '7', + sentry_client: 'raven-js/' + this.VERSION, + sentry_key: this._globalKey + }; + if (this._globalSecret) { + auth.sentry_secret = this._globalSecret; + } + + var exception = data.exception && data.exception.values[0]; + this.captureBreadcrumb({ + category: 'sentry', + message: exception + ? (exception.type ? exception.type + ': ' : '') + exception.value + : data.message, + event_id: data.event_id, + level: data.level || 'error' // presume error unless specified + }); + + var url = this._globalEndpoint; + (globalOptions.transport || this._makeRequest).call(this, { + url: url, + auth: auth, + data: data, + options: globalOptions, + onSuccess: function success() { + self._resetBackoff(); + + self._triggerEvent('success', { + data: data, + src: url + }); + callback && callback(); + }, + onError: function failure(error) { + self._logDebug('error', 'Raven transport failed to send: ', error); + + if (error.request) { + self._setBackoffState(error.request); + } + + self._triggerEvent('failure', { + data: data, + src: url + }); + error = error || new Error('Raven send failed (no additional details provided)'); + callback && callback(error); + } + }); + }, + + _makeRequest: function(opts) { + var request = new XMLHttpRequest(); + + // if browser doesn't support CORS (e.g. IE7), we are out of luck + var hasCORS = + 'withCredentials' in request || + typeof XDomainRequest !== 'undefined'; + + if (!hasCORS) return; + + var url = opts.url; + + if ('withCredentials' in request) { + request.onreadystatechange = function () { + if (request.readyState !== 4) { + return; + } else if (request.status === 200) { + opts.onSuccess && opts.onSuccess(); + } else if (opts.onError) { + var err = new Error('Sentry error code: ' + request.status); + err.request = request; + opts.onError(err); + } + }; + } else { + request = new XDomainRequest(); + // xdomainrequest cannot go http -> https (or vice versa), + // so always use protocol relative + url = url.replace(/^https?:/, ''); + + // onreadystatechange not supported by XDomainRequest + if (opts.onSuccess) { + request.onload = opts.onSuccess; + } + if (opts.onError) { + request.onerror = function () { + var err = new Error('Sentry error code: XDomainRequest'); + err.request = request; + opts.onError(err); + } + } + } + + // NOTE: auth is intentionally sent as part of query string (NOT as custom + // HTTP header) so as to avoid preflight CORS requests + request.open('POST', url + '?' + urlencode(opts.auth)); + request.send(stringify(opts.data)); + }, + + _logDebug: function(level) { + if (this._originalConsoleMethods[level] && this.debug) { + // In IE<10 console methods do not have their own 'apply' method + Function.prototype.apply.call( + this._originalConsoleMethods[level], + this._originalConsole, + [].slice.call(arguments, 1) + ); + } + }, + + _mergeContext: function(key, context) { + if (isUndefined(context)) { + delete this._globalContext[key]; + } else { + this._globalContext[key] = objectMerge(this._globalContext[key] || {}, context); + } + } +}; + +/*------------------------------------------------ + * utils + * + * conditionally exported for test via Raven.utils + ================================================= + */ +var objectPrototype = Object.prototype; + +function isUndefined(what) { + return what === void 0; +} + +function isFunction(what) { + return typeof what === 'function'; +} + +function isString(what) { + return objectPrototype.toString.call(what) === '[object String]'; +} + + +function isEmptyObject(what) { + for (var _ in what) return false; // eslint-disable-line guard-for-in, no-unused-vars + return true; +} + +function each(obj, callback) { + var i, j; + + if (isUndefined(obj.length)) { + for (i in obj) { + if (hasKey(obj, i)) { + callback.call(null, i, obj[i]); + } + } + } else { + j = obj.length; + if (j) { + for (i = 0; i < j; i++) { + callback.call(null, i, obj[i]); + } + } + } +} + +function objectMerge(obj1, obj2) { + if (!obj2) { + return obj1; + } + each(obj2, function(key, value){ + obj1[key] = value; + }); + return obj1; +} + +function truncate(str, max) { + return !max || str.length <= max ? str : str.substr(0, max) + '\u2026'; +} + +/** + * hasKey, a better form of hasOwnProperty + * Example: hasKey(MainHostObject, property) === true/false + * + * @param {Object} host object to check property + * @param {string} key to check + */ +function hasKey(object, key) { + return objectPrototype.hasOwnProperty.call(object, key); +} + +function joinRegExp(patterns) { + // Combine an array of regular expressions and strings into one large regexp + // Be mad. + var sources = [], + i = 0, len = patterns.length, + pattern; + + for (; i < len; i++) { + pattern = patterns[i]; + if (isString(pattern)) { + // If it's a string, we need to escape it + // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1')); + } else if (pattern && pattern.source) { + // If it's a regexp already, we want to extract the source + sources.push(pattern.source); + } + // Intentionally skip other cases + } + return new RegExp(sources.join('|'), 'i'); +} + +function urlencode(o) { + var pairs = []; + each(o, function(key, value) { + pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); + }); + return pairs.join('&'); +} + +// borrowed from https://tools.ietf.org/html/rfc3986#appendix-B +// intentionally using regex and not href parsing trick because React Native and other +// environments where DOM might not be available +function parseUrl(url) { + var match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/); + if (!match) return {}; + + // coerce to undefined values to empty string so we don't get 'undefined' + var query = match[6] || ''; + var fragment = match[8] || ''; + return { + protocol: match[2], + host: match[4], + path: match[5], + relative: match[5] + query + fragment // everything minus origin + }; +} +function uuid4() { + var crypto = _window.crypto || _window.msCrypto; + + if (!isUndefined(crypto) && crypto.getRandomValues) { + // Use window.crypto API if available + var arr = new Uint16Array(8); + crypto.getRandomValues(arr); + + // set 4 in byte 7 + arr[3] = arr[3] & 0xFFF | 0x4000; + // set 2 most significant bits of byte 9 to '10' + arr[4] = arr[4] & 0x3FFF | 0x8000; + + var pad = function(num) { + var v = num.toString(16); + while (v.length < 4) { + v = '0' + v; + } + return v; + }; + + return pad(arr[0]) + pad(arr[1]) + pad(arr[2]) + pad(arr[3]) + pad(arr[4]) + + pad(arr[5]) + pad(arr[6]) + pad(arr[7]); + } else { + // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 + return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random()*16|0, + v = c === 'x' ? r : r&0x3|0x8; + return v.toString(16); + }); + } +} + +/** + * Given a child DOM element, returns a query-selector statement describing that + * and its ancestors + * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz] + * @param elem + * @returns {string} + */ +function htmlTreeAsString(elem) { + /* eslint no-extra-parens:0*/ + var MAX_TRAVERSE_HEIGHT = 5, + MAX_OUTPUT_LEN = 80, + out = [], + height = 0, + len = 0, + separator = ' > ', + sepLength = separator.length, + nextStr; + + while (elem && height++ < MAX_TRAVERSE_HEIGHT) { + + nextStr = htmlElementAsString(elem); + // bail out if + // - nextStr is the 'html' element + // - the length of the string that would be created exceeds MAX_OUTPUT_LEN + // (ignore this limit if we are on the first iteration) + if (nextStr === 'html' || height > 1 && len + (out.length * sepLength) + nextStr.length >= MAX_OUTPUT_LEN) { + break; + } + + out.push(nextStr); + + len += nextStr.length; + elem = elem.parentNode; + } + + return out.reverse().join(separator); +} + +/** + * Returns a simple, query-selector representation of a DOM element + * e.g. [HTMLElement] => input#foo.btn[name=baz] + * @param HTMLElement + * @returns {string} + */ +function htmlElementAsString(elem) { + var out = [], + className, + classes, + key, + attr, + i; + + if (!elem || !elem.tagName) { + return ''; + } + + out.push(elem.tagName.toLowerCase()); + if (elem.id) { + out.push('#' + elem.id); + } + + className = elem.className; + if (className && isString(className)) { + classes = className.split(/\s+/); + for (i = 0; i < classes.length; i++) { + out.push('.' + classes[i]); + } + } + var attrWhitelist = ['type', 'name', 'title', 'alt']; + for (i = 0; i < attrWhitelist.length; i++) { + key = attrWhitelist[i]; + attr = elem.getAttribute(key); + if (attr) { + out.push('[' + key + '="' + attr + '"]'); + } + } + return out.join(''); +} + +/** + * Returns true if either a OR b is truthy, but not both + */ +function isOnlyOneTruthy(a, b) { + return !!(!!a ^ !!b); +} + +/** + * Returns true if the two input exception interfaces have the same content + */ +function isSameException(ex1, ex2) { + if (isOnlyOneTruthy(ex1, ex2)) + return false; + + ex1 = ex1.values[0]; + ex2 = ex2.values[0]; + + if (ex1.type !== ex2.type || + ex1.value !== ex2.value) + return false; + + return isSameStacktrace(ex1.stacktrace, ex2.stacktrace); +} + +/** + * Returns true if the two input stack trace interfaces have the same content + */ +function isSameStacktrace(stack1, stack2) { + if (isOnlyOneTruthy(stack1, stack2)) + return false; + + var frames1 = stack1.frames; + var frames2 = stack2.frames; + + // Exit early if frame count differs + if (frames1.length !== frames2.length) + return false; + + // Iterate through every frame; bail out if anything differs + var a, b; + for (var i = 0; i < frames1.length; i++) { + a = frames1[i]; + b = frames2[i]; + if (a.filename !== b.filename || + a.lineno !== b.lineno || + a.colno !== b.colno || + a['function'] !== b['function']) + return false; + } + return true; +} + +/** + * Polyfill a method + * @param obj object e.g. `document` + * @param name method name present on object e.g. `addEventListener` + * @param replacement replacement function + * @param track {optional} record instrumentation to an array + */ +function fill(obj, name, replacement, track) { + var orig = obj[name]; + obj[name] = replacement(orig); + if (track) { + track.push([obj, name, orig]); + } +} + +if (typeof __DEV__ !== 'undefined' && __DEV__) { + Raven.utils = { + isUndefined: isUndefined, + isFunction: isFunction, + isString: isString, + isObject: isObject, + isEmptyObject: isEmptyObject, + isError: isError, + each: each, + objectMerge: objectMerge, + truncate: truncate, + hasKey: hasKey, + joinRegExp: joinRegExp, + urlencode: urlencode, + uuid4: uuid4, + htmlTreeAsString: htmlTreeAsString, + htmlElementAsString: htmlElementAsString, + parseUrl: parseUrl, + fill: fill + }; +}; + +// Deprecations +Raven.prototype.setUser = Raven.prototype.setUserContext; +Raven.prototype.setReleaseContext = Raven.prototype.setRelease; + +module.exports = Raven; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"1":1,"2":2,"5":5,"6":6,"7":7}],4:[function(_dereq_,module,exports){ +(function (global){ +/** + * Enforces a single instance of the Raven client, and the + * main entry point for Raven. If you are a consumer of the + * Raven library, you SHOULD load this file (vs raven.js). + **/ + +'use strict'; + +var RavenConstructor = _dereq_(3); + +// This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) +var _window = typeof window !== 'undefined' ? window + : typeof global !== 'undefined' ? global + : typeof self !== 'undefined' ? self + : {}; +var _Raven = _window.Raven; + +var Raven = new RavenConstructor(); + +/* + * Allow multiple versions of Raven to be installed. + * Strip Raven from the global context and returns the instance. + * + * @return {Raven} + */ +Raven.noConflict = function () { + _window.Raven = _Raven; + return Raven; +}; + +Raven.afterLoad(); + +module.exports = Raven; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"3":3}],5:[function(_dereq_,module,exports){ +'use strict'; + +function isObject(what) { + return typeof what === 'object' && what !== null; +} + +// Sorta yanked from https://github.com/joyent/node/blob/aa3b4b4/lib/util.js#L560 +// with some tiny modifications +function isError(what) { + var toString = {}.toString.call(what); + return isObject(what) && + toString === '[object Error]' || + toString === '[object Exception]' || // Firefox NS_ERROR_FAILURE Exceptions + what instanceof Error; +} + +module.exports = { + isObject: isObject, + isError: isError +}; +},{}],6:[function(_dereq_,module,exports){ +(function (global){ +'use strict'; + +var utils = _dereq_(5); + +/* + TraceKit - Cross brower stack traces + + This was originally forked from github.com/occ/TraceKit, but has since been + largely re-written and is now maintained as part of raven-js. Tests for + this are in test/vendor. + + MIT license +*/ + +var TraceKit = { + collectWindowErrors: true, + debug: false +}; + +// This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) +var _window = typeof window !== 'undefined' ? window + : typeof global !== 'undefined' ? global + : typeof self !== 'undefined' ? self + : {}; + +// global reference to slice +var _slice = [].slice; +var UNKNOWN_FUNCTION = '?'; + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types +var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/; + +function getLocationHref() { + if (typeof document === 'undefined' || typeof document.location === 'undefined') + return ''; + + return document.location.href; +} + + +/** + * TraceKit.report: cross-browser processing of unhandled exceptions + * + * Syntax: + * TraceKit.report.subscribe(function(stackInfo) { ... }) + * TraceKit.report.unsubscribe(function(stackInfo) { ... }) + * TraceKit.report(exception) + * try { ...code... } catch(ex) { TraceKit.report(ex); } + * + * Supports: + * - Firefox: full stack trace with line numbers, plus column number + * on top frame; column number is not guaranteed + * - Opera: full stack trace with line and column numbers + * - Chrome: full stack trace with line and column numbers + * - Safari: line and column number for the top frame only; some frames + * may be missing, and column number is not guaranteed + * - IE: line and column number for the top frame only; some frames + * may be missing, and column number is not guaranteed + * + * In theory, TraceKit should work on all of the following versions: + * - IE5.5+ (only 8.0 tested) + * - Firefox 0.9+ (only 3.5+ tested) + * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require + * Exceptions Have Stacktrace to be enabled in opera:config) + * - Safari 3+ (only 4+ tested) + * - Chrome 1+ (only 5+ tested) + * - Konqueror 3.5+ (untested) + * + * Requires TraceKit.computeStackTrace. + * + * Tries to catch all unhandled exceptions and report them to the + * subscribed handlers. Please note that TraceKit.report will rethrow the + * exception. This is REQUIRED in order to get a useful stack trace in IE. + * If the exception does not reach the top of the browser, you will only + * get a stack trace from the point where TraceKit.report was called. + * + * Handlers receive a stackInfo object as described in the + * TraceKit.computeStackTrace docs. + */ +TraceKit.report = (function reportModuleWrapper() { + var handlers = [], + lastArgs = null, + lastException = null, + lastExceptionStack = null; + + /** + * Add a crash handler. + * @param {Function} handler + */ + function subscribe(handler) { + installGlobalHandler(); + handlers.push(handler); + } + + /** + * Remove a crash handler. + * @param {Function} handler + */ + function unsubscribe(handler) { + for (var i = handlers.length - 1; i >= 0; --i) { + if (handlers[i] === handler) { + handlers.splice(i, 1); + } + } + } + + /** + * Remove all crash handlers. + */ + function unsubscribeAll() { + uninstallGlobalHandler(); + handlers = []; + } + + /** + * Dispatch stack information to all handlers. + * @param {Object.} stack + */ + function notifyHandlers(stack, isWindowError) { + var exception = null; + if (isWindowError && !TraceKit.collectWindowErrors) { + return; + } + for (var i in handlers) { + if (handlers.hasOwnProperty(i)) { + try { + handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2))); + } catch (inner) { + exception = inner; + } + } + } + + if (exception) { + throw exception; + } + } + + var _oldOnerrorHandler, _onErrorHandlerInstalled; + + /** + * Ensures all global unhandled exceptions are recorded. + * Supported by Gecko and IE. + * @param {string} message Error message. + * @param {string} url URL of script that generated the exception. + * @param {(number|string)} lineNo The line number at which the error + * occurred. + * @param {?(number|string)} colNo The column number at which the error + * occurred. + * @param {?Error} ex The actual Error object. + */ + function traceKitWindowOnError(message, url, lineNo, colNo, ex) { + var stack = null; + + if (lastExceptionStack) { + TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message); + processLastException(); + } else if (ex && utils.isError(ex)) { + // non-string `ex` arg; attempt to extract stack trace + + // New chrome and blink send along a real error object + // Let's just report that like a normal error. + // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror + stack = TraceKit.computeStackTrace(ex); + notifyHandlers(stack, true); + } else { + var location = { + 'url': url, + 'line': lineNo, + 'column': colNo + }; + + var name = undefined; + var msg = message; // must be new var or will modify original `arguments` + var groups; + if ({}.toString.call(message) === '[object String]') { + var groups = message.match(ERROR_TYPES_RE); + if (groups) { + name = groups[1]; + msg = groups[2]; + } + } + + location.func = UNKNOWN_FUNCTION; + + stack = { + 'name': name, + 'message': msg, + 'url': getLocationHref(), + 'stack': [location] + }; + notifyHandlers(stack, true); + } + + if (_oldOnerrorHandler) { + return _oldOnerrorHandler.apply(this, arguments); + } + + return false; + } + + function installGlobalHandler () + { + if (_onErrorHandlerInstalled) { + return; + } + _oldOnerrorHandler = _window.onerror; + _window.onerror = traceKitWindowOnError; + _onErrorHandlerInstalled = true; + } + + function uninstallGlobalHandler () + { + if (!_onErrorHandlerInstalled) { + return; + } + _window.onerror = _oldOnerrorHandler; + _onErrorHandlerInstalled = false; + _oldOnerrorHandler = undefined; + } + + function processLastException() { + var _lastExceptionStack = lastExceptionStack, + _lastArgs = lastArgs; + lastArgs = null; + lastExceptionStack = null; + lastException = null; + notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs)); + } + + /** + * Reports an unhandled Error to TraceKit. + * @param {Error} ex + * @param {?boolean} rethrow If false, do not re-throw the exception. + * Only used for window.onerror to not cause an infinite loop of + * rethrowing. + */ + function report(ex, rethrow) { + var args = _slice.call(arguments, 1); + if (lastExceptionStack) { + if (lastException === ex) { + return; // already caught by an inner catch block, ignore + } else { + processLastException(); + } + } + + var stack = TraceKit.computeStackTrace(ex); + lastExceptionStack = stack; + lastException = ex; + lastArgs = args; + + // If the stack trace is incomplete, wait for 2 seconds for + // slow slow IE to see if onerror occurs or not before reporting + // this exception; otherwise, we will end up with an incomplete + // stack trace + setTimeout(function () { + if (lastException === ex) { + processLastException(); + } + }, (stack.incomplete ? 2000 : 0)); + + if (rethrow !== false) { + throw ex; // re-throw to propagate to the top level (and cause window.onerror) + } + } + + report.subscribe = subscribe; + report.unsubscribe = unsubscribe; + report.uninstall = unsubscribeAll; + return report; +}()); + +/** + * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript + * + * Syntax: + * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below) + * Returns: + * s.name - exception name + * s.message - exception message + * s.stack[i].url - JavaScript or HTML file URL + * s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work) + * s.stack[i].args - arguments passed to the function, if known + * s.stack[i].line - line number, if known + * s.stack[i].column - column number, if known + * + * Supports: + * - Firefox: full stack trace with line numbers and unreliable column + * number on top frame + * - Opera 10: full stack trace with line and column numbers + * - Opera 9-: full stack trace with line numbers + * - Chrome: full stack trace with line and column numbers + * - Safari: line and column number for the topmost stacktrace element + * only + * - IE: no line numbers whatsoever + * + * Tries to guess names of anonymous functions by looking for assignments + * in the source code. In IE and Safari, we have to guess source file names + * by searching for function bodies inside all page scripts. This will not + * work for scripts that are loaded cross-domain. + * Here be dragons: some function names may be guessed incorrectly, and + * duplicate functions may be mismatched. + * + * TraceKit.computeStackTrace should only be used for tracing purposes. + * Logging of unhandled exceptions should be done with TraceKit.report, + * which builds on top of TraceKit.computeStackTrace and provides better + * IE support by utilizing the window.onerror event to retrieve information + * about the top of the stack. + * + * Note: In IE and Safari, no stack trace is recorded on the Error object, + * so computeStackTrace instead walks its *own* chain of callers. + * This means that: + * * in Safari, some methods may be missing from the stack trace; + * * in IE, the topmost function in the stack trace will always be the + * caller of computeStackTrace. + * + * This is okay for tracing (because you are likely to be calling + * computeStackTrace from the function you want to be the topmost element + * of the stack trace anyway), but not okay for logging unhandled + * exceptions (because your catch block will likely be far away from the + * inner function that actually caused the exception). + * + */ +TraceKit.computeStackTrace = (function computeStackTraceWrapper() { + /** + * Escapes special characters, except for whitespace, in a string to be + * used inside a regular expression as a string literal. + * @param {string} text The string. + * @return {string} The escaped string literal. + */ + function escapeRegExp(text) { + return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&'); + } + + /** + * Escapes special characters in a string to be used inside a regular + * expression as a string literal. Also ensures that HTML entities will + * be matched the same as their literal friends. + * @param {string} body The string. + * @return {string} The escaped string. + */ + function escapeCodeAsRegExpForMatchingInsideHTML(body) { + return escapeRegExp(body).replace('<', '(?:<|<)').replace('>', '(?:>|>)').replace('&', '(?:&|&)').replace('"', '(?:"|")').replace(/\s+/g, '\\s+'); + } + + // Contents of Exception in various browsers. + // + // SAFARI: + // ex.message = Can't find variable: qq + // ex.line = 59 + // ex.sourceId = 580238192 + // ex.sourceURL = http://... + // ex.expressionBeginOffset = 96 + // ex.expressionCaretOffset = 98 + // ex.expressionEndOffset = 98 + // ex.name = ReferenceError + // + // FIREFOX: + // ex.message = qq is not defined + // ex.fileName = http://... + // ex.lineNumber = 59 + // ex.columnNumber = 69 + // ex.stack = ...stack trace... (see the example below) + // ex.name = ReferenceError + // + // CHROME: + // ex.message = qq is not defined + // ex.name = ReferenceError + // ex.type = not_defined + // ex.arguments = ['aa'] + // ex.stack = ...stack trace... + // + // INTERNET EXPLORER: + // ex.message = ... + // ex.name = ReferenceError + // + // OPERA: + // ex.message = ...message... (see the example below) + // ex.name = ReferenceError + // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message) + // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace' + + /** + * Computes stack trace information from the stack property. + * Chrome and Gecko use this property. + * @param {Error} ex + * @return {?Object.} Stack trace information. + */ + function computeStackTraceFromStackProp(ex) { + if (typeof ex.stack === 'undefined' || !ex.stack) return; + + var chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack||\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i, + gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?)(?::(\d+))?(?::(\d+))?\s*$/i, + winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i, + + // Used to additionally parse URL/line/column from eval frames + geckoEval = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i, + chromeEval = /\((\S*)(?::(\d+))(?::(\d+))\)/, + + lines = ex.stack.split('\n'), + stack = [], + submatch, + parts, + element, + reference = /^(.*) is undefined$/.exec(ex.message); + + for (var i = 0, j = lines.length; i < j; ++i) { + if ((parts = chrome.exec(lines[i]))) { + var isNative = parts[2] && parts[2].indexOf('native') === 0; // start of line + var isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line + if (isEval && (submatch = chromeEval.exec(parts[2]))) { + // throw out eval line/column and use top-most line/column number + parts[2] = submatch[1]; // url + parts[3] = submatch[2]; // line + parts[4] = submatch[3]; // column + } + element = { + 'url': !isNative ? parts[2] : null, + 'func': parts[1] || UNKNOWN_FUNCTION, + 'args': isNative ? [parts[2]] : [], + 'line': parts[3] ? +parts[3] : null, + 'column': parts[4] ? +parts[4] : null + }; + } else if ( parts = winjs.exec(lines[i]) ) { + element = { + 'url': parts[2], + 'func': parts[1] || UNKNOWN_FUNCTION, + 'args': [], + 'line': +parts[3], + 'column': parts[4] ? +parts[4] : null + }; + } else if ((parts = gecko.exec(lines[i]))) { + var isEval = parts[3] && parts[3].indexOf(' > eval') > -1; + if (isEval && (submatch = geckoEval.exec(parts[3]))) { + // throw out eval line/column and use top-most line number + parts[3] = submatch[1]; + parts[4] = submatch[2]; + parts[5] = null; // no column when eval + } else if (i === 0 && !parts[5] && typeof ex.columnNumber !== 'undefined') { + // FireFox uses this awesome columnNumber property for its top frame + // Also note, Firefox's column number is 0-based and everything else expects 1-based, + // so adding 1 + // NOTE: this hack doesn't work if top-most frame is eval + stack[0].column = ex.columnNumber + 1; + } + element = { + 'url': parts[3], + 'func': parts[1] || UNKNOWN_FUNCTION, + 'args': parts[2] ? parts[2].split(',') : [], + 'line': parts[4] ? +parts[4] : null, + 'column': parts[5] ? +parts[5] : null + }; + } else { + continue; + } + + if (!element.func && element.line) { + element.func = UNKNOWN_FUNCTION; + } + + stack.push(element); + } + + if (!stack.length) { + return null; + } + + return { + 'name': ex.name, + 'message': ex.message, + 'url': getLocationHref(), + 'stack': stack + }; + } + + /** + * Adds information about the first frame to incomplete stack traces. + * Safari and IE require this to get complete data on the first frame. + * @param {Object.} stackInfo Stack trace information from + * one of the compute* methods. + * @param {string} url The URL of the script that caused an error. + * @param {(number|string)} lineNo The line number of the script that + * caused an error. + * @param {string=} message The error generated by the browser, which + * hopefully contains the name of the object that caused the error. + * @return {boolean} Whether or not the stack information was + * augmented. + */ + function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) { + var initial = { + 'url': url, + 'line': lineNo + }; + + if (initial.url && initial.line) { + stackInfo.incomplete = false; + + if (!initial.func) { + initial.func = UNKNOWN_FUNCTION; + } + + if (stackInfo.stack.length > 0) { + if (stackInfo.stack[0].url === initial.url) { + if (stackInfo.stack[0].line === initial.line) { + return false; // already in stack trace + } else if (!stackInfo.stack[0].line && stackInfo.stack[0].func === initial.func) { + stackInfo.stack[0].line = initial.line; + return false; + } + } + } + + stackInfo.stack.unshift(initial); + stackInfo.partial = true; + return true; + } else { + stackInfo.incomplete = true; + } + + return false; + } + + /** + * Computes stack trace information by walking the arguments.caller + * chain at the time the exception occurred. This will cause earlier + * frames to be missed but is the only way to get any stack trace in + * Safari and IE. The top frame is restored by + * {@link augmentStackTraceWithInitialElement}. + * @param {Error} ex + * @return {?Object.} Stack trace information. + */ + function computeStackTraceByWalkingCallerChain(ex, depth) { + var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i, + stack = [], + funcs = {}, + recursion = false, + parts, + item, + source; + + for (var curr = computeStackTraceByWalkingCallerChain.caller; curr && !recursion; curr = curr.caller) { + if (curr === computeStackTrace || curr === TraceKit.report) { + // console.log('skipping internal function'); + continue; + } + + item = { + 'url': null, + 'func': UNKNOWN_FUNCTION, + 'line': null, + 'column': null + }; + + if (curr.name) { + item.func = curr.name; + } else if ((parts = functionName.exec(curr.toString()))) { + item.func = parts[1]; + } + + if (typeof item.func === 'undefined') { + try { + item.func = parts.input.substring(0, parts.input.indexOf('{')); + } catch (e) { } + } + + if (funcs['' + curr]) { + recursion = true; + }else{ + funcs['' + curr] = true; + } + + stack.push(item); + } + + if (depth) { + // console.log('depth is ' + depth); + // console.log('stack is ' + stack.length); + stack.splice(0, depth); + } + + var result = { + 'name': ex.name, + 'message': ex.message, + 'url': getLocationHref(), + 'stack': stack + }; + augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description); + return result; + } + + /** + * Computes a stack trace for an exception. + * @param {Error} ex + * @param {(string|number)=} depth + */ + function computeStackTrace(ex, depth) { + var stack = null; + depth = (depth == null ? 0 : +depth); + + try { + stack = computeStackTraceFromStackProp(ex); + if (stack) { + return stack; + } + } catch (e) { + if (TraceKit.debug) { + throw e; + } + } + + try { + stack = computeStackTraceByWalkingCallerChain(ex, depth + 1); + if (stack) { + return stack; + } + } catch (e) { + if (TraceKit.debug) { + throw e; + } + } + return { + 'name': ex.name, + 'message': ex.message, + 'url': getLocationHref() + }; + } + + computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement; + computeStackTrace.computeStackTraceFromStackProp = computeStackTraceFromStackProp; + + return computeStackTrace; +}()); + +module.exports = TraceKit; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"5":5}],7:[function(_dereq_,module,exports){ +'use strict'; + +/* + json-stringify-safe + Like JSON.stringify, but doesn't throw on circular references. + + Originally forked from https://github.com/isaacs/json-stringify-safe + version 5.0.1 on 3/8/2017 and modified for IE8 compatibility. + Tests for this are in test/vendor. + + ISC license: https://github.com/isaacs/json-stringify-safe/blob/master/LICENSE +*/ + +exports = module.exports = stringify +exports.getSerialize = serializer + +function indexOf(haystack, needle) { + for (var i = 0; i < haystack.length; ++i) { + if (haystack[i] === needle) return i; + } + return -1; +} + +function stringify(obj, replacer, spaces, cycleReplacer) { + return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces) +} + +function serializer(replacer, cycleReplacer) { + var stack = [], keys = [] + + if (cycleReplacer == null) cycleReplacer = function(key, value) { + if (stack[0] === value) return '[Circular ~]' + return '[Circular ~.' + keys.slice(0, indexOf(stack, value)).join('.') + ']' + } + + return function(key, value) { + if (stack.length > 0) { + var thisPos = indexOf(stack, this); + ~thisPos ? stack.splice(thisPos + 1) : stack.push(this) + ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key) + if (~indexOf(stack, value)) value = cycleReplacer.call(this, key, value) + } + else stack.push(value) + + return replacer == null ? value : replacer.call(this, key, value) + } +} + +},{}]},{},[4])(4) +}); \ No newline at end of file diff --git a/browser/extensions/screenshots/webextension/build/shot.js b/browser/extensions/screenshots/webextension/build/shot.js new file mode 100644 index 000000000000..2580cda2c279 --- /dev/null +++ b/browser/extensions/screenshots/webextension/build/shot.js @@ -0,0 +1,727 @@ +window.shot = (function () {let exports={}; // Note: in this library we can't use any "system" dependencies because this can be used from multiple +// environments +/* globals console */ + +/** Throws an error if the condition isn't true. Any extra arguments after the condition + are used as console.error() arguments. */ +function assert(condition, ...args) { + if (condition) { + return; + } + console.error("Failed assertion", ...args); + throw new Error("Failed assertion", ...args); +} + +/** True if `url` is a valid URL */ +function isUrl(url) { + // FIXME: this is rather naive, obviously + if ((/^about:.+$/i).test(url)) { + return true; + } + if ((/^file:\/.*$/i).test(url)) { + return true; + } + if ((/^data:.*$/i).test(url)) { + return true; + } + if ((/^chrome:.*/i).test(url)) { + return true; + } + if ((/^view-source:/i).test(url)) { + return isUrl(url.substr("view-source:".length)); + } + return (/^https?:\/\/[a-z0-9\.\-]+[a-z0-9](:[0-9]+)?\/?/i).test(url); +} + +function assertUrl(url) { + if (! url) { + throw new Error("Empty value is not URL"); + } + if (! isUrl(url)) { + let exc = new Error("Not a URL"); + exc.scheme = url.split(":")[0]; + throw exc; + } +} + +function assertOrigin(url) { + assertUrl(url); + if (url.search(/^https?:/i) != -1) { + let match = (/^https?:\/\/[^/:]+\/?$/i).exec(url); + if (! match) { + throw new Error("Bad origin, might include path"); + } + } +} + +function originFromUrl(url) { + if (! url) { + return null; + } + if (url.search(/^https?:/i) == -1) { + // Non-HTTP URLs don't have an origin + return null; + } + let match = (/^https?:\/\/[^/:]+/i).exec(url); + if (match) { + return match[0]; + } + return null; +} + +/** Check if the given object has all of the required attributes, and no extra + attributes exception those in optional */ +function checkObject(obj, required, optional) { + if (typeof obj != "object" || obj === null) { + throw new Error("Cannot check non-object: " + (typeof obj) + " that is " + JSON.stringify(obj)); + } + required = required || []; + for (let attr of required) { + if (! (attr in obj)) { + return false; + } + } + optional = optional || []; + for (let attr in obj) { + if (required.indexOf(attr) == -1 && optional.indexOf(attr) == -1) { + return false; + } + } + return true; +} + +/** Create a JSON object from a normal object, given the required and optional + attributes (filtering out any other attributes). Optional attributes are + only kept when they are truthy. */ +function jsonify(obj, required, optional) { + required = required || []; + let result = {}; + for (let attr of required) { + result[attr] = obj[attr]; + } + optional = optional || []; + for (let attr of optional) { + if (obj[attr]) { + result[attr] = obj[attr]; + } + } + return result; +} + +/** Resolve url relative to base */ +function resolveUrl(base, url) { + // FIXME: totally ad hoc and probably incorrect, but we can't + // use any libraries in this file + if (url.search(/^https?:/) != -1) { + // Absolute url + return url; + } + if (url.indexOf("//") === 0) { + // Protocol-relative URL + return (/^https?:/i).exec(base)[0] + url; + } + if (url.indexOf("/") === 0) { + // Domain-relative URL + return (/^https?:\/\/[a-z0-9\.\-]+/i).exec(base)[0] + url; + } + // Otherwise, a full relative URL + while (url.indexOf("./") === 0) { + url = url.substr(2); + } + let match = (/.*\//).exec(base)[0]; + if (match.search(/^https?:\/$/i) === 0) { + // Domain without path + match = match + "/"; + } + return match + url; +} + +/** True if the two objects look alike. Null, undefined, and absent properties + are all treated as equivalent. Traverses objects and arrays */ +function deepEqual(a, b) { + if ((a === null || a === undefined) && (b === null || b === undefined)) { + return true; + } + if (typeof a != "object" || typeof b != "object") { + return a === b; + } + if (Array.isArray(a)) { + if (! Array.isArray(b)) { + return false; + } + if (a.length != b.length) { + return false; + } + for (let i=0; i new this.Image(json)); + } + this.openGraph = attrs.openGraph || null; + this.twitterCard = attrs.twitterCard || null; + this.documentSize = attrs.documentSize || null; + this.fullScreenThumbnail = attrs.fullScreenThumbnail || null; + this.abTests = attrs.abTests || null; + this._clips = {}; + if (attrs.clips) { + for (let clipId in attrs.clips) { + let clip = attrs.clips[clipId]; + this._clips[clipId] = new this.Clip(this, clipId, clip); + } + } + + for (let attr in attrs) { + if (attr !== "clips" && attr !== "id" && this.REGULAR_ATTRS.indexOf(attr) === -1 && this.DEPRECATED_ATTRS.indexOf(attr) === -1) { + throw new Error("Unexpected attribute: " + attr); + } else if (attr === "id") { + console.warn("passing id in attrs in AbstractShot constructor"); + console.trace(); + assert(attrs.id === this.id); + } + } + } + + /** Update any and all attributes in the json object, with deep updating + of `json.clips` */ + update(json) { + let ALL_ATTRS = ["clips"].concat(this.REGULAR_ATTRS); + assert(checkObject(json, [], ALL_ATTRS), "Bad attr to new Shot():", Object.keys(json)); + for (let attr in json) { + if (attr == "clips") { + continue; + } + if (typeof json[attr] == "object" && typeof this[attr] == "object" && this[attr] !== null) { + let val = this[attr]; + if (val.asJson) { + val = val.asJson(); + } + if (! deepEqual(json[attr], val)) { + this[attr] = json[attr]; + } + } else if (json[attr] !== this[attr] && + (json[attr] || this[attr])) { + this[attr] = json[attr]; + } + } + if (json.clips) { + for (let clipId in json.clips) { + if (! json.clips[clipId]) { + this.delClip(clipId); + } else if (! this.getClip(clipId)) { + this.setClip(clipId, json.clips[clipId]); + } else if (! deepEqual(this.getClip(clipId).asJson(), json.clips[clipId])) { + this.setClip(clipId, json.clips[clipId]); + } + } + } + + } + + /** Returns a JSON version of this shot */ + asJson() { + let result = {}; + for (let attr of this.REGULAR_ATTRS) { + var val = this[attr]; + if (val && val.asJson) { + val = val.asJson(); + } + result[attr] = val; + } + result.clips = {}; + for (let attr in this._clips) { + result.clips[attr] = this._clips[attr].asJson(); + } + return result; + } + + /** A more minimal JSON representation for creating indexes of shots */ + asRecallJson() { + let result = {clips: {}}; + for (let attr of this.RECALL_ATTRS) { + var val = this[attr]; + if (val && val.asJson) { + val = val.asJson(); + } + result[attr] = val; + } + for (let name of this.clipNames()) { + result.clips[name] = this.getClip(name).asJson(); + } + return result; + } + + get backend() { + return this._backend; + } + + get id() { + return this._id; + } + + get url() { + return this.fullUrl || this.origin; + } + set url(val) { + throw new Error(".url is read-only"); + } + + get fullUrl() { + return this._fullUrl; + } + set fullUrl(val) { + if (val) { + assertUrl(val); + } + this._fullUrl = val || undefined; + } + + get origin() { + return this._origin; + } + set origin(val) { + if (val) { + assertOrigin(val); + } + this._origin = val || undefined; + } + + get filename() { + let filenameTitle = this.title; + let date = new Date(this.createdDate); + filenameTitle = filenameTitle.replace(/[\/!@&*.|\n\r\t]/g, " "); + filenameTitle = filenameTitle.replace(/\s+/g, " "); + let clipFilename = `Screenshot-${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${filenameTitle}`; + const clipFilenameBytesSize = clipFilename.length * 2; // JS STrings are UTF-16 + if (clipFilenameBytesSize > 251) { // 255 bytes (Usual filesystems max) - 4 for the ".png" file extension string + const excedingchars = (clipFilenameBytesSize - 246) / 2; // 251 - 5 for ellipsis "[...]" + clipFilename = clipFilename.substring(0, clipFilename.length - excedingchars); + clipFilename = clipFilename + '[...]'; + } + return clipFilename + '.png'; + } + + get urlDisplay() { + if (! this.url) { + return null; + } + if (this.url.search(/^https?/i) != -1) { + let txt = this.url; + txt = txt.replace(/^[a-z]+:\/\//i, ""); + txt = txt.replace(/\/.*/, ""); + txt = txt.replace(/^www\./i, ""); + return txt; + } else if (this.url.startsWith("data:")) { + return "data:url"; + } else { + let txt = this.url; + txt = txt.replace(/\?.*/, ""); + return txt; + } + } + + get viewUrl() { + let url = this.backend + "/" + this.id; + return url; + } + + get creatingUrl() { + let url = `${this.backend}/creating/${this.id}`; + url += `?title=${encodeURIComponent(this.title || "")}`; + url += `&url=${encodeURIComponent(this.url)}`; + return url; + } + + get jsonUrl() { + return this.backend + "/data/" + this.id; + } + + get oembedUrl() { + return this.backend + "/oembed?url=" + encodeURIComponent(this.viewUrl); + } + + get docTitle() { + return this._title; + } + set docTitle(val) { + assert(val === null || typeof val == "string", "Bad docTitle:", val); + this._title = val; + } + + get openGraph() { + return this._openGraph || null; + } + set openGraph(val) { + assert(val === null || typeof val == "object", "Bad openGraph:", val); + if (val) { + assert(checkObject(val, [], this._OPENGRAPH_PROPERTIES), "Bad attr to openGraph:", Object.keys(val)); + this._openGraph = val; + } else { + this._openGraph = null; + } + } + + get twitterCard() { + return this._twitterCard || null; + } + set twitterCard(val) { + assert(val === null || typeof val == "object", "Bad twitterCard:", val); + if (val) { + assert(checkObject(val, [], this._TWITTERCARD_PROPERTIES), "Bad attr to twitterCard:", Object.keys(val)); + this._twitterCard = val; + } else { + this._twitterCard = null; + } + } + + get userTitle() { + return this._userTitle; + } + set userTitle(val) { + assert(val === null || typeof val == "string", "Bad userTitle:", val); + this._userTitle = val; + } + + get title() { + // FIXME: we shouldn't support both openGraph.title and ogTitle + let ogTitle = this.openGraph && this.openGraph.title; + let twitterTitle = this.twitterCard && this.twitterCard.title; + let title = this.userTitle || ogTitle || twitterTitle || this.docTitle || this.url; + if (Array.isArray(title)) { + title = title[0]; + } + return title; + } + + get createdDate() { + return this._createdDate; + } + set createdDate(val) { + assert(val === null || typeof val == "number", "Bad createdDate:", val); + this._createdDate = val; + } + + get favicon() { + return this._favicon; + } + set favicon(val) { + assert(val === null || isUrl(val), "Bad favicon URL:", val); + if (val) { + val = resolveUrl(this.url, val); + } + this._favicon = val; + } + + clipNames() { + let names = Object.getOwnPropertyNames(this._clips); + names.sort(function (a, b) { + return a.sortOrder < b.sortOrder ? 1 : 0; + }); + return names; + } + getClip(name) { + return this._clips[name]; + } + addClip(val) { + let name = makeRandomId(); + this.setClip(name, val); + return name; + } + setClip(name, val) { + let clip = new this.Clip(this, name, val); + this._clips[name] = clip; + } + delClip(name) { + if (! this._clips[name]) { + throw new Error("No existing clip with id: " + name); + } + delete this._clips[name]; + } + biggestClipSortOrder() { + let biggest = 0; + for (let clipId in this._clips) { + biggest = Math.max(biggest, this._clips[clipId].sortOrder); + } + return biggest; + } + updateClipUrl(clipId, clipUrl) { + let clip = this.getClip(clipId); + if ( clip && clip.image ) { + clip.image.url = clipUrl; + } else { + console.warn("Tried to update the url of a clip with no image:", clip); + } + } + + get siteName() { + return this._siteName || null; + } + set siteName(val) { + assert(typeof val == "string" || ! val); + this._siteName = val; + } + + get documentSize() { + return this._documentSize; + } + set documentSize(val) { + assert(typeof val == "object" || ! val); + if (val) { + assert(checkObject(val, ["height", "width"], "Bad attr to documentSize:", Object.keys(val))); + assert(typeof val.height == "number"); + assert(typeof val.width == "number"); + this._documentSize = val; + } else { + this._documentSize = null; + } + } + + get fullScreenThumbnail() { + return this._fullScreenThumbnail; + } + set fullScreenThumbnail(val) { + assert(typeof val == "string" || ! val); + if (val) { + assert(isUrl(val)); + this._fullScreenThumbnail = val; + } else { + this._fullScreenThumbnail = null; + } + } + + get abTests() { + return this._abTests; + } + set abTests(val) { + if (val === null || val === undefined) { + this._abTests = null; + return; + } + assert(typeof val == "object", "abTests should be an object, not:", typeof val); + assert(! Array.isArray(val), "abTests should not be an Array"); + for (let name in val) { + assert(val[name] && typeof val[name] == "string", `abTests.${name} should be a string:`, typeof val[name]); + } + this._abTests = val; + } + +} + +AbstractShot.prototype.REGULAR_ATTRS = (` +origin fullUrl docTitle userTitle createdDate favicon images +siteName openGraph twitterCard documentSize +fullScreenThumbnail abTests +`).split(/\s+/g); + +// Attributes that will be accepted in the constructor, but ignored/dropped +AbstractShot.prototype.DEPRECATED_ATTRS = (` +microdata history ogTitle createdDevice head body htmlAttrs bodyAttrs headAttrs +readable hashtags comments showPage isPublic resources deviceId url +`).split(/\s+/g); + +AbstractShot.prototype.RECALL_ATTRS = (` +url docTitle userTitle createdDate favicon +openGraph twitterCard images fullScreenThumbnail +`).split(/\s+/g); + +AbstractShot.prototype._OPENGRAPH_PROPERTIES = (` +title type url image audio description determiner locale site_name video +image:secure_url image:type image:width image:height +video:secure_url video:type video:width image:height +audio:secure_url audio:type +article:published_time article:modified_time article:expiration_time article:author article:section article:tag +book:author book:isbn book:release_date book:tag +profile:first_name profile:last_name profile:username profile:gender +`).split(/\s+/g); + +AbstractShot.prototype._TWITTERCARD_PROPERTIES = (` +card site title description image +player player:width player:height player:stream player:stream:content_type +`).split(/\s+/g); + +/** Represents one found image in the document (not a clip) */ +class _Image { + // FIXME: either we have to notify the shot of updates, or make + // this read-only + constructor(json) { + assert(typeof json === "object", "Clip Image given a non-object", json); + assert(checkObject(json, ["url"], ["dimensions", "title", "alt"]), "Bad attrs for Image:", Object.keys(json)); + assert(isUrl(json.url), "Bad Image url:", json.url); + this.url = json.url; + assert((! json.dimensions) || + (typeof json.dimensions.x == "number" && typeof json.dimensions.y == "number"), + "Bad Image dimensions:", json.dimensions); + this.dimensions = json.dimensions; + assert(typeof json.title == "string" || ! json.title, "Bad Image title:", json.title); + this.title = json.title; + assert(typeof json.alt == "string" || ! json.alt, "Bad Image alt:", json.alt); + this.alt = json.alt; + } + + asJson() { + return jsonify(this, ["url"], ["dimensions"]); + } +} + +AbstractShot.prototype.Image = _Image; + +/** Represents a clip, either a text or image clip */ +class _Clip { + constructor(shot, id, json) { + this._shot = shot; + assert(checkObject(json, ["createdDate", "image"], ["sortOrder"]), "Bad attrs for Clip:", Object.keys(json)); + assert(typeof id == "string" && id, "Bad Clip id:", id); + this._id = id; + this.createdDate = json.createdDate; + if ('sortOrder' in json) { + assert(typeof json.sortOrder == "number" || ! json.sortOrder, "Bad Clip sortOrder:", json.sortOrder); + } + if ('sortOrder' in json) { + this.sortOrder = json.sortOrder; + } else { + let biggestOrder = shot.biggestClipSortOrder(); + this.sortOrder = biggestOrder + 100; + } + this.image = json.image; + } + + toString() { + return `[Shot Clip id=${this.id} sortOrder=${this.sortOrder} image ${this.image.dimensions.x}x${this.image.dimensions.y}]`; + } + + asJson() { + return jsonify(this, ["createdDate"], ["sortOrder", "image"]); + } + + get id() { + return this._id; + } + + get createdDate() { + return this._createdDate; + } + set createdDate(val) { + assert(typeof val == "number" || ! val, "Bad Clip createdDate:", val); + this._createdDate = val; + } + + get image() { + return this._image; + } + set image(image) { + if (! image) { + this._image = undefined; + return; + } + assert(checkObject(image, ["url"], ["dimensions", "text", "location", "captureType"]), "Bad attrs for Clip Image:", Object.keys(image)); + assert(isUrl(image.url), "Bad Clip image URL:", image.url); + assert(image.captureType == "madeSelection" || image.captureType == "selection" || image.captureType == "visible" || image.captureType == "auto" || image.captureType == "fullPage" || ! image.captureType, "Bad image.captureType:", image.captureType); + assert(typeof image.text == "string" || ! image.text, "Bad Clip image text:", image.text); + if (image.dimensions) { + assert(typeof image.dimensions.x == "number" && typeof image.dimensions.y == "number", "Bad Clip image dimensions:", image.dimensions); + } + assert(image.location && + typeof image.location.left == "number" && + typeof image.location.right == "number" && + typeof image.location.top == "number" && + typeof image.location.bottom == "number", "Bad Clip image pixel location:", image.location); + if (image.location.topLeftElement || image.location.topLeftOffset || + image.location.bottomRightElement || image.location.bottomRightOffset) { + assert(typeof image.location.topLeftElement == "string" && + image.location.topLeftOffset && + typeof image.location.topLeftOffset.x == "number" && + typeof image.location.topLeftOffset.y == "number" && + typeof image.location.bottomRightElement == "string" && + image.location.bottomRightOffset && + typeof image.location.bottomRightOffset.x == "number" && + typeof image.location.bottomRightOffset.y == "number", + "Bad Clip image element location:", image.location); + } + this._image = image; + } + + isDataUrl() { + if (this.image) { + return this.image.url.startsWith("data:"); + } + } + + get sortOrder() { + return this._sortOrder || null; + } + set sortOrder(val) { + assert(typeof val == "number" || ! val, "Bad Clip sortOrder:", val); + this._sortOrder = val; + } + +} + +AbstractShot.prototype.Clip = _Clip; + +if (typeof exports != "undefined") { + exports.AbstractShot = AbstractShot; + exports.originFromUrl = originFromUrl; +} + +return exports; +})(); +null; + diff --git a/browser/extensions/screenshots/webextension/buildSettings.js.template b/browser/extensions/screenshots/webextension/buildSettings.js.template new file mode 100644 index 000000000000..f09cf980621d --- /dev/null +++ b/browser/extensions/screenshots/webextension/buildSettings.js.template @@ -0,0 +1,5 @@ +window.buildSettings = { + defaultSentryDsn: process.env.SCREENSHOTS_SENTRY, + logLevel: process.env.SCREENSHOTS_LOG_LEVEL || "warn" +}; +null; diff --git a/browser/extensions/screenshots/webextension/catcher.js b/browser/extensions/screenshots/webextension/catcher.js new file mode 100644 index 000000000000..c5cde216bb54 --- /dev/null +++ b/browser/extensions/screenshots/webextension/catcher.js @@ -0,0 +1,83 @@ +/* globals log */ + +"use strict"; + +var global = this; + +this.catcher = (function () { + let exports = {}; + + let handler; + + let queue = []; + + exports.unhandled = function (error, info) { + log.error("Unhandled error:", error, info); + let e = makeError(error, info); + if (! handler) { + queue.push(e); + } else { + handler(e); + } + }; + + /** Turn an exception into an error object */ + function makeError(exc, info) { + let result; + if (exc.fromMakeError) { + result = exc; + } else { + result = { + fromMakeError: true, + name: exc.name || "ERROR", + message: String(exc), + stack: exc.stack + }; + for (let attr in exc) { + result[attr] = exc[attr]; + } + } + if (info) { + for (let attr of Object.keys(info)) { + result[attr] = info[attr]; + } + } + return result; + } + + /** Wrap the function, and if it raises any exceptions then call unhandled() */ + exports.watchFunction = function watchFunction(func) { + return function () { + try { + return func.apply(this, arguments); + } catch (e) { + exports.unhandled(e); + throw e; + } + }; + }; + + exports.watchPromise = function watchPromise(promise) { + return promise.catch((e) => { + log.error("------Error in promise:", e); + log.error(e.stack); + exports.unhandled(makeError(e)); + throw e; + }); + }; + + exports.registerHandler = function (h) { + if (handler) { + log.error("registerHandler called after handler was already registered"); + return; + } + handler = h; + for (let error of queue) { + handler(error); + } + queue = []; + }; + + return exports; +})(); +null; diff --git a/browser/extensions/screenshots/webextension/clipboard.js b/browser/extensions/screenshots/webextension/clipboard.js new file mode 100644 index 000000000000..5eb799577f4c --- /dev/null +++ b/browser/extensions/screenshots/webextension/clipboard.js @@ -0,0 +1,23 @@ +/* globals catcher */ + +"use strict"; + +this.clipboard = (function () { + let exports = {}; + + exports.copy = function (text) { + let el = document.createElement("textarea"); + document.body.appendChild(el); + el.value = text; + el.select(); + const copied = document.execCommand("copy"); + document.body.removeChild(el); + if (!copied) { + catcher.unhandled(new Error("Clipboard copy failed")); + } + return copied; + }; + + return exports; +})(); +null; diff --git a/browser/extensions/screenshots/webextension/domainFromUrl.js b/browser/extensions/screenshots/webextension/domainFromUrl.js new file mode 100644 index 000000000000..4b68d6caf58e --- /dev/null +++ b/browser/extensions/screenshots/webextension/domainFromUrl.js @@ -0,0 +1,29 @@ +/** Returns the domain of a URL, but safely and in ASCII; URLs without domains + (such as about:blank) return the scheme, Unicode domains get stripped down + to ASCII */ + +"use strict"; + +this.domainFromUrl = (function () { + + return function urlDomainForId(location) { // eslint-disable-line no-unused-vars + let domain = location.hostname; + if (!domain) { + domain = location.origin.split(":")[0]; + if (! domain) { + domain = "unknown"; + } + } + if (domain.search(/^[a-z0-9.\-]+$/i) === -1) { + // Probably a unicode domain; we could use punycode but it wouldn't decode + // well in the URL anyway. Instead we'll punt. + domain = domain.replace(/[^a-z0-9.\-]/ig, ""); + if (! domain) { + domain = "site"; + } + } + return domain; + }; + +})(); +null; diff --git a/browser/extensions/screenshots/webextension/icons/back.svg b/browser/extensions/screenshots/webextension/icons/back.svg new file mode 100644 index 000000000000..bb2158ec20a4 --- /dev/null +++ b/browser/extensions/screenshots/webextension/icons/back.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/browser/extensions/screenshots/webextension/icons/cancel.svg b/browser/extensions/screenshots/webextension/icons/cancel.svg new file mode 100644 index 000000000000..00c3b3ff3251 --- /dev/null +++ b/browser/extensions/screenshots/webextension/icons/cancel.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/browser/extensions/screenshots/webextension/icons/copy.png b/browser/extensions/screenshots/webextension/icons/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..85f952d7b68d4873e6d4631fe1ebf51bd1893f2b GIT binary patch literal 985 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wF4y>l>>Z2T!CCSH#Y}cCp$Ywdpidp zva9`^Q*4i4@P4haqp&M;XAds~oE4Gj$r zU<3X9{D9oFv^1a{pvCsP*COWn&%20}Gz=&Cl=1srNdre1$!C z#p%6^F8#UTz;fZ<`UK7=(OsUl8?vrd{q5G*r^A@l$YlD0l}(4as*zD!BDZv{{ne@+2O4<)oVa~w(#un~ zV><^q$9&9+nxu4tTU?|IL?VUYA^UtU-EPfo?B?6LZM{FKEZT4Em-H!$&C`6+UviZgfKg*eCAryG9wyHn3DQ4Q+O?|wf|A+GWL&c2#g%s*Tw@x~qro&rpK N44$rjF6*2UngI4sZEpYo literal 0 HcmV?d00001 diff --git a/browser/extensions/screenshots/webextension/icons/done.svg b/browser/extensions/screenshots/webextension/icons/done.svg new file mode 100644 index 000000000000..2da266867d06 --- /dev/null +++ b/browser/extensions/screenshots/webextension/icons/done.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/browser/extensions/screenshots/webextension/icons/download.svg b/browser/extensions/screenshots/webextension/icons/download.svg new file mode 100644 index 000000000000..5f4816b21472 --- /dev/null +++ b/browser/extensions/screenshots/webextension/icons/download.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/browser/extensions/screenshots/webextension/icons/icon-128.png b/browser/extensions/screenshots/webextension/icons/icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..bf4a84dd55bd43b3309f165a95cc562b637cd077 GIT binary patch literal 2489 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 SR|ottqRfowv|@_;h5rG$tR@uz literal 0 HcmV?d00001 diff --git a/browser/extensions/screenshots/webextension/icons/icon-16.png b/browser/extensions/screenshots/webextension/icons/icon-16.png new file mode 100644 index 0000000000000000000000000000000000000000..fd531ed80cc83c3529ecaf34848b108185be2a1e GIT binary patch literal 373 zcmV-*0gC>KP)0mS(lcXS*Mx#kEDYR$}Wtq9Es)j2vYbJzVq9De*fVCjO6c3>zD2VYc zU=PqL60Mhr@y-*0Qe7UA**qa^%7+5h51&3TNluLC$%iVI|X@Sxxu)62aFiV{C98Z7UJOJDd zp=?;Je#3oZVWAq1J9tp(=7B8-GMz(l$iWfhpxBbY2FLc*>2%31RbbOrt%$J-(BARR zB@|^qxJ44YAJ`tu-A8*J6m)y<17jxq9mP_+hVToQ9e_I#2hxc+fIep-&~g3;8|~OG TWw(Wj00000NkvXXu0mjfiyxml literal 0 HcmV?d00001 diff --git a/browser/extensions/screenshots/webextension/icons/icon-19.png b/browser/extensions/screenshots/webextension/icons/icon-19.png new file mode 100644 index 0000000000000000000000000000000000000000..2585ac748faaafc1eb4865f849f96dcf446e5ad0 GIT binary patch literal 619 zcmV-x0+juUP)YNe%~|mTOEFf_a*%IpHh0IGUPiS zt#xT-=pCfRaC8*qm_nLg@}#L3ahLJ?n1-wffcZemyIijHqeqWUWLZ{f(%rjvPb$(e z1!;cClct__r0GZhFffsZ8UCg!`h|ljS4nN?2BwnDc=ns79?MLA2aFlgKOitoM=PS6 zNBo_^h&R-RjXFknA7BsJA*;AL-alX zFJ&n!FM#&Y(El)dF&te({Wx`{WKtLyA|*FXr zbQB=hQN~=`wlh)Av4elXFDFf!J>uc!$svztVZ#6Z{QxaoB6a$xq*4F?002ovPDHLk FV1jMQ80Y{1 literal 0 HcmV?d00001 diff --git a/browser/extensions/screenshots/webextension/icons/icon-256.png b/browser/extensions/screenshots/webextension/icons/icon-256.png new file mode 100644 index 0000000000000000000000000000000000000000..9388dbfdde87cfb38e2c54746767034f64440d78 GIT binary patch literal 4474 zcmaJ^c{r5c`<}6f5lS>LHOP zKp_HwLc&oL910o|ctxo_Ug+xT0(_3404Dh3I3?{k4wL|_P(UUU3A6)dGzA36S3?>Z z83DcEaLp4;BvK3LLg7Qvbv*yqf+7k9M>#q=0wCJj+CaIEnhwAUrG^3?Bofd|!J+Uu zuB9vx0DqvJHo%8s7aXa6jH#)qNkO0_pa2z=SHOI1CeQ*f5MYZ$8d4IDB>+B945#R& zumV6Rawz$LIasKT{(!5Lh{({`=+;L(r7XBwaRp z>#4-$WCWC&ic{<9-ZM6VFN`n$oO#5>df6_jqh4?~cbT{Ha6)7T$AvkRnoE}+- z*XIkpKg>fCzv1I6{kUf8Gm`Vl2#ELI2nbF!0s^B(KmxlOd#FnD7`y8w)xnYD)J;QfEj>T7pMml! zYCPuX@Vrvxqq0<6^c8hPHY`JF>3Ohg1+jz;OH*3P1^!%dRS2|f2tJGaf5${kd+LJQ z2Tb|q1MRSC+b>Room{5SvdcDyeF0Ux!w9lRxbo&uaxA%LcENbAD=nt{$)APi-1SOW zembm}bxFVr+F$g^8R!3w!?<};50rK#3YBVfT8=O1CLYP3F8V3omaJ+>~=qQ^{r$S7ou;3w(G|QYk zgkM!^-StQLT77O14s6T{!QM(&I4rI!a=cn2%SgKAn|0>d&zdJ}MZQ(;LN)G(E5jM% zn%8^F7w3X=_uYM6q2q?ehUZ+&2D;025J;RFI2nl6!RfcRAPmDK@N<#t0AdtXLOwn_x5l z?OU?Ymu{^6DzZ&p&fYE~a?J@F?lx3^o_&g8&HYxrEfH2(>GL;B17Ao3+XG{`KAAmo zMwvVI{0Y3uVnsh+;7n)XD7L7jD9ya#$!T+udh41gi~h{xEdll?Mw|9JoXb>6CIkko zINC%ug5jNQq3JE`nVhsobeKCi`5a?*b$aR>nOb-yJw4p($B&fm1~g{A<=*cG|M++h zR$*b9pK~FsIy<+=&==3b=+V+5OnYJ7HS-Uy zCi&;%)EvAEJoyKei2VF>^lZPddzUw(xZ?$=1%1NU2M@1#$xhcUINs!1x{oPY*SS_{ zgC@PACFE{dOk%#$qD;fid8%h-+Q#;c23++v@6a%4k7XM|4S4UFM74+OK)io7oT5YT zD#qMFTedhx-RzRRSr;nFyvYbpPH+zeGVMXjbb@WmK>H38w(8sDix{!>)l1gqm4qjOLu!Gjiw_LpM?|=Ej z8!6C1iib(H5OE9VTW~JBmTofq&mp3!tO?7yQr|@Fz9)AT9fsH*PPVx+w)6|I*1Ezf z^Pigr;UYZeXgK{GI`-mb{Djt-@PelEU%<{PX=o{@u5b34lT0sSiFHQiXS(VXPYunQ zXJVY{;XW}KJrQ)Ba`D&fz})nttMN0d-jB+AO8l+D4>~79;%=Kq&IZ(~cFXSFIA{sg zZ5wc?dgmWJW0jAw@_MS`btHCkVT5jgvDrSSxn>@G@Uxaa9(Ev5$Hd{QTNfL6kg-kSNj(_SIHEFjo|PL;mn4aquN zr$8dUe!G0zHSzPu!LCP1c912;X1k)DHmZ#SB9FfIU8yliTa2#bYa-*3oMMDj&7y81 zr=)^~t8}PD^L~1-S5OGX8q{w3tss^-nZn-sN!}QNU2%19w;t6RdXGCSZ&^3s#8`pa zZ_eCNpn65BDt5RJ=@qvxAF~gv{#(J;tD*6-M|iaTl8Fs|!e?8e(*j<(asTU5FKJH2i~whSqk>W|K~ z`Ittkxdcyb0jPg9pV9a2>x{Yspdfx8X#Dm&vp$`cpSAX}vXX%b(d6Na%NV)MmB3${DdCg#CO1G~V-wRmwmBj-lGqZIZGcW$XOkQ)|LP~wkk#A>furmYDhHD)Z~Q+9zrji zAU(}@WUP==YIvS}t$RCc*h|yV6TY*X7`E$jZHXm*hod=0!*ng^_3m#`VpWRkh?Uzl z@MlSMW!BG}C4~n}kLUh%$$$`^gtb2(`=**i5)~_W;{v$T?8eLwR$s0(7JKbhY}vR+ zufd_Ur!}{yHp^V*ChAk@4yhl8<4~bHKLbQh$@0c25#|4UycxL$r@ETxo_hJyT3AFN z-+f;)$26p5C9Gc@&OV{ay2mLx6NqT9KRtE-Q>{wrt9`k#P_EOO+G9D4_2)E(o(Q*v z^OrQidodfnYuRnTL$y4{a{jGyzWZlAIb0X{;<7V(tlD1fVBKhRqOxrVdovfpqWhZXqkqv0-pYWEHbOHk#=BR; zl5Tvu$Ir;fk15g_e;1qlcYK}5SIW7`7kAL0p5U$iL^5WGyxVAU7#>GL|2)I$t=2Lh zCJ~GqYk9s!>|!{qbYojLNM#ODUY z6z(dGeOznU&9-H+g=kOT$n zP5PPIr!;gKjqAD|%d3{MGb|jH^+`LeQ~~RoidTxO8nL;>Y{EW>tHB|W+>QrG|D{uO zL5;FC5X8Mu1%q`dr>HlQ^U-_}d`(9}26rE^HOa)z6~XcsJeq1jqi9mEEKbp7p3aJE zE&9THq%FQY|28YL?~wtN7@=+WzkHj?*WJxzdWy*qBVi*Z1sB3ad&flPX z0a{m{V)EI=+H>uU1OsacT;EPU?0m_+pRtF|UtiK=D?lnQ+Gkq{QCOci!p6#kE!!DKvknq|!1}39T-Q~6YR)d!2w_2o;J#!mQzgYLP6bnceT-5B|xr(6A zdXbH?+72Sf2}?eg&rzeYG5I%D``GTLyx1?}^)!(7l~eR5ru$9r;t)ydoh2T9>;CKu zbqq>v(6p63)y4IZYXfOdAl#D>eVGMY$&YtOr1=)QqAyf-%y8czXD}Vj>@Dv<5H@9n zaQnpi-~|6VYkv8X(WJJxf~K1$B@@NExTPX4+T|_|nCl9-6<3wq@%>m)V03}QE=wv> z&7c30rPKc7)^zBZN@-&S5yh2PhIa%_y+h%pJT+5BaVd!f{Q8_?yF?B8Q)qHXr5BTh z^K`2^t+QBYe1Z(8kp%MT@+B3k(ciDA6bV_EG1pj}I3}p-O5gq4im77kYlHb_K-1D% zakS3N@k?8J3E)_c94jGo7kB<=lyW)95UeYCshFS7%N8sMLR*#S1=aO?-2mrhQWGjH zp7=W(0%WM4*K7w^mlDOZ9F zp*k7{LY}fp;*~?oyrcF0(Rei`)-l<#7FuZNkl+oqgh264P!x4uR?5gKxnK&mWrJrg0h@%aV8z|6Y#PjrHbMI(o5wE#F5GA|`Wpca z0pQ~Z^ja=?7n$g?lualUDvpZw3M11cI+>Ft;IcY5xS52^&2$sU7CV5xq2eXXTthB- z7g?%a=4Ltpv2*H&0LD)gS*l*<`UN5iv`3&5vFj8oZs#H(&X)Qit2QK!8mW4jn<#LH z4jujnpcOxu5$LgR0sx^(ehCnT8iL}B4siQ_mmmGi1Oz%_>w({B(BK8!qeqWF0B8mR zeKzF%ucY))H(L)U3pU@(tY6eN;}1njI~C!!)lpranZF9@h#22wFU0N`x|)Jt@T zfo7oiY9hOXiVkY6%OsQs{EUtrJLW>*RbIg2d<1%?IHQ#nBODGFMnyY4%ygBRn^^vb zw+Rqv2T`EE=!k-ZK=(bie2wgmojp!_tXXL-}D7qV6Dc)Mx$q)K`c zkjf`ASC02d!0xn=$06_s#}3>hiu{>YLo&^60-BJKhJ=~xAn+Ch+9T#m(+Pzw0gWK) zkyGLtkw=RxOItE?81h%SvZ^VSW1vmIe_Ni(hg1ItlgvGye+Qr%S*%TTtNs80002ov JPDHLkV1lE(LIwZ; literal 0 HcmV?d00001 diff --git a/browser/extensions/screenshots/webextension/icons/icon-38.png b/browser/extensions/screenshots/webextension/icons/icon-38.png new file mode 100644 index 0000000000000000000000000000000000000000..8c37139cf6beedd6f0e39c21e314f0655561eb85 GIT binary patch literal 1102 zcmV-U1hM;xP)h`UpQ=V)Q#^I&JP_m|qt|Ijn%b|`-aI@mBr>)``>PT2FypTXL|Pzg+fQUWIn z1DFr40$AMy;-}y^l}c>~$z(EX!!DoK$9JXQ4Ej?}Ib~(7hd>7#=9JmihaNtC9oF(Z z^Q6DN6Ig~aJ1*=n!kTVi`WhDUz?w@SFfwOCaR0|1d+fSQe35*ATjXmZeo6ixd;k6S zue2b5VNHMi8t79g$) zqGJ?KO(Qa&W3qhyy9``oP{KlrF@8;f8Yic+pSG8iA`_n$2gIB?b5>+dAHtm9CM>Yt zzEKG8R%O!F;n%u33G;4LP(-PH<+-+n&Cf!x2Li*@`9@2Ys@GFt(=l4D-qswln;-N}^7;JgrJ&Ng zila$t&JasB6OiSn$=uP7?C!&y*#q+~z4X#mam|nlC&n009+<5jZ0~vY3~5D|C6_s83D{g4ZMN;#hQ=*uO!xIj-(caAjm3E5Fu4%ncWZQ;_0B=5=fB_RROM zBHten`^+$>iRX#8F+z1kl1Pm;U z3{s9pi}S}hVK4W{yn`%sfch6#UtWgfgjzZ&$1^tqd?;XGYF%zL=cvwhmgX&-ii-wN zW(3?LE?y%t|43`KG3KI`M+`%%wKM}%+jFI(GJL6u_(vgWjlvD(4$#IP4%T<|66w=%SY5P7n)M# UT$fi#c>n+a07*qoM6N<$f`4lki2wiq literal 0 HcmV?d00001 diff --git a/browser/extensions/screenshots/webextension/icons/icon-48.png b/browser/extensions/screenshots/webextension/icons/icon-48.png new file mode 100644 index 0000000000000000000000000000000000000000..c96b2444f33e2d56aa10dbd5b33f2d5125dbe21e GIT binary patch literal 1054 zcmV+(1mXLMP)xE za=j)QM2hEzy@Dn!4P!tnXa?;8zE}g&%YgL)?E$!Hs|-2y2E+o=Be3O1f#{iFn~V{zG4iJv_gu zR{WzIK=?-y*(6oOYn>5IG7bd#yf7AXQb9bV=}o-F1m;%V2cpeJhHJ$i63IvS9%W8d zWdKguQnEY~kX@7Q)ir#_f9SKvV~wgYfXHGK1VW=Gqt70XHIgFky6di+Uu$zZG^>^T z#bmB3=4T6(zyaxQn@x@6BO3Z_b3%wsf_P)hIz;k)h=3l7WB@VVvgr?98)1xPauZu0 z!ZXK32NIb)ckbNzl4F46Z7UkhDwl9%z(n$zBihZXF{7{s^}9{TW`KKG8W>oX$&fLa z>j9kaMlMo@6Q2<#A+6<>qLdc)A$;$>_g;j_+(nA#IPr$$_d=nteB@%jVH7H4G8bu* zY0;|Y4n*1Q9O=rIq~w1$P|9qrGXG>Q{;NjX#1E2^(H>GC z&&0>ZMPk2n+ICnaPt+uUieF5EQS8aQ*SZxgXh=!t8fj8?GYf!@(!^uS(-6o zB+-gCyEZBLE++9kjWRn2Rc;od@&HfYc$)d}B~wGZi0nX0dV5HHERh_MI00@SkV*JX zB#$xYwr%DE%A7??ru#T5OwHaalgVuD)6YzDAk1(5iqV$!yB%X9)J$5To${YTNPHcqIh zuUT*?UT&h@h-!>kLo4~uV~xqI%xSTUdP}A6=PAEl{Um(He-5et>_Swe(nNf(Pb#fR zJ}zowmH0(H$@5VJpfAi(nap)@5EX*+)2s}yPYOLNGM-Q>8$=(*Bv6q-8!Ktz4?KcF z$7lxZnwa<-*Txu=L``f}c~eR;0aNm0K=GTd4N-(Ad@2Dz+dSIAWjyZg{LY1l`*>)e zLnY3I;NV)xJuMuZ_|jNTT&l2O&Ky?BlQ}eiAA*G@euqd-DCcZ+tSH&no+wHVB*kM= zayIG)GEMvi!XqFaj+=(lX{7D$qCTbf18`(q#@h@eazj(KM-7s?BdglO9!cF&R_k$_ Yd7Li-X)2jcYXATM07*qoM6N<$f@31_iU0rr literal 0 HcmV?d00001 diff --git a/browser/extensions/screenshots/webextension/icons/icon-64.png b/browser/extensions/screenshots/webextension/icons/icon-64.png new file mode 100644 index 0000000000000000000000000000000000000000..08c45bb72e8638b6912337a4bdd8ab529205ba96 GIT binary patch literal 1371 zcmV-h1*H0kP) z_KXR7mq_=NwKi)>l8nJd&K(f%Knie!uf7r*DxlWpt)qaB0y+xlD4?T&qIm1|`ozq3 zb2f4#8GV^azD391cn$AiOxtNGrF0@@Ig^b3-iS?M48~$kTYwiymLsFz1DsFC9L%jY zc(U7(kv{+^u>S!W6@Ky_;2lbUtV#tz_^;dS{oMgFE297eF&7{qLIK#u@}fPH{zN2O{`u#hgAFJMui-t6$=e+uyX6&tz1)#D+Om;AF?OrG`H&p} z6CUCySSyp{M^4tNkn+;x7nT)%ofC}r& zS&N6Cd+Z>q<+#Di%>uss^2-m+Mh|w8H8yf>Lj{1^W=%%0^LPrgWXXW+CS`V}XObry z=ltZ?z4qE`@E8i1CMSsg7J$#~ZaABe{8)5LhNZY<_J|8uvT2SBeBM*wyG{3aM0%4~ z*4OA`jpUXNf@n;7agGVTAhOqxWx+GsrJ3|I&y@FXBFSP!gqPU{F@O0+PXRs~M$eZY zkx5U798d7yi6ko(`jAi}ghr(6kkPLTEa)d@-2nlqIQaU!Z1gpM@bc*23ZJ^URG4H{ zCb=VEaSsr6P2~a(mtmuO!-W0h>+fuI{|z?SV5sf4qq z1s{gOkHg`ejoc}+(4_sulJ()c{&MsJP~q^?VEKeda0ds0H4y1l{`2)~_g3w%fU+sE z_y6nVRc<&b zGRZRmCVz*uc7jTS|Bo?#>l^6U=*|w>9HlvepneCA&Lr0`>m~~?yzp2VL2x)`IW>UP zyygmYe}C4wH2BWZ%nPJsNoaTW=y4LXVo$jD}8Y4KjsKnG6XZ1gFSrA95Y4gO)t+5!oW z1z?KNuE;pjZ$xq=k)FhC*9M&~PlQO`0nc^^KYhDem!$lu@Gr>s_sa%F7M>7wI9Jve zGTZK=B$IrKI(@Y+O1gKeUUO9zg;yMwG87QuvUCf7aWcYn`4<1`>|dkxhKa$xd79>1 dW(NEAc>rx;P=%G9zV-kB002ovPDHLkV1hAOo(2E_ literal 0 HcmV?d00001 diff --git a/browser/extensions/screenshots/webextension/icons/icon-highlight-19.png b/browser/extensions/screenshots/webextension/icons/icon-highlight-19.png new file mode 100644 index 0000000000000000000000000000000000000000..6f84b582bce39b345cbde70c073eef92ca47e50a GIT binary patch literal 1049 zcmV+!1m^pRP)Px&(@8`@R5%f>Q(tIXWfcFu@1{2)O>ASfYD>D_Hc@Kp(~9=NEXiDnmKo5|APQwD zLS^h>KI}mqqt4-j4+<(s3&jmVnSJrkMnj2hn)y<$DA@r;uv>S+c zk`lJS2;AfZ4pTy}t1id6hHjpaIRwbBOBWa4ci093DGvIM-|sIC4h}Y!mzO{Ecswkb zOd9EQ`qI|c*4I9t&l(vS5wo+iS1Of?7#}e3HP0g z|8bCcGGC_Hl_`$aQ?*)cFcyhL0H~We$W?%z3z^Ir{l9RBhKBTP_VOti$D{(NYL+Qp zL9-K0DmaM~apNc;vh@F(WHJSn^XiK-#;!~Vau5kGxm#VQ=jP@bvDkrUL5R_4G#;D8B9UXP*RsAVoO3CZp{HeT6WL&c{oJJc1R+K>UQc=h?`EF?u?ecxM#3S+EdbNH+ z5G;W6=f=m!8#uff8KX$m+oL@qDD4l0`ZfZAhZ%r1hS&EC0DmYHyq3@BtCN$HNQrWg zgY^;+>cREcaj5JJ`u?iz)WU^gVZ{dP3qE=Sxbe6U5T=yJBoHPTNSL1s@hQw57AN(=3a8G?zjqQEvLRCkC=u0=)q zq+mIDm&@_??%lpz^QNi0y1IZd<+il!HWRVLu0Kk*UdNOu2(s5O%oriHeOSsKtOx%n zcA?{eSI<_rzu&u4tsm30-dzI&{cG#%>)h-0y7an!T()UfR)*>e2<)Rw`IITn_YmY3 zK1eeBsk+p6mzEa3@%hpW*C&8?DlTSNPBa#KTC*&RQ$|;0Wz9AL`WYiXNG=u^Q)@t# zFQrqV%2)IA^Lcrmo}M-rvx}brQ9hEBME*L2FecYNS zyXTXssi{UF(DMv8#o>lwI)vbE%MeE`&bxEDtG}Cu{w4r_3|H@G#JJpqyhr+Py!5%Z T{dzD{00000NkvXXu0mjfdoc241A2K$IQ&kG_oXnIh;IZW@bKS=KF>H$=r0Md{GVE>;#J9DjmqwV@s{}p4GIi zH=@?POuX5VoV4bhe-Zuabi(?X^cVP>}Rj^*KcOm*;ef8Cg@qgpL4E_xpHk@PqF!<1iG1iYY;K!QL zhrNiTANS7#vtPT$4STKwTQOj&`Wy`Rk62*lSBT_2X8poBeN#jpkFm4T4SUA@O9zR_ z^F+KK=yT>|V+i54HRm#smIHt)BhtgFdizT5J25eFiE}yw%XND1uXl+YTLK_QnE3+* ze>wd&!#7)o!{NDW5@XE%9dl)Snfo``ev$Um7M%C~*(M+s78cHC?%pvN46dw~l}@(B zPE8wp`7!#XIkopk1*JNEvrFXP#rbj~275IybB*nG`_AGc=kC%~`|n3Pr4Ss+(M(AE zA1eg2egi84wl_}DM4u`-u{TdIoam=@K;g<qnD}cGV}E`2*=Oe$1n5EY zwY$Cd4^^${oqx81dbY(lMk3*wVYx*Dg-0_J|0D`eg13m2oReL}U+rJdegp`bNIoj1 z9j6~+Os(KY6DOacb5vu}4QX87iZ%Qz{yrkzUNjD;hwQFR7v@VZr~=2t$DNZ+MSVyf zlrLmk;oL-jn4X@#Vq_k(jnZpH%poBFhDdjSY}4$y<<(b*=L*SW1W?0{iik&MHX_(j zTL|GUCf!m63Y^tywQj0ICx|)NvVpxCUcJ1G24Kv7K2;;jsf0Q{j^NRdsmRzx z!15YEHi|J3nQBHCe87r42udYwXFm`p0umin!QP(Xrah26BJ#43yp^eQcC(-G+R2t7 zgg{xAI_Fym_*%fg)V5rpux1~`_5|>6oJtuDpv*A)=~7xrKZu#fF!qBFKDY=X6;@Ql zcg9ILCHShq4 zg%SnwzRke8z^n?kY6A>VL&55&rr2&yQ~lBm)8A$jKuM{YzUQ&jUSV!OQd9HuJ$bzp z0;JF43q}rEKEv^^B%xRG7_?O#-U3+5>2_=IMd4}v2#uvA&>hCMqb^4! cvfi4sQ??j5*}InmF8}}l07*qoM6N<$f-^q*?f?J) literal 0 HcmV?d00001 diff --git a/browser/extensions/screenshots/webextension/icons/icon-starred-19.png b/browser/extensions/screenshots/webextension/icons/icon-starred-19.png new file mode 100644 index 0000000000000000000000000000000000000000..395afb5abfaafa67a51c9ada8acde311f4d239e1 GIT binary patch literal 993 zcmV<710MW|P)Px&n@L1LR5%f>R9$FXRS=#zcklk>hq%}WOH#|OyW5(!nwC82?&j`pgGh^lLVYNR z(uOKNln8y2(&|I;B}jeflaW>=2vrDH4MyVLY;bL$QbqdX8roe;#geFP)2w$l_nhgR zvuP zE!6wl7?8Mye1=DH_69YnnqrbByxFv>x#lm8$W&EffQSO=|$@0B#lagTf81zbJY~qld!w6Q#N0(+GHssXXU6!-EFS4vuiO zuCdqpAfAC#V$^^Uzy{{~)AaQ8ijK7qXSG(!k86$9n8&?&ku9m$4+`D}+unmN`YoD3 zW$xY!j#Jp3&F*~C2VE(r0v#$83MrE;tq9Yx9t1M0Ztw%9_XaEwv<~w2O(o?SApas$ z%rI1)2nJW1>b#S6_JUN2dq4;kwyUi*AqdYfFkmw?GZi~%Z9ySkMS*X#JrG8FD+B(( z)pT$h7z`J=)G~nB2hXJXA)4X?5pQuX&jN-1)xZW!8@6rp7~o%A%?&Ao|5%;Y9Zp`- zKHf{`Q~M)$ebckU{cnDOr%~bsZ1N2_kxNcw3bEHr)qJb~j`Muq${p`pbHqAXd5!uC zE%P|6L*#h}7$X|G;AH8*YEzC4WpWdth&|dmu%EkpQu@HqRL04C7a_rSaRh~xXShpT zjRsl}7N$!j_uWWj-Xg%Kh^Ui*MjBr8d_I%k5e(K>nd;Y^-=;}QJ8~n2*5BeuPJw2Wf*8BH?!6G|1JI+x{3re>n+GF#WTPh0QB&P-Nr##fOM ze@4|b&0bTlFwCUFK=MIKtTqYcK;D-ieNp8-^AX2Iwpu103qx~^Z+*K&kNNE4`B&}Y ze?MwhD9l_xn1<3|sq{e&fF0on`};P#*x$Cz@~71e*5NRaYw0YT%`QcYqG)#B$M19V zZ#BLU$9La-_vzy?k=^I;KrN1Fja6X%?E1ZDYX~d~qGem1CU&vHtpw7BEj_g%Fh;AT zP)l}SOhrpszAvLT#lft#)7m8q%Of(6B%u`C)pohkQq{o%vk#3qwZjfO%!z6|6o20b zdk%SbD}L^9(M1xNfcr zMdf~yPN$c5=TZKxS^y1z-c<<|$i5oH12-a*$*hzFqeit148_v1a4wfy3{HnAof?Pj zdkWilC4PS|0rv%Eq5^E0E{g!J(1K`vW!LIEyUH=SQ$YB44 z3X#P3(){wRl39OVVm+uELU{Q)utus;O~oZ zf1(D{jUt@fp_~p!m0palb*_$xE?Zt0aJK+p!<Z(M5qA8WNeE3+@d#BX5**e9$=RuAtd#gsXzjbrV_^0XcY&`E9i qn%*6$(ml5J`|rW;>dmIZ)axJc#=%=FCvwmL0000 + + + + + + + + + + + diff --git a/browser/extensions/screenshots/webextension/icons/menu-myshot.svg b/browser/extensions/screenshots/webextension/icons/menu-myshot.svg new file mode 100644 index 000000000000..e1e6774641b7 --- /dev/null +++ b/browser/extensions/screenshots/webextension/icons/menu-myshot.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/browser/extensions/screenshots/webextension/icons/menu-visible.svg b/browser/extensions/screenshots/webextension/icons/menu-visible.svg new file mode 100644 index 000000000000..ad9bca760252 --- /dev/null +++ b/browser/extensions/screenshots/webextension/icons/menu-visible.svg @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/browser/extensions/screenshots/webextension/icons/onboarding-1.png b/browser/extensions/screenshots/webextension/icons/onboarding-1.png new file mode 100644 index 0000000000000000000000000000000000000000..541f5e01c200c595b9f122468d295c8bd5f6ca25 GIT binary patch literal 27607 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%0AlV1noGdUd!t@fn7|%MxcEc^~6UA~ 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

YOl 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 literal 0 HcmV?d00001 diff --git a/browser/extensions/screenshots/webextension/icons/onboarding-3.png b/browser/extensions/screenshots/webextension/icons/onboarding-3.png new file mode 100644 index 0000000000000000000000000000000000000000..02db5d2bdf575b68db30a0dcc7bfb8b84ac18442 GIT binary patch literal 34779 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 btda)PX^1^*ptc$d%jJvO7xMqS`SAY$M*xUS literal 0 HcmV?d00001 diff --git a/browser/extensions/screenshots/webextension/icons/onboarding-4.png b/browser/extensions/screenshots/webextension/icons/onboarding-4.png new file mode 100644 index 0000000000000000000000000000000000000000..7f0cc5c56af63fa32d980947a5058956b9f37718 GIT binary patch literal 35881 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]@HQ2^5?{#S{kE5jG}H2 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", + "https://screenshots.firefox.com/" + ] +} diff --git a/browser/extensions/screenshots/webextension/onboarding/slides.html b/browser/extensions/screenshots/webextension/onboarding/slides.html new file mode 100644 index 000000000000..a956f769c90a --- /dev/null +++ b/browser/extensions/screenshots/webextension/onboarding/slides.html @@ -0,0 +1,60 @@ + + + + + + + + +
+ +
+
+ +
+
+
+

Firefox ScreenshotsBeta

+

+
+ +
+
+
+
+
+

+

+
+
+
+
+
+

+

+
+
+
+
+
+

+

+
+
+ + + + + + +
+ + + + +
+ +
+
+ + diff --git a/browser/extensions/screenshots/webextension/onboarding/slides.js b/browser/extensions/screenshots/webextension/onboarding/slides.js new file mode 100644 index 000000000000..8daedbce547b --- /dev/null +++ b/browser/extensions/screenshots/webextension/onboarding/slides.js @@ -0,0 +1,202 @@ +/* globals catcher, onboardingHtml, onboardingCss, browser, util, shooter, callBackground, assertIsTrusted */ + +"use strict"; + +this.slides = (function () { + let exports = {}; + + const { watchFunction } = catcher; + + let iframe; + let doc; + let currentSlide = 1; + let numberOfSlides; + let callbacks; + let backend; + + exports.display = function (addCallbacks) { + if (iframe) { + throw new Error("Attemted to call slides.display() twice"); + } + return new Promise((resolve, reject) => { + callbacks = addCallbacks; + // FIXME: a lot of this iframe logic is in ui.js; maybe move to util.js + iframe = document.createElement("iframe"); + iframe.src = browser.extension.getURL("blank.html"); + iframe.id = "firefox-screenshots-onboarding-iframe"; + iframe.style.zIndex = "99999999999"; + iframe.style.border = "none"; + iframe.style.position = "fixed"; + iframe.style.top = "0"; + iframe.style.left = "0"; + iframe.style.margin = "0"; + iframe.scrolling = "no"; + updateIframeSize(); + let html = onboardingHtml.replace('', ``); + html = html.replace(/MOZ_EXTENSION([^\"]+)/g, (match, filename) => { + return browser.extension.getURL(filename); + }); + iframe.onload = catcher.watchFunction(() => { + let parsedDom = (new DOMParser()).parseFromString( + html, + "text/html" + ); + doc = iframe.contentDocument; + doc.replaceChild( + doc.adoptNode(parsedDom.documentElement), + doc.documentElement + ); + doc.addEventListener("keyup", onKeyUp, false); + callBackground("getBackend").then((backendResult) => { + backend = backendResult; + localizeText(doc); + activateSlide(doc); + resolve(); + }).catch((error) => { + // Handled in communication.js + }); + }); + document.body.appendChild(iframe); + iframe.focus(); + window.addEventListener("resize", onResize, false); + }); + }; + + exports.remove = exports.unload = function () { + window.removeEventListener("resize", onResize, false); + if (doc) { + doc.removeEventListener("keyup", onKeyUp, false); + } + util.removeNode(iframe); + iframe = doc = null; + currentSlide = 1; + numberOfSlides = undefined; + callbacks = undefined; + }; + + function localizeText(doc) { + let els = doc.querySelectorAll("[data-l10n-id]"); + for (let el of els) { + let id = el.getAttribute("data-l10n-id"); + let text = browser.i18n.getMessage(id); + el.textContent = text; + } + els = doc.querySelectorAll("[data-l10n-label-id]"); + for (let el of els) { + let id = el.getAttribute("data-l10n-label-id"); + let text = browser.i18n.getMessage(id); + el.setAttribute("aria-label", text); + } + // termsAndPrivacyNotice is a more complicated substitution: + let termsContainer = doc.querySelector(".onboarding-legal-notice"); + termsContainer.innerHTML = ""; + let termsSentinal = "__TERMS__"; + let privacySentinal = "__PRIVACY__"; + let sentinalSplitter = "!!!"; + let linkTexts = { + [termsSentinal]: browser.i18n.getMessage("termsAndPrivacyNoticeTermsLink"), + [privacySentinal]: browser.i18n.getMessage("termsAndPrivacyNoticyPrivacyLink") + }; + let linkUrls = { + [termsSentinal]: "https://www.mozilla.org/about/legal/terms/services/", + [privacySentinal]: "https://www.mozilla.org/privacy/firefox-cloud/" + }; + let text = browser.i18n.getMessage( + "termsAndPrivacyNotice", + [sentinalSplitter + termsSentinal + sentinalSplitter, + sentinalSplitter + privacySentinal + sentinalSplitter]); + let parts = text.split(sentinalSplitter); + for (let part of parts) { + let el; + if (part === termsSentinal || part === privacySentinal) { + el = doc.createElement("a"); + el.href = linkUrls[part]; + el.textContent = linkTexts[part]; + el.target = "_blank"; + } else { + el = doc.createTextNode(part); + } + termsContainer.appendChild(el); + } + } + + function activateSlide(doc) { + numberOfSlides = parseInt(doc.querySelector("[data-number-of-slides]").getAttribute("data-number-of-slides"), 10); + doc.querySelector("#next").addEventListener("click", watchFunction(assertIsTrusted(() => { + shooter.sendEvent("navigate-slide", "next"); + next(); + })), false); + doc.querySelector("#prev").addEventListener("click", watchFunction(assertIsTrusted(() => { + shooter.sendEvent("navigate-slide", "prev"); + prev(); + })), false); + for (let el of doc.querySelectorAll(".goto-slide")) { + el.addEventListener("click", watchFunction(assertIsTrusted((event) => { + shooter.sendEvent("navigate-slide", "goto"); + let el = event.target; + let index = parseInt(el.getAttribute("data-number"), 10); + setSlide(index); + })), false); + } + doc.querySelector("#skip").addEventListener("click", watchFunction(assertIsTrusted((event) => { + shooter.sendEvent("cancel-slides", "skip"); + callbacks.onEnd(); + })), false); + doc.querySelector("#done").addEventListener("click", watchFunction(assertIsTrusted((event) => { + shooter.sendEvent("finish-slides", "done"); + callbacks.onEnd(); + })), false); + setSlide(1); + } + + function next() { + setSlide(currentSlide + 1); + } + + function prev() { + setSlide(currentSlide - 1); + } + + const onResize = catcher.watchFunction(function () { + if (! iframe) { + log.warn("slides onResize called when iframe is not setup"); + return; + } + updateIframeSize(); + }); + + function updateIframeSize() { + iframe.style.height = window.innerHeight + "px"; + iframe.style.width = window.innerWidth + "px"; + } + + const onKeyUp = catcher.watchFunction(assertIsTrusted(function (event) { + if ((event.key || event.code) === "Escape") { + shooter.sendEvent("cancel-slides", "keyboard-escape"); + callbacks.onEnd(); + } + })); + + function setSlide(index) { + if (index < 1) { + index = 1; + } + if (index > numberOfSlides) { + index = numberOfSlides; + } + shooter.sendEvent("visited-slide", `slide-${index}`); + currentSlide = index; + let slideEl = doc.querySelector("#slide-container"); + for (let i=1; i<=numberOfSlides; i++) { + let className = `active-slide-${i}`; + if (i == currentSlide) { + slideEl.classList.add(className); + } else { + slideEl.classList.remove(className); + } + } + } + + return exports; +})(); +null; diff --git a/browser/extensions/screenshots/webextension/randomString.js b/browser/extensions/screenshots/webextension/randomString.js new file mode 100644 index 000000000000..3721927bade3 --- /dev/null +++ b/browser/extensions/screenshots/webextension/randomString.js @@ -0,0 +1,14 @@ +/* exported randomString */ + +"use strict"; + +this.randomString = function randomString(length, chars) { + let randomStringChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + chars = chars || randomStringChars; + let result = ""; + for (let i=0; i { + if (result.type === "success") { + return result.value; + } else if (result.type === "error") { + let exc = new Error(result.message); + exc.name = "BackgroundError"; + throw exc; + } else { + log.error("Unexpected background result:", result); + let exc = new Error(`Bad response type from background page: ${result.type || undefined}`); + exc.resultType = result.type || "undefined"; + throw exc; + } + }); +} +null; diff --git a/browser/extensions/screenshots/webextension/selector/documentMetadata.js b/browser/extensions/screenshots/webextension/selector/documentMetadata.js new file mode 100644 index 000000000000..fc93b013c6b7 --- /dev/null +++ b/browser/extensions/screenshots/webextension/selector/documentMetadata.js @@ -0,0 +1,87 @@ +"use strict"; + +this.documentMetadata = (function () { + + function findSiteName() { + let el = document.querySelector("meta[property='og:site_name']"); + if (el) { + return el.getAttribute("content"); + } + // nytimes.com uses this property: + el = document.querySelector("meta[name='cre']"); + if (el) { + return el.getAttribute("content"); + } + return null; + } + + function getOpenGraph() { + let openGraph = {}; + // If you update this, also update _OPENGRAPH_PROPERTIES in shot.js: + let forceSingle = `title type url`.split(/\s+/g); + let openGraphProperties = ` + title type url image audio description determiner locale site_name video + image:secure_url image:type image:width image:height + video:secure_url video:type video:width image:height + audio:secure_url audio:type + article:published_time article:modified_time article:expiration_time article:author article:section article:tag + book:author book:isbn book:release_date book:tag + profile:first_name profile:last_name profile:username profile:gender + `.split(/\s+/g); + for (let prop of openGraphProperties) { + let elems = document.querySelectorAll(`meta[property='og:${prop}']`); + if (forceSingle.includes(prop) && elems.length > 1) { + elems = [elems[0]]; + } + let value; + if (elems.length > 1) { + value = []; + for (let elem of elems) { + let v = elem.getAttribute("content"); + if (v) { + value.push(v); + } + } + if (! value.length) { + value = null; + } + } else if (elems.length === 1) { + value = elems[0].getAttribute("content"); + } + if (value) { + openGraph[prop] = value; + } + } + return openGraph; + } + + function getTwitterCard() { + let twitterCard = {}; + // If you update this, also update _TWITTERCARD_PROPERTIES in shot.js: + let properties = ` + card site title description image + player player:width player:height player:stream player:stream:content_type + `.split(/\s+/g); + for (let prop of properties) { + let elem = document.querySelector(`meta[name='twitter:${prop}']`); + if (elem) { + let value = elem.getAttribute("content"); + if (value) { + twitterCard[prop] = value; + } + } + } + return twitterCard; + } + + return function documentMetadata() { + let result = {}; + result.docTitle = document.title; + result.siteName = findSiteName(); + result.openGraph = getOpenGraph(); + result.twitterCard = getTwitterCard(); + return result; + }; + +})(); +null; diff --git a/browser/extensions/screenshots/webextension/selector/shooter.js b/browser/extensions/screenshots/webextension/selector/shooter.js new file mode 100644 index 000000000000..1840a5cab539 --- /dev/null +++ b/browser/extensions/screenshots/webextension/selector/shooter.js @@ -0,0 +1,151 @@ +/* globals callBackground, documentMetadata, uicontrol, util, ui, catcher */ +/* globals XMLHttpRequest, window, location, alert, console, domainFromUrl, randomString */ +/* globals clipboard, document, setTimeout, location */ + +"use strict"; + +this.shooter = (function () { // eslint-disable-line no-unused-vars + let exports = {}; + const { AbstractShot } = window.shot; + + const RANDOM_STRING_LENGTH = 16; + let backend; + let shot; + let supportsDrawWindow; + + 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)}[^\s",>]*`, '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)); + }); + + { + let canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas'); + let ctx = canvas.getContext('2d'); + supportsDrawWindow = !! ctx.drawWindow; + } + + function screenshotPage(selectedPos) { + if (! supportsDrawWindow) { + return null; + } + let height = selectedPos.bottom - selectedPos.top; + let width = selectedPos.right - selectedPos.left; + let canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas'); + canvas.width = width * window.devicePixelRatio; + canvas.height = height * window.devicePixelRatio; + let ctx = canvas.getContext('2d'); + if (window.devicePixelRatio !== 1) { + ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + } + ui.iframe.hide(); + try { + ctx.drawWindow(window, selectedPos.left, selectedPos.top, width, height, "#fff"); + } finally { + ui.iframe.unhide(); + } + return canvas.toDataURL(); + } + + let isSaving = null; + + exports.takeShot = function (captureType, selectedPos) { + // 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 (isSaving) { + return; + } + isSaving = setTimeout(() => { + isSaving = null; + }, 1000); + selectedPos = selectedPos.asJson(); + let captureText = util.captureEnclosedText(selectedPos); + let dataUrl = screenshotPage(selectedPos); + if (dataUrl) { + shot.addClip({ + createdDate: Date.now(), + image: { + url: dataUrl, + 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: shot.id, + shot: shot.asJson() + }).then((url) => { + const copied = clipboard.copy(url); + return callBackground("openShot", { url, copied }); + }, (error) => { + if (error.name != "BackgroundError") { + // BackgroundError errors are reported in the Background page + throw error; + } + }).then(() => uicontrol.deactivate())); + }; + + exports.downloadShot = function (selectedPos) { + let dataUrl = 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 + }); + } + catcher.watchPromise(promise.then((dataUrl) => { + ui.triggerDownload(dataUrl, shot.filename); + uicontrol.deactivate(); + })); + }; + + exports.sendEvent = function (...args) { + callBackground("sendEvent", ...args); + }; + + shot = new AbstractShot( + backend, + randomString(RANDOM_STRING_LENGTH) + "/" + domainFromUrl(location), + { + origin: window.shot.originFromUrl(location.href) + } + ); + shot.update(documentMetadata()); + + return exports; +})(); +null; diff --git a/browser/extensions/screenshots/webextension/selector/ui.js b/browser/extensions/screenshots/webextension/selector/ui.js new file mode 100644 index 000000000000..f547c8e8abff --- /dev/null +++ b/browser/extensions/screenshots/webextension/selector/ui.js @@ -0,0 +1,609 @@ +/* globals window, document, console, browser */ +/* globals util, catcher, inlineSelectionCss, callBackground, assertIsTrusted */ + +"use strict"; + +this.ui = (function () { // eslint-disable-line no-unused-vars + let exports = {}; + const SAVE_BUTTON_HEIGHT = 50; + + const { watchFunction } = catcher; + + // The tag itself can have margins and offsets, which need to be used when + // setting the position of the boxEl. + function getBodyRect() { + if (getBodyRect.cached) { + return getBodyRect.cached; + } + let rect = document.body.getBoundingClientRect(); + let cached = { + top: rect.top + window.scrollY, + bottom: rect.bottom + window.scrollY, + left: rect.left + window.scrollX, + right: rect.right + window.scrollX + }; + // FIXME: I can't decide when this is necessary + // *not* necessary on http://patriciogonzalezvivo.com/2015/thebookofshaders/ + // (actually causes mis-selection there) + // *is* necessary on http://atirip.com/2015/03/17/sorry-sad-state-of-matrix-transforms-in-browsers/ + cached = {top: 0, bottom: 0, left: 0, right: 0}; + getBodyRect.cached = cached; + return cached; + } + + exports.isHeader = function (el) { + while (el) { + if (el.classList && + (el.classList.contains("myshots-button") || + el.classList.contains("visible") || + el.classList.contains("full-page"))) { + return true; + } + el = el.parentNode; + } + return false; + } + + let substitutedCss = inlineSelectionCss.replace(/MOZ_EXTENSION([^\"]+)/g, (match, filename) => { + return browser.extension.getURL(filename); + }); + + function makeEl(tagName, className) { + if (! iframe.document()) { + throw new Error("Attempted makeEl before iframe was initialized"); + } + let 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); + } + + let iframeSelection = exports.iframeSelection = { + element: null, + addClassName: "", + sizeTracking: { + timer: null, + windowDelayer: null, + lastHeight: null, + lastWidth: null + }, + document: null, + display: function (installHandlerOnDocument) { + return new Promise((resolve, reject) => { + if (! this.element) { + this.element = document.createElement("iframe"); + this.element.src = browser.extension.getURL("blank.html"); + this.element.id = "firefox-screenshots-selection-iframe"; + this.element.style.display = "none"; + this.element.style.zIndex = "99999999999"; + this.element.style.border = "none"; + this.element.style.position = "absolute"; + this.element.style.top = "0"; + this.element.style.left = "0"; + this.element.style.margin = "0"; + this.element.scrolling = "no"; + this.updateElementSize(); + this.element.onload = watchFunction(() => { + this.document = this.element.contentDocument; + this.document.documentElement.innerHTML = ` + + + + + `; + installHandlerOnDocument(this.document); + if (this.addClassName) { + this.document.body.className = this.addClassName; + } + resolve(); + }); + document.body.appendChild(this.element); + } else { + resolve(); + } + }); + }, + + hide: function () { + this.element.style.display = "none"; + this.stopSizeWatch(); + }, + + unhide: function () { + this.updateElementSize(); + this.element.style.display = ""; + this.initSizeWatch(); + this.element.focus(); + }, + + updateElementSize: function (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"; + } + let height = Math.max( + document.documentElement.clientHeight, + document.body.clientHeight, + document.documentElement.scrollHeight, + document.body.scrollHeight, + window.innerHeight); + if (height !== this.sizeTracking.lastHeight) { + this.sizeTracking.lastHeight = height; + this.element.style.height = height + "px"; + } + let width = Math.max( + document.documentElement.clientWidth, + document.body.clientWidth, + document.documentElement.scrollWidth, + document.body.scrollWidth, + window.innerWidth); + if (width !== this.sizeTracking.lastWidth) { + this.sizeTracking.lastWidth = width; + this.element.style.width = width + "px"; + } + if (force && visible) { + this.element.style.display = ""; + } + }, + + initSizeWatch: function () { + this.stopSizeWatch(); + this.sizeTracking.timer = setInterval(watchFunction(this.updateElementSize.bind(this)), 2000); + window.addEventListener("resize", this.onResize, true); + }, + + stopSizeWatch: function () { + 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: function (x, y) { + this.element.style.pointerEvents = "none"; + let el; + try { + el = document.elementFromPoint(x, y); + } finally { + this.element.style.pointerEvents = ""; + } + return el; + }, + + remove: function () { + this.stopSizeWatch(); + util.removeNode(this.element); + this.element = this.document = null; + } + }; + + iframeSelection.onResize = watchFunction(onResize.bind(iframeSelection)); + + let iframePreSelection = exports.iframePreSelection = { + element: null, + document: null, + sizeTracking: { + windowDelayer: null + }, + display: function (installHandlerOnDocument, standardOverlayCallbacks) { + return new Promise((resolve, reject) => { + if (! this.element) { + this.element = document.createElement("iframe"); + this.element.src = browser.extension.getURL("blank.html"); + this.element.id = "firefox-screenshots-preselection-iframe"; + this.element.style.zIndex = "99999999999"; + this.element.style.border = "none"; + this.element.style.position = "fixed"; + this.element.style.top = "0"; + this.element.style.left = "0"; + this.element.style.margin = "0"; + this.element.scrolling = "no"; + this.updateElementSize(); + this.element.onload = watchFunction(() => { + this.document = this.element.contentDocument; + this.document.documentElement.innerHTML= ` + + + + + +
+
+
+
+ +
+ + +
+
+
+ `; + installHandlerOnDocument(this.document); + if (this.addClassName) { + this.document.body.className = this.addClassName; + } + const overlay = this.document.querySelector(".preview-overlay"); + overlay.querySelector(".preview-instructions").textContent = browser.i18n.getMessage("screenshotInstructions"); + overlay.querySelector(".myshots-link").textContent = browser.i18n.getMessage("myShotsLink"); + overlay.querySelector(".visible").textContent = browser.i18n.getMessage("saveScreenshotVisibleArea"); + overlay.querySelector(".full-page").textContent = browser.i18n.getMessage("saveScreenshotFullPage"); + overlay.querySelector(".myshots-button").addEventListener( + "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onOpenMyShots)), false); + overlay.querySelector(".visible").addEventListener( + "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onClickVisible)), false); + overlay.querySelector(".full-page").addEventListener( + "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onClickFullPage)), false); + resolve(); + }); + document.body.appendChild(this.element); + this.unhide(); + } else { + resolve(); + } + }); + }, + + updateElementSize: function () { + this.element.style.height = window.innerHeight + "px"; + this.element.style.width = window.innerWidth + "px"; + }, + + hide: function () { + window.removeEventListener("scroll", this.onScroll, false); + window.removeEventListener("resize", this.onResize, true); + if (this.element) { + this.element.style.display = "none"; + } + }, + + unhide: function () { + this.updateElementSize(); + window.addEventListener("scroll", this.onScroll, false); + window.addEventListener("resize", this.onResize, true); + this.element.style.display = ""; + this.element.focus(); + }, + + onScroll: function () { + exports.HoverBox.hide(); + }, + + getElementFromPoint: function (x, y) { + this.element.style.pointerEvents = "none"; + let el; + try { + el = document.elementFromPoint(x, y); + } finally { + this.element.style.pointerEvents = ""; + } + return el; + }, + + remove: function () { + this.hide(); + util.removeNode(this.element); + this.element = null; + this.document = null; + } + }; + + iframePreSelection.onResize = watchFunction(onResize.bind(iframePreSelection)); + + let iframe = exports.iframe = { + currentIframe: iframePreSelection, + display: function (installHandlerOnDocument, standardOverlayCallbacks) { + return iframeSelection.display(installHandlerOnDocument) + .then(() => iframePreSelection.display(installHandlerOnDocument, standardOverlayCallbacks)); + }, + + hide: function () { + this.currentIframe.hide(); + }, + + unhide: function () { + this.currentIframe.unhide(); + }, + + getElementFromPoint: function (x, y) { + return this.currentIframe.getElementFromPoint(x, y); + }, + + remove: function () { + iframeSelection.remove(); + iframePreSelection.remove(); + }, + + document: function () { + return this.currentIframe.document; + }, + + useSelection: function () { + if (this.currentIframe === iframePreSelection) { + this.hide(); + } + this.currentIframe = iframeSelection; + this.unhide(); + }, + + usePreSelection: function () { + if (this.currentIframe === iframeSelection) { + this.hide(); + } + this.currentIframe = iframePreSelection; + this.unhide(); + } + }; + + let movements = ["topLeft", "top", "topRight", "left", "right", "bottomLeft", "bottom", "bottomRight"]; + + /** Creates the selection box */ + exports.Box = { + + display: function (pos, callbacks) { + 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.save) { + // We use onclick here because we don't want addEventListener + // to add multiple event handlers to the same button + this.save.removeAttribute("disabled"); + this.save.onclick = watchFunction(assertIsTrusted((e) => { + this.save.setAttribute("disabled", "true"); + callbacks.save(e); + })); + this.save.style.display = ""; + } else { + this.save.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"; + } + let bodyRect = getBodyRect(); + // Note, document.documentElement.scrollHeight is zero on some strange pages (such as the page created when you load an image): + let docHeight = Math.max(document.documentElement.scrollHeight || 0, document.body.scrollHeight); + let docWidth = Math.max(document.documentElement.scrollWidth, document.body.scrollWidth); + + let winBottom = window.innerHeight; + let 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"); + } + this.el.style.top = (pos.top - bodyRect.top) + "px"; + this.el.style.left = (pos.left - bodyRect.left) + "px"; + this.el.style.height = (pos.bottom - pos.top - bodyRect.top) + "px"; + this.el.style.width = (pos.right - pos.left - bodyRect.left) + "px"; + this.bgTop.style.top = "0px"; + this.bgTop.style.height = (pos.top - bodyRect.top) + "px"; + this.bgTop.style.left = "0px"; + this.bgTop.style.width = docWidth + "px"; + this.bgBottom.style.top = (pos.bottom - bodyRect.top) + "px"; + this.bgBottom.style.height = docHeight - (pos.bottom - bodyRect.top) + "px"; + this.bgBottom.style.left = "0px"; + this.bgBottom.style.width = docWidth + "px"; + this.bgLeft.style.top = (pos.top - bodyRect.top) + "px"; + this.bgLeft.style.height = pos.bottom - pos.top + "px"; + this.bgLeft.style.left = "0px"; + this.bgLeft.style.width = (pos.left - bodyRect.left) + "px"; + this.bgRight.style.top = (pos.top - bodyRect.top) + "px"; + this.bgRight.style.height = pos.bottom - pos.top + "px"; + this.bgRight.style.left = (pos.right - bodyRect.left) + "px"; + this.bgRight.style.width = docWidth - (pos.right - bodyRect.left) + "px"; + }, + + remove: function () { + for (let name of ["el", "bgTop", "bgLeft", "bgRight", "bgBottom"]) { + if (name in this) { + util.removeNode(this[name]); + this[name] = null; + } + } + }, + + _createEl: function () { + let boxEl = this.el; + if (boxEl) { + return; + } + boxEl = makeEl("div", "highlight"); + let buttons = makeEl("div", "highlight-buttons"); + let cancel = makeEl("button", "highlight-button-cancel"); + cancel.title = browser.i18n.getMessage("cancelScreenshot"); + buttons.appendChild(cancel); + let download = makeEl("button", "highlight-button-download"); + download.title = browser.i18n.getMessage("downloadScreenshot"); + buttons.appendChild(download); + let save = makeEl("button", "highlight-button-save"); + save.textContent = browser.i18n.getMessage("saveScreenshotSelectedArea"); + save.title = browser.i18n.getMessage("saveScreenshotSelectedArea"); + buttons.appendChild(save); + this.cancel = cancel; + this.download = download; + this.save = save; + boxEl.appendChild(buttons); + for (let name of movements) { + let elTarget = makeEl("div", "mover-target direction-" + name); + let 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: function (target) { + while (target) { + if (target.nodeType == document.ELEMENT_NODE) { + if (target.classList.contains("mover-target")) { + for (let 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: function (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: function (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: function (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: function () { + if (this.el) { + this.el.style.display = "none"; + } + }, + + remove: function () { + util.removeNode(this.el); + this.el = null; + } + }; + + exports.PixelDimensions = { + el: null, + xEl: null, + yEl: null, + display: function (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 = x; + this.yEl.textContent = y; + this.el.style.top = (yPos + 12) + "px"; + this.el.style.left = (xPos + 12) + "px"; + }, + remove: function () { + util.removeNode(this.el); + this.el = this.xEl = this.yEl = null; + } + }; + + /** Removes every UI this module creates */ + exports.remove = function () { + for (let 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; diff --git a/browser/extensions/screenshots/webextension/selector/uicontrol.js b/browser/extensions/screenshots/webextension/selector/uicontrol.js new file mode 100644 index 000000000000..3d97b1360761 --- /dev/null +++ b/browser/extensions/screenshots/webextension/selector/uicontrol.js @@ -0,0 +1,907 @@ +/* globals console, catcher, util, ui, slides */ +/* globals window, document, location, shooter, callBackground, selectorLoader, assertIsTrusted */ + +"use strict"; + +this.uicontrol = (function () { + let exports = {}; + + /********************************************************** + * selection + */ + + /* States: + + "crosshairs": + Nothing has happened, and the crosshairs will follow the movement of the mouse + "draggingReady": + The user has pressed the mouse button, but hasn't moved enough to create a selection + "dragging": + The user has pressed down a mouse button, and is dragging out an area far enough to show a selection + "selected": + The user has selected an area + "resizing": + The user is resizing the selection + "cancelled": + Everything has been cancelled + + A mousedown goes from crosshairs to dragging. + A mouseup goes from dragging to selected + A click outside of the selection goes from selected to crosshairs + A click on one of the draggers goes from selected to resizing + + State variables: + + state (string, one of the above) + mousedownPos (object with x/y during draggingReady, shows where the selection started) + selectedPos (object with x/y/h/w during selected or dragging, gives the entire selection) + resizeDirection (string: top, topLeft, etc, during resizing) + resizeStartPos (x/y position where resizing started) + mouseupNoAutoselect (true if a mouseup in draggingReady should not trigger autoselect) + + */ + + const { watchFunction, watchPromise } = catcher; + + const MAX_PAGE_HEIGHT = 5000; + const MAX_PAGE_WIDTH = 5000; + // An autoselection smaller than these will be ignored entirely: + const MIN_DETECT_ABSOLUTE_HEIGHT = 10; + const MIN_DETECT_ABSOLUTE_WIDTH = 30; + // An autoselection smaller than these will not be preferred: + const MIN_DETECT_HEIGHT = 30; + const MIN_DETECT_WIDTH = 100; + // An autoselection bigger than either of these will be ignored: + const MAX_DETECT_HEIGHT = Math.max(window.innerHeight + 100, 700); + const MAX_DETECT_WIDTH = Math.max(window.innerWidth + 100, 1000); + // This is how close (in pixels) you can get to the edge of the window and then + // it will scroll: + const SCROLL_BY_EDGE = 20; + + const { sendEvent } = shooter; + + function round10(n) { + return Math.floor(n / 10) * 10; + } + + function eventOptionsForBox(box) { + return { + cd1: round10(Math.abs(box.bottom - box.top)), + cd2: round10(Math.abs(box.right - box.left)) + }; + } + + function eventOptionsForResize(boxStart, boxEnd) { + return { + cd1: round10( + (boxEnd.bottom - boxEnd.top) + - (boxStart.bottom - boxStart.top)), + cd2: round10( + (boxEnd.right - boxEnd.left) + - (boxStart.right - boxStart.left)) + }; + } + + function eventOptionsForMove(posStart, posEnd) { + return { + cd1: round10(posEnd.y - posStart.y), + cd2: round10(posEnd.x - posStart.x) + }; + } + + /*********************************************** + * State and stateHandlers infrastructure + */ + + // This enumerates all the anchors on the selection, and what part of the + // selection they move: + const movements = { + topLeft: ["x1", "y1"], + top: [null, "y1"], + topRight: ["x2", "y1"], + left: ["x1", null], + right: ["x2", null], + bottomLeft: ["x1", "y2"], + bottom: [null, "y2"], + bottomRight: ["x2", "y2"], + move: ["*", "*"] + }; + + const doNotAutoselectTags = { + H1: true, + H2: true, + H3: true, + H4: true, + H5: true, + H6: true + }; + + let standardDisplayCallbacks = { + cancel: () => { + sendEvent("cancel-shot", "overlay-cancel-button"); + exports.deactivate(); + }, save: () => { + sendEvent("save-shot", "overlay-save-button"); + shooter.takeShot("selection", selectedPos); + }, download: () => { + sendEvent("download-shot", "overlay-download-button"); + shooter.downloadShot(selectedPos); + } + }; + + let standardOverlayCallbacks = { + onOpenMyShots: () => { + sendEvent("goto-myshots", "selection-button"); + callBackground("openMyShots") + .then(() => exports.deactivate()) + .catch(() => { + // Handled in communication.js + }); + }, + onClickVisible: () => { + sendEvent("capture-visible", "selection-button"); + selectedPos = new Selection( + window.scrollX, window.scrollY, + window.scrollX + window.innerWidth, window.scrollY + window.innerHeight); + shooter.takeShot("visible", selectedPos); + }, + onClickFullPage: () => { + sendEvent("capture-full-page", "selection-button"); + let width = Math.max( + document.body.clientWidth, + document.documentElement.clientWidth, + document.body.scrollWidth, + document.documentElement.scrollWidth); + width = Math.min(width, MAX_PAGE_WIDTH); + let height = Math.max( + document.body.clientHeight, + document.documentElement.clientHeight, + document.body.scrollHeight, + document.documentElement.scrollHeight); + height = Math.min(height, MAX_PAGE_HEIGHT); + selectedPos = new Selection( + 0, 0, + width, height); + shooter.takeShot("fullPage", selectedPos); + } + } + + /** Holds all the objects that handle events for each state: */ + let stateHandlers = {}; + + function getState() { + return getState.state; + } + getState.state = "cancel"; + + function setState(s) { + if (! stateHandlers[s]) { + throw new Error("Unknown state: " + s); + } + let cur = getState.state; + let handler = stateHandlers[cur]; + if (handler.end) { + handler.end(); + } + getState.state = s; + if (stateHandlers[s].start) { + stateHandlers[s].start(); + } + } + + /** Various values that the states use: */ + let mousedownPos; + let selectedPos; + let resizeDirection; + let resizeStartPos; + let resizeStartSelected; + let resizeHasMoved; + let mouseupNoAutoselect = false; + let autoDetectRect; + + /** Represents a selection box: */ + class Selection { + constructor(x1, y1, x2, y2) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + + rect() { + return { + top: Math.floor(this.top), + left: Math.floor(this.left), + bottom: Math.floor(this.bottom), + right: Math.floor(this.right) + }; + } + + get top() { + return Math.min(this.y1, this.y2); + } + set top(val) { + if (this.y1 < this.y2) { + this.y1 = val; + } else { + this.y2 = val; + } + } + + get bottom() { + return Math.max(this.y1, this.y2); + } + set bottom(val) { + if (this.y1 > this.y2) { + this.y1 = val; + } else { + this.y2 = val; + } + } + + get left() { + return Math.min(this.x1, this.x2); + } + set left(val) { + if (this.x1 < this.x2) { + this.x1 = val; + } else { + this.x2 = val; + } + } + + get right() { + return Math.max(this.x1, this.x2); + } + set right(val) { + if (this.x1 > this.x2) { + this.x1 = val; + } else { + this.x2 = val; + } + } + + get width() { + return Math.abs(this.x1 - this.x2); + } + get height() { + return Math.abs(this.y1 - this.y2); + } + + /** Sort x1/x2 and y1/y2 so x1 this.x2) { + let tmp = this.x2; + this.x2 = this.x1; + this.x1 = tmp; + } + if (this.y1 > this.y2) { + let tmp = this.y2; + this.y2 = this.y1; + this.y1 = tmp; + } + } + + union(other) { + return new Selection( + Math.min(this.left, other.left), + Math.min(this.top, other.top), + Math.max(this.right, other.right), + Math.max(this.bottom, other.bottom) + ); + } + + clone() { + return new Selection(this.x1, this.y1, this.x2, this.y2); + } + + asJson() { + return { + left: this.left, + right: this.right, + top: this.top, + bottom: this.bottom + }; + } + } + + Selection.getBoundingClientRect = function (el) { + if (! el.getBoundingClientRect) { + // Typically the element or somesuch + return null; + } + let rect = el.getBoundingClientRect(); + if (! rect) { + return null; + } + return new Selection(rect.left, rect.top, rect.right, rect.bottom); + }; + + /** Represents a single x/y point, typically for a mouse click that doesn't have a drag: */ + class Pos { + constructor(x, y) { + this.x = x; + this.y = y; + } + + elementFromPoint() { + return ui.iframe.getElementFromPoint( + this.x - window.pageXOffset, + this.y - window.pageYOffset + ); + } + + distanceTo(x, y) { + return Math.sqrt(Math.pow(this.x - x, 2), Math.pow(this.y - y)); + } + } + + /*********************************************** + * all stateHandlers + */ + + stateHandlers.onboarding = { + start: function () { + if (typeof slides == "undefined") { + throw new Error("Attempted to set state to onboarding without loading slides"); + } + catcher.watchPromise(slides.display({ + onEnd: this.slidesOnEnd.bind(this) + })); + }, + + slidesOnEnd: function () { + callBackground("hasSeenOnboarding"); + setState("crosshairs"); + }, + + end: function () { + slides.remove(); + } + }; + + stateHandlers.crosshairs = { + + cachedEl: null, + + start: function () { + selectedPos = mousedownPos = null; + this.cachedEl = null; + watchPromise(ui.iframe.display(installHandlersOnDocument, standardOverlayCallbacks).then(() => { + ui.iframe.usePreSelection(); + ui.Box.remove(); + const handler = watchFunction(assertIsTrusted(keyupHandler)); + document.addEventListener("keyup", handler, false); + registeredDocumentHandlers.push({name: "keyup", doc: document, handler}); + })); + }, + + mousemove: function (event) { + ui.PixelDimensions.display(event.pageX, event.pageY, event.pageX, event.pageY); + if (event.target.classList && + (! event.target.classList.contains("preview-overlay"))) { + // User is hovering over a toolbar button or control + autoDetectRect = null; + ui.HoverBox.hide(); + return; + } + let el; + if (event.target.classList.contains("preview-overlay")) { + // The hover is on the overlay, so we need to figure out the real element + el = ui.iframe.getElementFromPoint( + event.pageX + window.scrollX - window.pageXOffset, + event.pageY + window.scrollY - window.pageYOffset + ); + } else { + // The hover is on the element we care about, so we use that + el = event.target; + } + if (this.cachedEl && this.cachedEl === el) { + // Still hovering over the same element + return; + } + this.cachedEl = el; + this.setAutodetectBasedOnElement(el); + }, + + setAutodetectBasedOnElement: function (el) { + let lastRect; + let lastNode; + let rect; + let attemptExtend = false; + let node = el; + while (node) { + rect = Selection.getBoundingClientRect(node); + if (! rect) { + rect = lastRect; + break; + } + if (rect.width > MAX_DETECT_WIDTH || rect.height > MAX_DETECT_HEIGHT) { + // Then the last rectangle is better + rect = lastRect; + attemptExtend = true; + break; + } + if (rect.width >= MIN_DETECT_WIDTH && rect.height >= MIN_DETECT_HEIGHT) { + if (! doNotAutoselectTags[node.tagName]) { + break; + } + } + lastRect = rect; + lastNode = node; + node = node.parentNode; + } + if (rect && node) { + let evenBetter = this.evenBetterElement(node, rect); + if (evenBetter) { + node = lastNode = evenBetter; + rect = Selection.getBoundingClientRect(evenBetter); + attemptExtend = false; + } + } + if (rect && attemptExtend) { + let extendNode = lastNode.nextSibling; + while (extendNode) { + if (extendNode.nodeType === document.ELEMENT_NODE) { + break; + } + extendNode = extendNode.nextSibling; + if (! extendNode) { + let parent = lastNode.parentNode; + for (let i=0; i this.minMove) { + selectedPos = new Selection( + mousedownPos.x, + mousedownPos.y, + event.pageX + window.scrollX, + event.pageY + window.scrollY); + mousedownPos = null; + setState("dragging"); + } + }, + + mouseup: function (event) { + // If we don't get into "dragging" then we attempt an autoselect + if (mouseupNoAutoselect) { + sendEvent("cancel-selection", "selection-background-mousedown"); + setState("crosshairs"); + return false; + } + if (autoDetectRect) { + selectedPos = autoDetectRect; + selectedPos.x1 += window.scrollX; + selectedPos.y1 += window.scrollY; + selectedPos.x2 += window.scrollX; + selectedPos.y2 += window.scrollY; + autoDetectRect = null; + mousedownPos = null; + ui.iframe.useSelection(); + ui.Box.display(selectedPos, standardDisplayCallbacks); + sendEvent("make-selection", "selection-click", eventOptionsForBox(selectedPos)); + setState("selected"); + sendEvent("autoselect"); + } else { + sendEvent("no-selection", "no-element-found"); + setState("crosshairs"); + } + }, + + click: function (event) { + this.mouseup(event); + }, + + findGoodEl: function () { + let el = mousedownPos.elementFromPoint(); + if (! el) { + return null; + } + let isGoodEl = (el) => { + if (el.nodeType != document.ELEMENT_NODE) { + return false; + } + if (el.tagName == "IMG") { + let rect = el.getBoundingClientRect(); + return rect.width >= this.minAutoImageWidth && rect.height >= this.minAutoImageHeight; + } + let display = window.getComputedStyle(el).display; + if (['block', 'inline-block', 'table'].indexOf(display) != -1) { + return true; + // FIXME: not sure if this is useful: + //let rect = el.getBoundingClientRect(); + //return rect.width <= this.maxAutoElementWidth && rect.height <= this.maxAutoElementHeight; + } + return false; + }; + while (el) { + if (isGoodEl(el)) { + return el; + } + el = el.parentNode; + } + return null; + }, + + end: function () { + mouseupNoAutoselect = false; + } + + }; + + stateHandlers.dragging = { + + start: function () { + ui.iframe.useSelection(); + ui.Box.display(selectedPos); + }, + + mousemove: function (event) { + selectedPos.x2 = util.truncateX(event.pageX); + selectedPos.y2 = util.truncateY(event.pageY); + scrollIfByEdge(event.pageX, event.pageY); + ui.Box.display(selectedPos); + ui.PixelDimensions.display(event.pageX, event.pageY, selectedPos.width, selectedPos.height); + }, + + mouseup: function (event) { + selectedPos.x2 = util.truncateX(event.pageX); + selectedPos.y2 = util.truncateY(event.pageY); + ui.Box.display(selectedPos, standardDisplayCallbacks); + sendEvent( + "make-selection", "selection-drag", + eventOptionsForBox({ + top: selectedPos.y1, + bottom: selectedPos.y2, + left: selectedPos.x1, + right: selectedPos.x2 + })); + setState("selected"); + }, + + end: function () { + ui.PixelDimensions.remove(); + } + }; + + stateHandlers.selected = { + start: function () { + ui.iframe.useSelection(); + }, + + mousedown: function (event) { + let target = event.target; + if (target.tagName == "HTML") { + // This happens when you click on the scrollbar + return; + } + let direction = ui.Box.draggerDirection(target); + if (direction) { + sendEvent("start-resize-selection", "handle"); + stateHandlers.resizing.startResize(event, direction); + } else if (ui.Box.isSelection(target)) { + sendEvent("start-move-selection", "selection"); + stateHandlers.resizing.startResize(event, "move"); + } else if (! ui.Box.isControl(target)) { + mousedownPos = new Pos(event.pageX, event.pageY); + setState("crosshairs"); + } + event.preventDefault(); + return false; + } + }; + + stateHandlers.resizing = { + start: function () { + ui.iframe.useSelection(); + selectedPos.sortCoords(); + }, + + startResize: function (event, direction) { + selectedPos.sortCoords(); + resizeDirection = direction; + resizeStartPos = new Pos(event.pageX, event.pageY); + resizeStartSelected = selectedPos.clone(); + resizeHasMoved = false; + setState("resizing"); + }, + + mousemove: function (event) { + this._resize(event); + return false; + }, + + mouseup: function (event) { + this._resize(event); + sendEvent("selection-resized"); + ui.Box.display(selectedPos, standardDisplayCallbacks); + if (resizeHasMoved) { + if (resizeDirection == "move") { + let startPos = new Pos(resizeStartSelected.left, resizeStartSelected.top); + let endPos = new Pos(selectedPos.left, selectedPos.top); + sendEvent( + "move-selection", "mouseup", + eventOptionsForMove(startPos, endPos)); + } else { + sendEvent( + "resize-selection", "mouseup", + eventOptionsForResize(resizeStartSelected, selectedPos)); + } + } else { + if (resizeDirection == "move") { + sendEvent("keep-resize-selection", "mouseup"); + } else { + sendEvent("keep-move-selection", "mouseup"); + } + } + setState("selected"); + }, + + _resize: function (event) { + let diffX = event.pageX - resizeStartPos.x; + let diffY = event.pageY - resizeStartPos.y; + let movement = movements[resizeDirection]; + if (movement[0]) { + let moveX = movement[0]; + moveX = moveX == "*" ? ["x1", "x2"] : [moveX]; + for (let moveDir of moveX) { + selectedPos[moveDir] = util.truncateX(resizeStartSelected[moveDir] + diffX); + } + } + if (movement[1]) { + let moveY = movement[1]; + moveY = moveY == "*" ? ["y1", "y2"] : [moveY]; + for (let moveDir of moveY) { + selectedPos[moveDir] = util.truncateY(resizeStartSelected[moveDir] + diffY); + } + } + if (diffX || diffY) { + resizeHasMoved = true; + } + scrollIfByEdge(event.pageX, event.pageY); + ui.Box.display(selectedPos); + }, + + end: function () { + resizeDirection = resizeStartPos = resizeStartSelected = null; + selectedPos.sortCoords(); + } + }; + + stateHandlers.cancel = { + start: function () { + ui.iframe.hide(); + ui.Box.remove(); + } + }; + + let documentWidth = Math.max( + document.body.clientWidth, + document.documentElement.clientWidth, + document.body.scrollWidth, + document.documentElement.scrollWidth); + let documentHeight = Math.max( + document.body.clientHeight, + document.documentElement.clientHeight, + document.body.scrollHeight, + document.documentElement.scrollHeight); + + function scrollIfByEdge(pageX, pageY) { + let top = window.scrollY; + let bottom = top + window.innerHeight; + let left = window.scrollX; + let right = left + window.innerWidth; + if (pageY + SCROLL_BY_EDGE >= bottom && bottom < documentHeight) { + window.scrollBy(0, SCROLL_BY_EDGE); + } else if (pageY - SCROLL_BY_EDGE <= top) { + window.scrollBy(0, -SCROLL_BY_EDGE); + } + if (pageX + SCROLL_BY_EDGE >= right && right < documentWidth) { + window.scrollBy(SCROLL_BY_EDGE, 0); + } else if (pageX - SCROLL_BY_EDGE <= left) { + window.scrollBy(-SCROLL_BY_EDGE, 0); + } + } + + /*********************************************** + * Selection communication + */ + + // If the slides module is loaded then we're supposed to onboard + let shouldOnboard = typeof slides !== "undefined"; + + exports.activate = function () { + if (isFrameset()) { + callBackground("abortFrameset"); + selectorLoader.unloadModules(); + return; + } + addHandlers(); + // FIXME: self.options is gone + if (self.options && self.options.styleMyShotsButton) { + ui.iframe.addClassName = `styleMyShotsButton-${self.options.styleMyShotsButton.value}`; + } + if (shouldOnboard) { + setState("onboarding"); + } else { + setState("crosshairs"); + } + } + + function isFrameset() { + return document.body.tagName == "FRAMESET"; + } + + exports.deactivate = function () { + try { + setState("cancel"); + callBackground('closeSelector'); + selectorLoader.unloadModules(); + } catch (e) { + log.error('Error in deactivate', e) + // Sometimes this fires so late that the document isn't available + // We don't care about the exception, so we swallow it here + } + }; + + exports.unload = function () { + // Note that ui.unload() will be called on its own + removeHandlers(); + }; + + /*********************************************** + * Event handlers + */ + + let primedDocumentHandlers = new Map(); + let registeredDocumentHandlers = [] + + function addHandlers() { + ["mouseup", "mousedown", "mousemove", "click"].forEach((eventName) => { + let fn = watchFunction((function (eventName, event) { + if (typeof event.button == "number" && event.button !== 0) { + // Not a left click + return; + } + if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) { + // Modified click of key + return; + } + let state = getState(); + let handler = stateHandlers[state]; + if (handler[eventName]) { + return handler[eventName](event); + } + }).bind(null, eventName)); + primedDocumentHandlers.set(eventName, fn); + }); + primedDocumentHandlers.set("keyup", keyupHandler); + window.addEventListener('beforeunload', beforeunloadHandler); + } + + function installHandlersOnDocument(docObj) { + for (let [eventName, handler] of primedDocumentHandlers) { + let watchHandler = watchFunction(handler); + docObj.addEventListener(eventName, watchHandler, eventName !== "keyup"); + registeredDocumentHandlers.push({name: eventName, doc: docObj, watchHandler}); + } + let mousedownHandler = primedDocumentHandlers.get("mousedown"); + document.addEventListener("mousedown", mousedownHandler, true); + registeredDocumentHandlers.push({name: "mousedown", doc: document, watchHandler: mousedownHandler, useCapture: true}); + } + + function beforeunloadHandler() { + sendEvent("cancel-shot", "tab-load"); + exports.deactivate(); + } + + function keyupHandler(event) { + if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) { + // Modified + return; + } + if ((event.key || event.code) === "Escape") { + sendEvent("cancel-shot", "keyboard-escape"); + exports.deactivate(); + } + if ((event.key || event.code) === "Enter") { + if (getState.state === "selected") { + sendEvent("save-shot", "keyboard-enter"); + shooter.takeShot("selection", selectedPos); + } + } + } + + function removeHandlers() { + window.removeEventListener("beforeunload", beforeunloadHandler); + for (let {name, doc, handler, useCapture} of registeredDocumentHandlers) { + doc.removeEventListener(name, handler, !!useCapture); + } + registeredDocumentHandlers = []; + } + + exports.activate(); + + return exports; +})(); + +null; diff --git a/browser/extensions/screenshots/webextension/selector/util.js b/browser/extensions/screenshots/webextension/selector/util.js new file mode 100644 index 000000000000..19dc576a7238 --- /dev/null +++ b/browser/extensions/screenshots/webextension/selector/util.js @@ -0,0 +1,106 @@ +"use strict"; + +this.util = (function () { // eslint-disable-line no-unused-vars + let exports = {}; + + /** Removes a node from its document, if it's a node and the node is attached to a parent */ + exports.removeNode = function (el) { + if (el && el.parentNode) { + el.parentNode.removeChild(el); + } + }; + + /** Truncates the X coordinate to the document size */ + exports.truncateX = function (x) { + let max = Math.max(document.documentElement.clientWidth, document.body.clientWidth, document.documentElement.scrollWidth, document.body.scrollWidth); + if (x < 0) { + return 0; + } else if (x > max) { + return max; + } else { + return x; + } + }; + + /** Truncates the Y coordinate to the document size */ + exports.truncateY = function (y) { + let max = Math.max(document.documentElement.clientHeight, document.body.clientHeight, document.documentElement.scrollHeight, document.body.scrollHeight); + if (y < 0) { + return 0; + } else if (y > max) { + return max; + } else { + return y; + } + }; + + // Pixels of wiggle the captured region gets in captureSelectedText: + var CAPTURE_WIGGLE = 10; + const ELEMENT_NODE = document.ELEMENT_NODE; + + exports.captureEnclosedText = function (box) { + var scrollX = window.scrollX; + var scrollY = window.scrollY; + var text = []; + function traverse(el) { + var elBox = el.getBoundingClientRect(); + elBox = { + top: elBox.top + scrollY, + bottom: elBox.bottom + scrollY, + left: elBox.left + scrollX, + right: elBox.right + scrollX + }; + if (elBox.bottom < box.top || + elBox.top > box.bottom || + elBox.right < box.left || + elBox.left > box.right) { + // Totally outside of the box + return; + } + if (elBox.bottom > box.bottom + CAPTURE_WIGGLE || + elBox.top < box.top - CAPTURE_WIGGLE || + elBox.right > box.right + CAPTURE_WIGGLE || + elBox.left < box.left - CAPTURE_WIGGLE) { + // Partially outside the box + for (var i=0; i { + callBackground("reportError", errorObj); + }); + + + function sendCustomEvent(name, detail) { + if (typeof detail == "object") { + // Note sending an object can lead to security problems, while a string + // is safe to transfer: + detail = JSON.stringify(detail); + } + document.dispatchEvent(new CustomEvent(name, {detail})); + } + + document.addEventListener("delete-everything", catcher.watchFunction((event) => { + // FIXME: reset some data in the add-on + }, false)); + + document.addEventListener("request-login", catcher.watchFunction((event) => { + let shotId = event.detail; + catcher.watchPromise(callBackground("getAuthInfo", shotId || null).then((info) => { + sendCustomEvent("login-successful", {deviceId: info.deviceId, isOwner: info.isOwner}); + })); + })); + + // Depending on the script loading order, the site might get the addon-present event, + // but probably won't - instead the site will ask for that event after it has loaded + document.addEventListener("request-addon-present", catcher.watchFunction(() => { + sendCustomEvent("addon-present"); + }), false); + + sendCustomEvent("addon-present"); + +})(); +null;