merge mozilla-central to mozilla-inbound. CLOSED TREE

--HG--
extra : amend_source : 0e657a5dd4f6c8893d3f5ab7b173e3c4178e9e61
This commit is contained in:
Sebastian Hengst 2018-02-10 00:29:53 +02:00
Родитель 2e7425adfb f5e840425b
Коммит 87f465d8f7
241 изменённых файлов: 43095 добавлений и 84418 удалений

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -4335,17 +4335,17 @@ var XULBrowserWindow = {
},
setOverLink(url, anchorElt) {
const textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
getService(Ci.nsITextToSubURI);
url = textToSubURI.unEscapeURIForUI("UTF-8", url);
if (url) {
url = Services.textToSubURI.unEscapeURIForUI("UTF-8", url);
// Encode bidirectional formatting characters.
// (RFC 3987 sections 3.2 and 4.1 paragraph 6)
url = url.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
encodeURIComponent);
// Encode bidirectional formatting characters.
// (RFC 3987 sections 3.2 and 4.1 paragraph 6)
url = url.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
encodeURIComponent);
if (gURLBar && gURLBar._mayTrimURLs /* corresponds to browser.urlbar.trimURLs */)
url = trimURL(url);
if (gURLBar && gURLBar._mayTrimURLs /* corresponds to browser.urlbar.trimURLs */)
url = trimURL(url);
}
this.overLink = url;
LinkTargetDisplay.update();

Просмотреть файл

@ -1275,10 +1275,8 @@ nsContextMenu.prototype = {
// Let's try to unescape it using a character set
// in case the address is not ASCII.
try {
const textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
getService(Ci.nsITextToSubURI);
addresses = textToSubURI.unEscapeURIForUI(gContextMenuContentData.charSet,
addresses);
addresses = Services.textToSubURI.unEscapeURIForUI(gContextMenuContentData.charSet,
addresses);
} catch (ex) {
// Do nothing.
}

Просмотреть файл

@ -1646,10 +1646,6 @@
// Let's try to unescape it using a character set
// in case the URI is not ASCII.
try {
var characterSet = browser.characterSet;
const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
.getService(Components.interfaces.nsITextToSubURI);
title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
// If it's a long data: URI that uses base64 encoding, truncate to
// a reasonable length rather than trying to display the entire thing.
// We can't shorten arbitrary URIs like this, as bidi etc might mean
@ -1658,6 +1654,9 @@
// (See bug 1408854.)
if (title.length > 500 && title.match(/^data:[^,]+;base64,/)) {
title = title.substring(0, 500) + "\u2026";
} else {
var characterSet = browser.characterSet;
title = Services.textToSubURI.unEscapeNonAsciiURI(characterSet, title);
}
} catch (ex) { /* Do nothing. */ }
} else {

Просмотреть файл

@ -379,6 +379,11 @@ class ParentDevToolsInspectorSidebar {
// Set by setObject if the sidebar has not been created yet.
this._initializeSidebar = null;
// Set by _updateLastObjectValueGrip to keep track of the last
// object value grip (to release the previous selected actor
// on the remote debugging server when the actor changes).
this._lastObjectValueGrip = null;
this.toolbox.registerInspectorExtensionSidebar(this.id, {
title: sidebarOptions.title,
});
@ -389,12 +394,15 @@ class ParentDevToolsInspectorSidebar {
throw new Error("Unable to close a destroyed DevToolsSelectionObserver");
}
// Release the last selected actor on the remote debugging server.
this._updateLastObjectValueGrip(null);
this.toolbox.off(`extension-sidebar-created-${this.id}`, this.onSidebarCreated);
this.toolbox.off(`inspector-sidebar-select`, this.onSidebarSelect);
this.toolbox.unregisterInspectorExtensionSidebar(this.id);
this.extensionSidebar = null;
this._initializeSidebar = null;
this._lazySidebarInit = null;
this.destroyed = true;
}
@ -402,9 +410,11 @@ class ParentDevToolsInspectorSidebar {
onSidebarCreated(evt, sidebar) {
this.extensionSidebar = sidebar;
if (typeof this._initializeSidebar === "function") {
this._initializeSidebar();
this._initializeSidebar = null;
const {_lazySidebarInit} = this;
this._lazySidebarInit = null;
if (typeof _lazySidebarInit === "function") {
_lazySidebarInit();
}
}
@ -428,6 +438,8 @@ class ParentDevToolsInspectorSidebar {
}
setObject(object, rootTitle) {
this._updateLastObjectValueGrip(null);
// Nest the object inside an object, as the value of the `rootTitle` property.
if (rootTitle) {
object = {[rootTitle]: object};
@ -437,7 +449,38 @@ class ParentDevToolsInspectorSidebar {
this.extensionSidebar.setObject(object);
} else {
// Defer the sidebar.setObject call.
this._initializeSidebar = () => this.extensionSidebar.setObject(object);
this._setLazySidebarInit(() => this.extensionSidebar.setObject(object));
}
}
_setLazySidebarInit(cb) {
this._lazySidebarInit = cb;
}
setObjectValueGrip(objectValueGrip, rootTitle) {
this._updateLastObjectValueGrip(objectValueGrip);
if (this.extensionSidebar) {
this.extensionSidebar.setObjectValueGrip(objectValueGrip, rootTitle);
} else {
// Defer the sidebar.setObjectValueGrip call.
this._setLazySidebarInit(() => {
this.extensionSidebar.setObjectValueGrip(objectValueGrip, rootTitle);
});
}
}
_updateLastObjectValueGrip(newObjectValueGrip = null) {
const {_lastObjectValueGrip} = this;
this._lastObjectValueGrip = newObjectValueGrip;
const oldActor = _lastObjectValueGrip && _lastObjectValueGrip.actor;
const newActor = newObjectValueGrip && newObjectValueGrip.actor;
// Release the previously active actor on the remote debugging server.
if (oldActor && oldActor !== newActor) {
this.toolbox.target.client.release(oldActor);
}
}
}
@ -513,18 +556,20 @@ this.devtools_panels = class extends ExtensionAPI {
}
const front = await waitForInspectedWindowFront;
const evalOptions = Object.assign({}, getToolboxEvalOptions(context));
const evalOptions = Object.assign({
evalResultAsGrip: true,
}, getToolboxEvalOptions(context));
const evalResult = await front.eval(callerInfo, evalExpression, evalOptions);
let jsonObject;
if (evalResult.exceptionInfo) {
jsonObject = evalResult.exceptionInfo;
} else {
jsonObject = evalResult.value;
return sidebar.setObject(jsonObject, rootTitle);
}
return sidebar.setObject(jsonObject, rootTitle);
return sidebar.setObjectValueGrip(evalResult.valueGrip, rootTitle);
},
},
},

Просмотреть файл

@ -84,6 +84,8 @@ skip-if = (os == 'win' && !debug) # bug 1352668
[browser_ext_devtools_panel.js]
[browser_ext_devtools_panels_elements.js]
[browser_ext_devtools_panels_elements_sidebar.js]
support-files =
../../../../../devtools/client/inspector/extensions/test/head_devtools_inspector_sidebar.js
[browser_ext_find.js]
skip-if = (os == 'win' && ccov) # Bug 1423667
[browser_ext_geckoProfiler_symbolicate.js]

Просмотреть файл

@ -9,11 +9,55 @@ ChromeUtils.defineModuleGetter(this, "devtools",
ChromeUtils.defineModuleGetter(this, "ContentTaskUtils",
"resource://testing-common/ContentTaskUtils.jsm");
/* globals getExtensionSidebarActors, expectNoSuchActorIDs, testSetExpressionSidebarPanel */
// Import the shared test helpers from the related devtools tests.
Services.scriptloader.loadSubScript(
new URL("head_devtools_inspector_sidebar.js", gTestPath).href,
this);
function isActiveSidebarTabTitle(inspector, expectedTabTitle, message) {
const actualTabTitle = inspector.panelDoc.querySelector(".tabs-menu-item.is-active").innerText;
is(actualTabTitle, expectedTabTitle, message);
}
function testSetObjectSidebarPanel(panel, expectedCellType, expectedTitle) {
is(panel.querySelectorAll("table.treeTable").length, 1,
"The sidebar panel contains a rendered TreeView component");
is(panel.querySelectorAll(`table.treeTable .${expectedCellType}Cell`).length, 1,
`The TreeView component contains the expected a cell of type ${expectedCellType}`);
if (expectedTitle) {
const panelTree = panel.querySelector("table.treeTable");
ok(
panelTree.innerText.includes(expectedTitle),
"The optional root object title has been included in the object tree"
);
}
}
async function testSidebarPanelSelect(extension, inspector, tabId, expected) {
const {
sidebarShown,
sidebarHidden,
activeSidebarTabTitle,
} = expected;
inspector.sidebar.show(tabId);
const shown = await extension.awaitMessage("devtools_sidebar_shown");
is(shown, sidebarShown, "Got the shown event on the second extension sidebar");
if (sidebarHidden) {
const hidden = await extension.awaitMessage("devtools_sidebar_hidden");
is(hidden, sidebarHidden, "Got the hidden event on the first extension sidebar");
}
isActiveSidebarTabTitle(inspector, activeSidebarTabTitle,
"Got the expected title on the active sidebar tab");
}
add_task(async function test_devtools_panels_elements_sidebar() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
@ -34,14 +78,21 @@ add_task(async function test_devtools_panels_elements_sidebar() {
sidebar2.onHidden.addListener(() => onShownListener("hidden", "sidebar2"));
sidebar3.onHidden.addListener(() => onShownListener("hidden", "sidebar3"));
sidebar1.setObject({propertyName: "propertyValue"}, "Optional Root Object Title");
sidebar2.setObject({anotherPropertyName: 123});
// Refresh the sidebar content on every inspector selection.
browser.devtools.panels.elements.onSelectionChanged.addListener(() => {
sidebar3.setExpression("$0 && $0.tagName", "Selected Element tagName");
const expression = `
var obj = Object.create(null);
obj.prop1 = 123;
obj[Symbol('sym1')] = 456;
obj.cyclic = obj;
obj;
`;
sidebar1.setExpression(expression, "sidebar.setExpression rootTitle");
});
sidebar2.setObject({anotherPropertyName: 123});
sidebar3.setObject({propertyName: "propertyValue"}, "Optional Root Object Title");
browser.test.sendMessage("devtools_page_loaded");
}
@ -80,6 +131,8 @@ add_task(async function test_devtools_panels_elements_sidebar() {
const inspector = await toolbox.getPanel("inspector");
info("Test extension inspector sidebar 1 (sidebar.setExpression)");
inspector.sidebar.show(sidebarIds[0]);
const shownSidebarInstance = await extension.awaitMessage("devtools_sidebar_shown");
@ -93,69 +146,48 @@ add_task(async function test_devtools_panels_elements_sidebar() {
ok(sidebarPanel1, "Got a rendered sidebar panel for the first registered extension sidebar");
is(sidebarPanel1.querySelectorAll("table.treeTable").length, 1,
"The first sidebar panel contains a rendered TreeView component");
info("Waiting for the first panel to be rendered");
is(sidebarPanel1.querySelectorAll("table.treeTable .stringCell").length, 1,
"The TreeView component contains the expected number of string cells.");
// Verify that the panel contains an ObjectInspector, with the expected number of nodes
// and with the expected property names.
await testSetExpressionSidebarPanel(sidebarPanel1, {
nodesLength: 4,
propertiesNames: ["cyclic", "prop1", "Symbol(sym1)"],
rootTitle: "sidebar.setExpression rootTitle",
});
const sidebarPanel1Tree = sidebarPanel1.querySelector("table.treeTable");
ok(
sidebarPanel1Tree.innerText.includes("Optional Root Object Title"),
"The optional root object title has been included in the object tree"
);
// Retrieve the actors currently rendered into the extension sidebars.
const actors = getExtensionSidebarActors(inspector);
inspector.sidebar.show(sidebarIds[1]);
info("Test extension inspector sidebar 2 (sidebar.setObject without a root title)");
const shownSidebarInstance2 = await extension.awaitMessage("devtools_sidebar_shown");
const hiddenSidebarInstance1 = await extension.awaitMessage("devtools_sidebar_hidden");
is(shownSidebarInstance2, "sidebar2", "Got the shown event on the second extension sidebar");
is(hiddenSidebarInstance1, "sidebar1", "Got the hidden event on the first extension sidebar");
isActiveSidebarTabTitle(inspector, "Test Sidebar 2",
"Got the expected title on the active sidebar tab");
await testSidebarPanelSelect(extension, inspector, sidebarIds[1], {
sidebarShown: "sidebar2",
sidebarHidden: "sidebar1",
activeSidebarTabTitle: "Test Sidebar 2",
});
const sidebarPanel2 = inspector.sidebar.getTabPanel(sidebarIds[1]);
ok(sidebarPanel2, "Got a rendered sidebar panel for the second registered extension sidebar");
is(sidebarPanel2.querySelectorAll("table.treeTable").length, 1,
"The second sidebar panel contains a rendered TreeView component");
testSetObjectSidebarPanel(sidebarPanel2, "number");
is(sidebarPanel2.querySelectorAll("table.treeTable .numberCell").length, 1,
"The TreeView component contains the expected a cell of type number.");
info("Test extension inspector sidebar 3 (sidebar.setObject with a root title)");
inspector.sidebar.show(sidebarIds[2]);
const shownSidebarInstance3 = await extension.awaitMessage("devtools_sidebar_shown");
const hiddenSidebarInstance2 = await extension.awaitMessage("devtools_sidebar_hidden");
is(shownSidebarInstance3, "sidebar3", "Got the shown event on the third extension sidebar");
is(hiddenSidebarInstance2, "sidebar2", "Got the hidden event on the second extension sidebar");
isActiveSidebarTabTitle(inspector, "Test Sidebar 3",
"Got the expected title on the active sidebar tab");
await testSidebarPanelSelect(extension, inspector, sidebarIds[2], {
sidebarShown: "sidebar3",
sidebarHidden: "sidebar2",
activeSidebarTabTitle: "Test Sidebar 3",
});
const sidebarPanel3 = inspector.sidebar.getTabPanel(sidebarIds[2]);
ok(sidebarPanel3, "Got a rendered sidebar panel for the third registered extension sidebar");
info("Waiting for the third panel to be rendered");
await ContentTaskUtils.waitForCondition(() => {
return sidebarPanel3.querySelectorAll("table.treeTable").length > 0;
});
testSetObjectSidebarPanel(sidebarPanel3, "string", "Optional Root Object Title");
is(sidebarPanel3.querySelectorAll("table.treeTable").length, 1,
"The third sidebar panel contains a rendered TreeView component");
const treeViewStringValues = sidebarPanel3.querySelectorAll("table.treeTable .stringCell");
is(treeViewStringValues.length, 1,
"The TreeView component contains the expected content of type string.");
is(treeViewStringValues[0].innerText, "\"BODY\"",
"Got the expected content in the sidebar.setExpression rendered TreeView");
info("Unloading the extension and check that all the sidebar have been removed");
await extension.unload();
@ -171,6 +203,8 @@ add_task(async function test_devtools_panels_elements_sidebar() {
is(inspector.sidebar.getTabPanel(sidebarIds[2]), undefined,
"The third registered sidebar has been removed");
await expectNoSuchActorIDs(target.client, actors);
await gDevTools.closeToolbox(target);
await target.destroy();

Просмотреть файл

@ -4,9 +4,7 @@ ChromeUtils.defineModuleGetter(this, "FormHistory",
function expectedURL(aSearchTerms) {
const ENGINE_HTML_BASE = "http://mochi.test:8888/browser/browser/components/search/test/test.html";
var textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
getService(Ci.nsITextToSubURI);
var searchArg = textToSubURI.ConvertAndEscape("utf-8", aSearchTerms);
var searchArg = Services.textToSubURI.ConvertAndEscape("utf-8", aSearchTerms);
return ENGINE_HTML_BASE + "?test=" + searchArg;
}

41
browser/extensions/screenshots/bootstrap.js поставляемый
Просмотреть файл

@ -1,10 +1,9 @@
/* globals ADDON_DISABLE */
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 = "datareporting.healthreport.uploadEnabled";
const PREF_BRANCH = "extensions.screenshots.";
const USER_DISABLE_PREF = "extensions.screenshots.disabled";
const UPLOAD_DISABLED_PREF = "extensions.screenshots.upload-disabled";
const HISTORY_ENABLED_PREF = "places.history.enabled";
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
@ -209,33 +208,25 @@ function handleMessage(msg, sender, sendReply) {
return;
}
if (msg.funcName === "getTelemetryPref") {
if (msg.funcName === "isTelemetryEnabled") {
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;
} else if (msg.funcName === "getHistoryPref") {
} else if (msg.funcName === "isUploadDisabled") {
let isESR = AppConstants.MOZ_UPDATE_CHANNEL === 'esr';
let uploadDisabled = getBoolPref(UPLOAD_DISABLED_PREF);
sendReply({type: "success", value: uploadDisabled || isESR});
} else if (msg.funcName === "isHistoryEnabled") {
let historyEnabled = getBoolPref(HISTORY_ENABLED_PREF);
sendReply({type: "success", value: historyEnabled});
} else if (msg.funcName === "incrementDownloadCount") {
Services.telemetry.scalarAdd('screenshots.download', 1);
sendReply({type: "success", value: true});
} else if (msg.funcName === "incrementUploadCount") {
Services.telemetry.scalarAdd('screenshots.upload', 1);
sendReply({type: "success", value: true});
} else if (msg.funcName === "incrementCopyCount") {
Services.telemetry.scalarAdd('screenshots.copy', 1);
sendReply({type: "success", value: true});
} else if (msg.funcName === "incrementCount") {
let allowedScalars = ["download", "upload", "copy"];
let scalar = msg.args && msg.args[0] && msg.args[0].scalar;
if (!allowedScalars.includes(scalar)) {
sendReply({type: "error", name: `incrementCount passed an unrecognized scalar ${scalar}`});
} else {
Services.telemetry.scalarAdd(`screenshots.${scalar}`, 1);
sendReply({type: "success", value: true});
}
}
}

Просмотреть файл

@ -12,7 +12,7 @@
</Description>
</em:targetApplication>
<em:type>2</em:type>
<em:version>25.0.0</em:version>
<em:version>29.0.0</em:version>
<em:bootstrap>true</em:bootstrap>
<em:homepageURL>https://screenshots.firefox.com/</em:homepageURL>
<em:multiprocessCompatible>true</em:multiprocessCompatible>

Просмотреть файл

@ -57,6 +57,10 @@ FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales
'webextension/_locales/bn_BD/messages.json'
]
FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["br"] += [
'webextension/_locales/br/messages.json'
]
FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["bs"] += [
'webextension/_locales/bs/messages.json'
]
@ -237,6 +241,10 @@ FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales
'webextension/_locales/mk/messages.json'
]
FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["ml"] += [
'webextension/_locales/ml/messages.json'
]
FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["mr"] += [
'webextension/_locales/mr/messages.json'
]
@ -371,7 +379,8 @@ FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["build"]
'webextension/build/onboardingCss.js',
'webextension/build/onboardingHtml.js',
'webextension/build/raven.js',
'webextension/build/shot.js'
'webextension/build/shot.js',
'webextension/build/thumbnailGenerator.js'
]
FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["icons"] += [

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "”لا تتذكر التأريخ أبدًا“ مفعّل."
},
"downloadOnlyDetailsESR": {
"message": "تستخدم الإصدارة طويلة الدعم من فَيَرفُكس."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "عُطل الرفع."
},
"notificationLinkCopiedTitle": {
"message": "نُسخ الرابط"
},

Просмотреть файл

@ -29,8 +29,29 @@
"downloadScreenshot": {
"message": "Endir"
},
"downloadOnlyNotice": {
"message": "Hazırda Ancaq-Endirmə rejimindəsiniz."
},
"downloadOnlyDetails": {
"message": "Firefox Screenshots bu vəziyyətlərdə avtomatik olaraq Ancaq-Endirmə rejiminə keçir:"
},
"downloadOnlyDetailsPrivate": {
"message": "Məxfi Səyahət pəncərəsində."
},
"downloadOnlyDetailsThirdParty": {
"message": "Üçüncü tərəf çərəzlər söndürülüb."
},
"downloadOnlyDetailsNeverRemember": {
"message": "“Tarixçəni heç vaxt xatırlama” aktivdir."
},
"downloadOnlyDetailsESR": {
"message": "Firefox ESR işlədirsiniz."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Yükləmələr söndürülüb."
},
"notificationLinkCopiedTitle": {
"message": "Keçid köçürüldü"
"message": "Keçid Köçürüldü"
},
"notificationLinkCopiedDetails": {
"message": "Ekran görüntüsünün keçidi buferə köçürüldü. Yapışdırmaq üçün $META_KEY$-V basın.",
@ -40,6 +61,28 @@
}
}
},
"copyScreenshot": {
"message": "Köçür"
},
"notificationImageCopiedTitle": {
"message": "Görüntü Köçürüldü"
},
"notificationImageCopiedDetails": {
"message": "Görüntünüz mübadilə buferinə köçürüldü. Yapışdırmaq üçün $META_KEY$-V basın.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "Şəkil $PIXELS$px ölçülərinə kəsildi.",
"placeholders": {
"pixels": {
"content": "$1"
}
}
},
"requestErrorTitle": {
"message": "Xəta baş verdi."
},
@ -68,7 +111,7 @@
"message": "Seçiminiz çox balacadır"
},
"privateWindowErrorTitle": {
"message": "Ekran görüntüləri Məxfi Səyahət rejimində sönülüdür"
"message": "Screenshots özəlliyi Məxfi Səyahət rejimində sönülüdür"
},
"privateWindowErrorDetails": {
"message": "Narahatlıq üçün üzr istəyirik. Gələcək buraxılışlarda bu özəllik üzərində işləyirik."
@ -82,6 +125,12 @@
"tourBodyIntro": {
"message": "Firefoxu tərk etmədən ekran görüntüləri alın, saxlayın və paylaşın."
},
"tourHeaderPageAction": {
"message": "Saxlamağın yeni yolu"
},
"tourBodyPageAction": {
"message": "Ekran görüntüsü almaq istədiyinizdə ünvan sətrindəki səhifə əməliyyatları menyusunu açın."
},
"tourHeaderClickAndDrag": {
"message": "İstədiyiniz hər şeyin görüntüsünü alın"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Уключана “не будзе запамінаць гісторыю”."
},
"downloadOnlyDetailsESR": {
"message": "Вы выкарыстоўваеце Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Зацягванне было выключана."
},
"notificationLinkCopiedTitle": {
"message": "Спасылка скапіявана"
},
@ -55,6 +61,20 @@
}
}
},
"copyScreenshot": {
"message": "Капіраваць"
},
"notificationImageCopiedTitle": {
"message": "Скрыншот скапіраваны"
},
"notificationImageCopiedDetails": {
"message": "Скрыншот скапіраваны ў буфер абмену. Націсніце $META_KEY$-V, каб уставіць.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "Гэта выява была абрэзана да $PIXELS$пікселяў.",
"placeholders": {

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "настройката „Не помни история“ е включена"
},
"downloadOnlyDetailsESR": {
"message": "Използвате Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Качването на снимки е изключено."
},
"notificationLinkCopiedTitle": {
"message": "Препратката е копирана"
},
@ -55,6 +61,20 @@
}
}
},
"copyScreenshot": {
"message": "Копиране"
},
"notificationImageCopiedTitle": {
"message": "Снимката е копирана"
},
"notificationImageCopiedDetails": {
"message": "Снимката е копирана в системния буфер. За да я поставите натиснете $META_KEY$-V.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "Изображението е отрязано до $PIXELS$px.",
"placeholders": {

Просмотреть файл

@ -29,6 +29,27 @@
"downloadScreenshot": {
"message": "ডাউনলোড"
},
"downloadOnlyNotice": {
"message": "আপনি বর্তমানে Download-Only মোডে আছেন।"
},
"downloadOnlyDetails": {
"message": "Firefox Screeenshots স্বয়ংক্রিয়ভাবে Download-Only এ পরিবর্তণ হয় যেসব অবস্থায়:"
},
"downloadOnlyDetailsPrivate": {
"message": "ব্যক্তিগত ব্রাউজিং উইন্ডোতে।"
},
"downloadOnlyDetailsThirdParty": {
"message": "তৃতীয়-পক্ষীয় কুকি নিষ্ক্রিয় আছে।"
},
"downloadOnlyDetailsNeverRemember": {
"message": "“ইতিহাস কখনো মনে রাখবেন না” সক্রিয় হয়েছে।"
},
"downloadOnlyDetailsESR": {
"message": "অাপনি Firefox ESR ব্যবহার করছেন।"
},
"downloadOnlyDetailsNoUploadPref": {
"message": "আপলোড নিষ্ক্রিয় করা হয়েছে।"
},
"notificationLinkCopiedTitle": {
"message": "লিঙ্ক কপি করা হয়েছে"
},
@ -40,6 +61,20 @@
}
}
},
"copyScreenshot": {
"message": "অনুলিপি"
},
"notificationImageCopiedTitle": {
"message": "শট অনুলিপি করা হয়েছে"
},
"notificationImageCopiedDetails": {
"message": "আপনার শট ক্লিপবোর্ডে অনুলিপি করা হয়েছে। প্রতিলেপন করতে $META_KEY$-V চাপুন।",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "ইমেজটিকে $PIXELS$পিক্সেলে ক্রপ করা হয়েছে।",
"placeholders": {

Просмотреть файл

@ -0,0 +1,184 @@
{
"addonDescription": {
"message": "Tapit skeudennoù war ar Web hag enrollit anezho e-pad ur mare pe da viken."
},
"addonAuthorsList": {
"message": "Mozilla <screenshots-feedback@mozilla.com>"
},
"contextMenuLabel": {
"message": "Kemer un dapadenn-skramm"
},
"myShotsLink": {
"message": "Ma zapadennoù"
},
"screenshotInstructions": {
"message": "Riklit pe klikit war ar bajenn da ziuzañ ur vaezienn. Pouezit war ESC evit nullañ."
},
"saveScreenshotSelectedArea": {
"message": "Enrollañ"
},
"saveScreenshotVisibleArea": {
"message": "Enrollañ ar pezh a vez gwelet"
},
"saveScreenshotFullPage": {
"message": "Enrollañ ar bajenn glok"
},
"cancelScreenshot": {
"message": "Nullañ"
},
"downloadScreenshot": {
"message": "Pellgargañ"
},
"downloadOnlyNotice": {
"message": "E mod pellgargañ hepken emaoc'h bremañ."
},
"downloadOnlyDetails": {
"message": "Mont a raio Firefox Screenshots e mod pellgargañ hepken en degouezhioù da heul:"
},
"downloadOnlyDetailsPrivate": {
"message": "En ur prenestr merdeiñ prevez."
},
"downloadOnlyDetailsThirdParty": {
"message": "Diweredekaet eo toupinoù an tredeoù."
},
"downloadOnlyDetailsNeverRemember": {
"message": "Gweredekaet eo \"Na enrollañ ar roll istor\"."
},
"downloadOnlyDetailsESR": {
"message": "Arverañ a rit Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Diweredekaet eo bet an hizivadurioù."
},
"notificationLinkCopiedTitle": {
"message": "Ere eilet"
},
"notificationLinkCopiedDetails": {
"message": "Eilet eo bet ere ho tapadenn er golver. Pouezit war $META_KEY$-V evit pegañ.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"copyScreenshot": {
"message": "Eilañ"
},
"notificationImageCopiedTitle": {
"message": "Tapadenn eilet"
},
"notificationImageCopiedDetails": {
"message": "Eilet eo bet ho tapadenn er golver. Pouezit war $META_KEY$-V evit pegañ.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "Digresket eo bet ho skeudenn da $PIXELS$ piksel.",
"placeholders": {
"pixels": {
"content": "$1"
}
}
},
"requestErrorTitle": {
"message": "Degouezhet ez eus bet ur fazi."
},
"requestErrorDetails": {
"message": "Digarezit! N'haller ket enrollañ ho tapadenn. Klaskit en-dro diwezhatoc'h."
},
"connectionErrorTitle": {
"message": "N'haller ket adkennaskañ d'ho tapadennoù-skramm."
},
"connectionErrorDetails": {
"message": "Gwiriekait ho kennask kenrouedad. Ma 'z hoc'h gouest da gennaskañ ouzh ar genrouedad e c'hall bezañ abalamour d'ur gudenn gant ar gwazerezhioù Firefox Screenshots."
},
"loginErrorDetails": {
"message": "N'haller ket enrollañ ho tapadenn dre ma 'z eus bet ur gudenn gant ar gwazerezh Firefox Screenshots. Klaskit en-dro diwezhatoc'h."
},
"unshootablePageErrorTitle": {
"message": "N'haller ket tapout ar bajenn-mañ."
},
"unshootablePageErrorDetails": {
"message": "N'eo ket ur bajenn web reoliek neuze n'haller ket kemer un dapadenn-skramm ganti."
},
"selfScreenshotErrorTitle": {
"message": "N'hallit ket kemer un dapadenn-skramm eus ur bajenn Firefox Screenshots!"
},
"emptySelectionErrorTitle": {
"message": "Re vihan eo ho tiuzadenn"
},
"privateWindowErrorTitle": {
"message": "Diweredekaet eo an tapadennoù-skramm er merdeiñ prevez."
},
"privateWindowErrorDetails": {
"message": "Digarezit evit ar gudenn. Labourat a reomp war ar c'heweriuster-mañ evit ermaeziadennoù da zont."
},
"genericErrorTitle": {
"message": "Chaous! Ur gudenn a zo savet gant Firefox Screenshots."
},
"genericErrorDetails": {
"message": "N'omp ket sur eus ar pezh a zo c'hoarvezet. Gallout a rit adklask pe kemer ur dapadenn eus ur bajenn all."
},
"tourBodyIntro": {
"message": "Kemerit, enrollit ha skignit tapadennoù-skramm he kuitaat Firefox."
},
"tourHeaderPageAction": {
"message": "Un doare nevez da enrollañ"
},
"tourBodyPageAction": {
"message": "Kreskiñ lañser gweredoù ar bajenn er varrenn chomlec'h pa fell deoc'h kemer un dapadenn-skramm."
},
"tourHeaderClickAndDrag": {
"message": "Tapit ar pezh a fell deoc'h hepken"
},
"tourBodyClickAndDrag": {
"message": "Klikit ha riklit evit tapout ul lodenn eus ar bajenn hepken. Gallout a rit ivez tremen a-us evit usskediñ an diuzad."
},
"tourHeaderFullPage": {
"message": "Tapit prenestroù pe pajennoù a-bezh"
},
"tourBodyFullPage": {
"message": "Diuzit an afelloù a-us a-zehou evit skeudenniñ ar vaezienn welus er prenestr pe evit skeudenniñ ar bajenn a-bezh."
},
"tourHeaderDownloadUpload": {
"message": "Evel ma fell deoc'h"
},
"tourBodyDownloadUpload": {
"message": "Enrollit ho tapadennoù didroc'het evit ar web evit skignañ aesoc'h, pe pellgargit anezho war ho urzhiataer. Gallout a rit ivez klikañ war an afell Ma zapadennoù evit kavout an holl dapadennoù kemeret ganeoc'h."
},
"tourSkip": {
"message": "TREMEN"
},
"tourNext": {
"message": "Treyonenn da-heul"
},
"tourPrevious": {
"message": "Treyonenn gent"
},
"tourDone": {
"message": "Graet"
},
"termsAndPrivacyNotice2": {
"message": "En ur implij Firefox Screenshots ec'h asantit d'hor $TERMSANDPRIVACYNOTICETERMSLINK$ ha $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
"placeholders": {
"termsandprivacynoticetermslink": {
"content": "$1"
},
"termsandprivacynoticeprivacylink": {
"content": "$2"
}
}
},
"termsAndPrivacyNoticeTermsLink": {
"message": "Divizoù"
},
"termsAndPrivacyNoticyPrivacyLink": {
"message": "Evezhiadenn a-fed buhez prevez"
},
"libraryLabel": {
"message": "Tapadennoù-skramm"
}
}

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Kada je omogućeno “Nikad ne pamti historiju”."
},
"downloadOnlyDetailsESR": {
"message": "Koristite Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Prijenosi su onemogućeni."
},
"notificationLinkCopiedTitle": {
"message": "Link je kopiran"
},

Просмотреть файл

@ -29,6 +29,21 @@
"downloadScreenshot": {
"message": "Baixa"
},
"downloadOnlyNotice": {
"message": "Actualment esteu en mode de només baixada."
},
"downloadOnlyDetails": {
"message": "El Firefox Screenshots canvia automàticament al mode de només baixada en aquestes situacions:"
},
"downloadOnlyDetailsPrivate": {
"message": "En una finestra de navegació privada."
},
"downloadOnlyDetailsThirdParty": {
"message": "S'han desactivat les galetes de tercers."
},
"downloadOnlyDetailsNeverRemember": {
"message": "S'ha activat l'opció «No recordarà mai l'historial»."
},
"notificationLinkCopiedTitle": {
"message": "S'ha copiat l'enllaç"
},
@ -40,6 +55,28 @@
}
}
},
"copyScreenshot": {
"message": "Copia"
},
"notificationImageCopiedTitle": {
"message": "S'ha copiat la captura"
},
"notificationImageCopiedDetails": {
"message": "La captura s'ha copiat al porta-retalls. Premeu $META_KEY$-V per enganxar-la.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "Aquesta imatge s'ha retallat a $PIXELS$ px.",
"placeholders": {
"pixels": {
"content": "$1"
}
}
},
"requestErrorTitle": {
"message": "No funciona."
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“Majub'ey tinatäx natab'äl” tzijon."
},
"downloadOnlyDetailsESR": {
"message": "Tajin nawokisaj ri Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "E chupül ri kisamajib'exik taq yakb'äl."
},
"notificationLinkCopiedTitle": {
"message": "Ximonel Wachib'en"
},
@ -55,6 +61,20 @@
}
}
},
"copyScreenshot": {
"message": "Tiwachib'ëx"
},
"notificationImageCopiedTitle": {
"message": "Xwachib'ëx chapoj"
},
"notificationImageCopiedDetails": {
"message": "Xwachib'ëx pa molwuj ri ruchapoj awachib'al. Tapitz'a' $META_KEY$-V richin natz'äm.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "Re wachib'äl re' xqupïx pa $PIXELS$px.",
"placeholders": {

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Máte zcela zakázané ukládání historie."
},
"downloadOnlyDetailsESR": {
"message": "Používáte Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Nahrávání bylo zakázáno."
},
"notificationLinkCopiedTitle": {
"message": "Odkaz zkopírován"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Mae “Peidio cofio hanes” wedi ei alluogi."
},
"downloadOnlyDetailsESR": {
"message": "Rydych yn defnyddio Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Mae llwytho i fyny wedi ei atal."
},
"notificationLinkCopiedTitle": {
"message": "Dolen wedi ei Chadw"
},
@ -55,6 +61,20 @@
}
}
},
"copyScreenshot": {
"message": "Copïo"
},
"notificationImageCopiedTitle": {
"message": "Copïwyd y Llun"
},
"notificationImageCopiedDetails": {
"message": "Mae eich llun wedi ei gopïo i'r clipfwrdd. Pwyswch $META_KEY$-V i'w ludo.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "Mae'r ddelwedd wedi ei thocio i $PIXELS$px.",
"placeholders": {

Просмотреть файл

@ -29,9 +29,27 @@
"downloadScreenshot": {
"message": "Hent"
},
"downloadOnlyNotice": {
"message": "Du kan i øjeblikket kun hente skærmbilleder."
},
"downloadOnlyDetails": {
"message": "I følgende situationer lader Firefox Screenshots dig kun hente skærmbilleder:"
},
"downloadOnlyDetailsPrivate": {
"message": "I et privat browsing-vindue."
},
"downloadOnlyDetailsThirdParty": {
"message": "Når tredjeparts cookies er deaktiveret."
},
"downloadOnlyDetailsNeverRemember": {
"message": "Når Firefox-indstillingen “Aldrig gemme historik” er valgt."
},
"downloadOnlyDetailsESR": {
"message": "Du bruger Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Uploads er blevet deaktiveret."
},
"notificationLinkCopiedTitle": {
"message": "Link kopieret"
},
@ -43,6 +61,20 @@
}
}
},
"copyScreenshot": {
"message": "Kopier"
},
"notificationImageCopiedTitle": {
"message": "Skærmbillede kopieret"
},
"notificationImageCopiedDetails": {
"message": "Dit skærmbillede er blevet kopieret til udklipsholderen. Tryk $META_KEY$-V for at indsætte.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "Billedet er blevet beskåret til $PIXELS$ px.",
"placeholders": {

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "„Se historiju nigda njespomnjeś“ jo zmóžnjone."
},
"downloadOnlyDetailsESR": {
"message": "Wužywaśo Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Nagraśa su znjemóžnjone."
},
"notificationLinkCopiedTitle": {
"message": "Wótkaz kopěrowany"
},

Просмотреть файл

@ -40,6 +40,12 @@
}
}
},
"copyScreenshot": {
"message": "Αντιγραφή"
},
"notificationImageCopiedTitle": {
"message": "Το στιγμιότυπο αντιγράφηκε"
},
"imageCroppedWarning": {
"message": "Αυτή η εικόνα έχει περικοπεί σε $PIXELS$px.",
"placeholders": {

Просмотреть файл

@ -29,6 +29,21 @@
"downloadScreenshot": {
"message": "Download"
},
"downloadOnlyNotice": {
"message": "You are currently in Download-Only mode."
},
"downloadOnlyDetails": {
"message": "Firefox Screenshots automatically changes to Download-Only mode in these situations:"
},
"downloadOnlyDetailsPrivate": {
"message": "In a Private Browsing window."
},
"downloadOnlyDetailsThirdParty": {
"message": "Third-party cookies are disabled."
},
"downloadOnlyDetailsNeverRemember": {
"message": "“Never remember history” is enabled."
},
"notificationLinkCopiedTitle": {
"message": "Link Copied"
},
@ -40,6 +55,28 @@
}
}
},
"copyScreenshot": {
"message": "Copy"
},
"notificationImageCopiedTitle": {
"message": "Shot Copied"
},
"notificationImageCopiedDetails": {
"message": "Your shot has been copied to the clipboard. Press $META_KEY$-V to paste.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "This image has been cropped to $PIXELS$px.",
"placeholders": {
"pixels": {
"content": "$1"
}
}
},
"requestErrorTitle": {
"message": "Out of order."
},
@ -64,6 +101,15 @@
"selfScreenshotErrorTitle": {
"message": "You cant take a shot of a Firefox Screenshots page!"
},
"emptySelectionErrorTitle": {
"message": "Your selection is too small"
},
"privateWindowErrorTitle": {
"message": "Screenshots is disabled in Private Browsing Mode"
},
"privateWindowErrorDetails": {
"message": "Sorry for the inconvenience. We are working on this feature for future releases."
},
"genericErrorTitle": {
"message": "Whoa! Firefox Screenshots went haywire."
},
@ -73,6 +119,12 @@
"tourBodyIntro": {
"message": "Take, save, and share screenshots without leaving Firefox."
},
"tourHeaderPageAction": {
"message": "A new way to save"
},
"tourBodyPageAction": {
"message": "Expand the page actions menu in the address bar any time you want to take a screenshot."
},
"tourHeaderClickAndDrag": {
"message": "Capture Just What You Want"
},
@ -102,5 +154,25 @@
},
"tourDone": {
"message": "Done"
},
"termsAndPrivacyNotice2": {
"message": "By using Firefox Screenshots, you agree to our $TERMSANDPRIVACYNOTICETERMSLINK$ and $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
"placeholders": {
"termsandprivacynoticetermslink": {
"content": "$1"
},
"termsandprivacynoticeprivacylink": {
"content": "$2"
}
}
},
"termsAndPrivacyNoticeTermsLink": {
"message": "Terms"
},
"termsAndPrivacyNoticyPrivacyLink": {
"message": "Privacy Notice"
},
"libraryLabel": {
"message": "Screenshots"
}
}

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“Never remember history” is enabled."
},
"downloadOnlyDetailsESR": {
"message": "You are using Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Uploads have been disabled."
},
"notificationLinkCopiedTitle": {
"message": "Link Copied"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“Neniam memori historion” estas aktiva."
},
"downloadOnlyDetailsESR": {
"message": "Vi uzas Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "La alŝutoj estis malaktivigitaj."
},
"notificationLinkCopiedTitle": {
"message": "Ligilo kopiita"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“Nunca recordar historial” está habilitado."
},
"downloadOnlyDetailsESR": {
"message": "Está usando Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Las cargas de archivos están desactivadas."
},
"notificationLinkCopiedTitle": {
"message": "Enlace copiado"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Al estar activado \"Nunca recordar el historial\"."
},
"downloadOnlyDetailsESR": {
"message": "Estás usando Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Las subidas han sido desactivadas."
},
"notificationLinkCopiedTitle": {
"message": "Enlace copiado"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Cuando está activada la opción “No recordar nunca el historial”."
},
"downloadOnlyDetailsESR": {
"message": "Estás usando Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Las cargas se han deshabilitado."
},
"notificationLinkCopiedTitle": {
"message": "Enlace copiado"
},
@ -132,10 +138,10 @@
"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."
},
"tourHeaderFullPage": {
"message": "Haz capturas de Windows o páginas completas"
"message": "Haz capturas de ventanas o de páginas completas"
},
"tourBodyFullPage": {
"message": "Selecciona los botones de la parte superior derecha para capturar el área visible en Windows o la página completa."
"message": "Selecciona los botones de la parte superior derecha para capturar el área visible en la ventana o en la página completa."
},
"tourHeaderDownloadUpload": {
"message": "Como más te guste"

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“Nunca recordar el historial” está habilitado."
},
"downloadOnlyDetailsESR": {
"message": "Estás usando Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Las cargas de archivos están deshabilitadas."
},
"notificationLinkCopiedTitle": {
"message": "Enlace copiado"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "امکان «هیچ‌وقت تاریخچه ذخیره نشود» فعال باشد."
},
"downloadOnlyDetailsESR": {
"message": "شما از فایرفاکس ESR استفاده می‌کنید."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "بارگیری ها غیرفعال شده اند."
},
"notificationLinkCopiedTitle": {
"message": "پیوند کپی شد"
},

Просмотреть файл

@ -4,5 +4,34 @@
},
"addonAuthorsList": {
"message": "Mozilla <screenshots-feedback@mozilla.com>"
},
"downloadOnlyNotice": {
"message": "Ngonɗaa e oo sahaa ko e mbaydi Aawto-Tan."
},
"downloadOnlyDetails": {
"message": "Leƴƴande kuurgal Firefox ina wayla ɗoon e ɗoon e mbaydi Aawto-Tan e ɗii ngonkaaji:"
},
"downloadOnlyDetailsPrivate": {
"message": "Nder henorde Banngogol Suturo."
},
"downloadOnlyDetailsThirdParty": {
"message": "Cuukiije jiggaaɗe ɗee ndaaƴaama."
},
"downloadOnlyDetailsNeverRemember": {
"message": "“Woto siiftor aslol abadaa” hurminaama."
},
"copyScreenshot": {
"message": "Natto"
},
"notificationImageCopiedTitle": {
"message": "Natto ngoo nattaama"
},
"notificationImageCopiedDetails": {
"message": "Natto maa nattaama e ɗakkitorde ndee. Ñoƴƴu $META_KEY$-V ngam ɗakkude.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
}
}

Просмотреть файл

@ -29,6 +29,27 @@
"downloadScreenshot": {
"message": "Lataa"
},
"downloadOnlyNotice": {
"message": "Olet vain lataus -tilassa."
},
"downloadOnlyDetails": {
"message": "Firefox Screenshots vaihtuu automaattisesti vain lataus -tilaan, kun"
},
"downloadOnlyDetailsPrivate": {
"message": "käytetään yksityisen selaamisen ikkunaa"
},
"downloadOnlyDetailsThirdParty": {
"message": "kolmannen osapuolen evästeet ovat estetty"
},
"downloadOnlyDetailsNeverRemember": {
"message": "”Ei mitään historiatietoja” on valittuna"
},
"downloadOnlyDetailsESR": {
"message": "Käytät Firefox ESR:ää."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Verkkoon tallentaminen on poistettu käytöstä."
},
"notificationLinkCopiedTitle": {
"message": "Linkki kopioitu"
},
@ -40,6 +61,28 @@
}
}
},
"copyScreenshot": {
"message": "Kopioi"
},
"notificationImageCopiedTitle": {
"message": "Kaappaus kopioitu"
},
"notificationImageCopiedDetails": {
"message": "Kaappauksesi on kopioitu leikepöydälle. Liitä se painamalla $META_KEY$-V.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "Tämä kuva on rajattu $PIXELS$ kuvapisteeseen.",
"placeholders": {
"pixels": {
"content": "$1"
}
}
},
"requestErrorTitle": {
"message": "Epäkunnossa."
},
@ -82,6 +125,12 @@
"tourBodyIntro": {
"message": "Ota, tallenna ja jaa kuvakaappaus poistumatta Firefoxista."
},
"tourHeaderPageAction": {
"message": "Uusi tapa tallentaa"
},
"tourBodyPageAction": {
"message": "Avaa osoitepalkissa oleva Sivun toiminnot -valikko milloin vain, kun haluat ottaa kuvakaappauksen."
},
"tourHeaderClickAndDrag": {
"message": "Kaappaa mitä haluat"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "lorsque « Ne jamais conserver lhistorique » est activé."
},
"downloadOnlyDetailsESR": {
"message": "Vous utilisez Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Lenvoi de fichiers est désactivé."
},
"notificationLinkCopiedTitle": {
"message": "Lien copié"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Nea skiednis ûnthâlde ynskeakele is."
},
"downloadOnlyDetailsESR": {
"message": "Jo brûke Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Oplaads binne útskeakele."
},
"notificationLinkCopiedTitle": {
"message": "Keppeling kopiearre"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“ઇતિહાસ ક્યારેય ન સંભારવો” સક્ષમ કરેલુ છે."
},
"downloadOnlyDetailsESR": {
"message": "તમે Firefox ESR નો ઉપયોગ કરી રહ્યા છો."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "અપલોડ્સ અક્ષમ કરવામાં આવ્યા છે."
},
"notificationLinkCopiedTitle": {
"message": "લિંક કૉપિ"
},
@ -55,6 +61,20 @@
}
}
},
"copyScreenshot": {
"message": "નકલ કરો"
},
"notificationImageCopiedTitle": {
"message": "શોટ નકલ કર્યો"
},
"notificationImageCopiedDetails": {
"message": "તમારા શોટ ક્લિપબોર્ડ પર નકલ કરવામાં આવ્યાં છે. પેસ્ટ કરવા માટે $META_KEY$-V દબાવો.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "આ છબીને $PIXELS$px પર કાપવામાં આવી છે.",
"placeholders": {

Просмотреть файл

@ -41,6 +41,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“कभी इतिहास याद न रखें” सक्षम है."
},
"downloadOnlyDetailsESR": {
"message": "आप Firefox ESR का उपयोग कर रहे हैं."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "अपलोड अक्षम कर दिए गए हैं."
},
"notificationLinkCopiedTitle": {
"message": "लिंक की नक़ल की गयी"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "„Sej historiju ženje njespomjatkować“ je zmóžnjene."
},
"downloadOnlyDetailsESR": {
"message": "Wužiwaće Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Nahraća su znjemóžnjene."
},
"notificationLinkCopiedTitle": {
"message": "Wotkaz kopěrowany"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "A „Soha ne jegyezze meg az előzményeket” engedélyezett."
},
"downloadOnlyDetailsESR": {
"message": "Firefox ESR-t használ."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "A feltöltések le lettek tiltva."
},
"notificationLinkCopiedTitle": {
"message": "Hivatkozás másolva"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“Oblidar le chronologia” activate."
},
"downloadOnlyDetailsESR": {
"message": "Tu usa Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Le cargamentos ha essite disactivate."
},
"notificationLinkCopiedTitle": {
"message": "Ligamine copiate"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“Jangan pernah ingat riwayat” diaktifkan."
},
"downloadOnlyDetailsESR": {
"message": "Anda menggunakan Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Unduhan dinonaktifkan."
},
"notificationLinkCopiedTitle": {
"message": "Tautan Disalin"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Se è attiva lopzione “Non salvare la cronologia”."
},
"downloadOnlyDetailsESR": {
"message": "Stai usando Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Il caricamento è disattivato."
},
"notificationLinkCopiedTitle": {
"message": "Link copiato"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "「履歴を一切記憶させない」設定が有効になっている場合。"
},
"downloadOnlyDetailsESR": {
"message": "あなたは Firefox ESR をお使いです。"
},
"downloadOnlyDetailsNoUploadPref": {
"message": "アップロードは無効化されています。"
},
"notificationLinkCopiedTitle": {
"message": "リンクをコピーしました"
},

Просмотреть файл

@ -1,6 +1,6 @@
{
"addonDescription": {
"message": "გადაუღეთ ვიდეოები სურათები ვებ-გვერდებს და შეინახეთ დროებით ან მუდმივად."
"message": "გადაუღეთ ვიდეოები ა სურათები ვებგვერდებს და შეინახეთ დროებით, ან მუდმივად."
},
"addonAuthorsList": {
"message": "Mozilla <screenshots-feedback@mozilla.com>"
@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“არასდროს დაიმახსოვროს ისტორია” ჩართულია."
},
"downloadOnlyDetailsESR": {
"message": "თქვენ იყენებთ Firefox ESR-ს."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "ატვირთვა შეზღუდულია."
},
"notificationLinkCopiedTitle": {
"message": "ბმული დაკოპირებულია"
},
@ -96,7 +102,7 @@
"message": "ამ გვერდისთვის სურათის გადაღება ვერ ხერხდება."
},
"unshootablePageErrorDetails": {
"message": "ეს არ არის ჩვეულებრივი ვებ-გვერდი, ამიტომაც შეუძლებელია სურათის გადაღება."
"message": "ეს არ არის ჩვეულებრივი ვებგვერდი, ამიტომაც შეუძლებელია სურათის გადაღება."
},
"selfScreenshotErrorTitle": {
"message": "Firefox Screenshots-ის გვერდისთვის სურათის გადაღება არ შეგიძლიათ!"
@ -114,7 +120,7 @@
"message": "ვაი! Firefox Screenshots მწყობრიდან გამოვიდა."
},
"genericErrorDetails": {
"message": "გაუგებარია რა მოხდა. ისევ ცდით ხელახლა თუ სხვა ვებ-გვერდს გადაუღებთ სურათს?"
"message": "გაუგებარია რა მოხდა. ისევ ცდით ხელახლა, თუ სხვა ვებგვერდს გადაუღებთ სურათს?"
},
"tourBodyIntro": {
"message": "გადაიღეთ, შეინახეთ და გააზიარეთ ეკრანის სურათები Firefox-იდან გაუსვლელად."
@ -129,10 +135,10 @@
"message": "გადაუღეთ სურათი რასაც გინდათ"
},
"tourBodyClickAndDrag": {
"message": "გადაადგილეთ ან დააწკაპეთ გვერდზე გადასაღები ნაწილის შესარჩევად. ასევე, კურსორის გადატარებით შეგიძლიათ მონიშნოთ სასურველი არეალი."
"message": "გადაადგილეთ, ან დააწკაპეთ გვერდზე გადასაღები სივრცის შესარჩევად. ასევე, მაჩვენებელი ისარის გადატარებით შეგიძლიათ მონიშნოთ სასურველი არე."
},
"tourHeaderFullPage": {
"message": "გადაუღეთ სურათები ფანჯრებს ან მთლიან ვებ-გვერდებს"
"message": "გადაუღეთ სურათები ფანჯრებს, ან მთლიან ვებგვერდებს"
},
"tourBodyFullPage": {
"message": "მარჯვენა ზედა კუთხეში არსებული ღილაკების საშუალებით, შეგიძლიათ გადაუღოთ სურათი ხილულ ნაწილს, ან მთლიან გვერდს."

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“Werǧin ad tecfuḍ γef umazray” insa."
},
"downloadOnlyDetailsESR": {
"message": "Aql-ak teseqdaceḍ Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Tuzna n ifayluyen tensa."
},
"notificationLinkCopiedTitle": {
"message": "Aseγwen yettwanγel"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "\"Тарихты ешқашан сақтамау\" іске қосылған."
},
"downloadOnlyDetailsESR": {
"message": "Сіз Firefox ESR қолданып отырсыз."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Жүктеп жіберу сөндірілген."
},
"notificationLinkCopiedTitle": {
"message": "Сілтеме көшірілді"
},

Просмотреть файл

@ -36,7 +36,7 @@
"message": "Firefox Screenshots은 다음과 같은 경우에만 다운로드 전용 모드로 전환됩니다:"
},
"downloadOnlyDetailsPrivate": {
"message": "개인정보 호 창에 있을 때"
"message": "개인정보 호 창에 있을 때"
},
"downloadOnlyDetailsThirdParty": {
"message": "제 3자 쿠키가 비활성화되어 있을 때"

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "kai įjungta „niekada nevesti žurnalo“ nuostata"
},
"downloadOnlyDetailsESR": {
"message": "Jūs naudojate „Firefox“ ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Įkėlimai išjungti."
},
"notificationLinkCopiedTitle": {
"message": "Saitas nukopijuotas"
},

Просмотреть файл

@ -0,0 +1,115 @@
{
"addonDescription": {
"message": "വെബിൽ നിന്ന് ക്ലിപ്പുകളും സ്ക്രീൻഷോട്ടുകളും എടുത്ത് അവ താൽക്കാലികമായോ അല്ലെങ്കിൽ സ്ഥിരമായോ സംരക്ഷിക്കുക."
},
"addonAuthorsList": {
"message": "മോസില്ല <screenshots-feedback@mozilla.com>"
},
"contextMenuLabel": {
"message": "സ്ക്രീൻഷോട്ട് എടുക്കുക"
},
"myShotsLink": {
"message": "എന്റെ ഷോട്ടുകള്‍"
},
"screenshotInstructions": {
"message": "ഒരു പ്രദേശം തിരഞ്ഞെടുക്കാൻ താളില്‍ ഡ്രാഗ് ചെയ്യുക അല്ലെങ്കിൽ ക്ലിക്കുചെയ്യുക. റദ്ദാക്കാൻ ESC അമർത്തുക."
},
"saveScreenshotSelectedArea": {
"message": "സംരക്ഷിക്കുക"
},
"saveScreenshotVisibleArea": {
"message": "കാണുന്നതു സംരക്ഷിക്കുക"
},
"saveScreenshotFullPage": {
"message": "താള്‍ മുഴുവന്‍ സംരക്ഷിക്കുക"
},
"cancelScreenshot": {
"message": "റദ്ദാക്കുക"
},
"downloadScreenshot": {
"message": "ഡൗണ്‍ലോഡ് ചെയ്യുക"
},
"downloadOnlyDetailsPrivate": {
"message": "സ്വകാര്യ ബ്രൌസിങ്ങ് ജാലകത്തില്‍."
},
"notificationLinkCopiedTitle": {
"message": "ലിങ്ക് പകര്‍ത്തി"
},
"notificationLinkCopiedDetails": {
"message": "നിങ്ങളുടെ ഷോട്ടിലേക്കുള്ള ലിങ്ക് ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തി. ഒട്ടിക്കാൻ $META_KEY$-V അമർത്തുക.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"copyScreenshot": {
"message": "പകര്‍ത്തുക"
},
"notificationImageCopiedTitle": {
"message": "ഷോട്ട് പകര്‍ത്തി"
},
"requestErrorTitle": {
"message": "പണി പാളി."
},
"requestErrorDetails": {
"message": "ക്ഷമിക്കണം! താങ്കളുടെ സ്ക്രീൻഷോട്ട് സൂക്ഷിക്കാന്‍ കഴിഞ്ഞില്ല. ദയവായി പിന്നീടു ശ്രമിക്കുക."
},
"connectionErrorTitle": {
"message": "നിങ്ങളുടെ സ്ക്രീൻഷോട്ടുകളിലേക്കു ബന്ധിപ്പിക്കാന്‍ കഴിയുന്നില്ല."
},
"connectionErrorDetails": {
"message": "നിങ്ങളുടെ ഇന്റർനെറ്റ് കണക്ഷൻ പരിശോധിക്കുക. നിങ്ങൾക്ക് ഇന്റർനെറ്റിലേക്ക് ബന്ധിപ്പിക്കാൻ കഴിയുന്നുണ്ടെങ്കിൽ, ഫയർഫോക്സ് സ്ക്രീൻഷോട്ടുകൾ സേവനത്തിൽ താൽക്കാലിക പ്രശ്നം ഉണ്ടായേക്കാം."
},
"loginErrorDetails": {
"message": "ഫയർഫോക്സ് സ്ക്രീൻഷോട്ടുകൾ സേവനത്തിൽ ഒരു പ്രശ്നമുള്ളതിനാൽ ഞങ്ങൾക്ക് നിങ്ങളുടെ ഷോട്ട് സംരക്ഷിക്കാൻ കഴിഞ്ഞില്ല. പിന്നീട് വീണ്ടും ശ്രമിക്കുക."
},
"unshootablePageErrorTitle": {
"message": "ഈ താളിന്റെ സ്ക്രീൻഷോട്ട് എടുക്കാന്‍ സാധ്യമല്ല."
},
"unshootablePageErrorDetails": {
"message": "ഇതൊരു സാധാരണ താള്‍ അല്ല, അതിനാൽ അതിന്റെ ഒരു സ്ക്രീൻഷോട്ട് എടുക്കാനാകില്ല."
},
"selfScreenshotErrorTitle": {
"message": "സ്ക്രീൻഷോട്ട് താളിന്റെ സ്ക്രീൻഷോട്ട് എടുക്കാന്‍ പറ്റില്ല‌!"
},
"emptySelectionErrorTitle": {
"message": "നിങ്ങളുടെ തെരഞ്ഞെടുപ്പ് തീരെ ചെറുതാണ്"
},
"privateWindowErrorDetails": {
"message": "അസൗകര്യം നേരിടേണ്ടി വന്നതിൽ ഖേദിക്കുന്നു. ഈ സവിശേഷതയുള്ള ഭാവിയിലെ റിലീസുകൾക്കായി ഞങ്ങൾ പ്രവർത്തിക്കുന്നു."
},
"genericErrorTitle": {
"message": "അയ്യോ! ഫയര്‍ഫോക്സ് സ്ക്രീൻഷോട്ടിനു എന്തോ പറ്റി."
},
"genericErrorDetails": {
"message": "എന്താ സംഭവിച്ചതെന്ന് വല്യ പിടി ഇല്ല. ഒന്നുകൂടി നോക്കുകയോ അല്ലെങ്കില്‍ വേറെ താളിന്റെ സ്ക്രീൻഷോട്ട് എടുക്കുകയോ ചെയ്യുന്നോ?"
},
"tourHeaderPageAction": {
"message": "സൂക്ഷിക്കാന്‍ പുതിയൊരു മാർഗ്ഗം"
},
"tourHeaderDownloadUpload": {
"message": "നിങ്ങളുടെ ഇഷ്ടാനുസൃതം"
},
"tourSkip": {
"message": "ഒഴിവാക്കുക"
},
"tourNext": {
"message": "അടുത്ത സ്ലൈഡ്"
},
"tourPrevious": {
"message": "മുമ്പത്തെ സ്ലൈഡ്"
},
"tourDone": {
"message": "തീര്‍ന്നു"
},
"termsAndPrivacyNoticeTermsLink": {
"message": "നിബന്ധനകൾ"
},
"termsAndPrivacyNoticyPrivacyLink": {
"message": "സ്വകാര്യതാ അറിയിപ്പ്"
},
"libraryLabel": {
"message": "സ്ക്രീൻഷോട്ടുകൾ"
}
}

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“Jangan sesekali ingat sejarah” didayakan."
},
"downloadOnlyDetailsESR": {
"message": "Anda sedang menggunakan Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Muat naik telah dinyahdayakan."
},
"notificationLinkCopiedTitle": {
"message": "Pautan Disalin"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "«Husk aldri historikk» er aktivert."
},
"downloadOnlyDetailsESR": {
"message": "Du bruker Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Opplastinger er slått av."
},
"notificationLinkCopiedTitle": {
"message": "Lenke kopiert"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Nooit geschiedenis onthouden is ingeschakeld."
},
"downloadOnlyDetailsESR": {
"message": "U gebruikt Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Uploads zijn uitgeschakeld."
},
"notificationLinkCopiedTitle": {
"message": "Koppeling gekopieerd"
},
@ -141,7 +147,7 @@
"message": "Zoals u wilt"
},
"tourBodyDownloadUpload": {
"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."
"message": "Bewaar uw bijgesneden afbeeldingen 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."
},
"tourSkip": {
"message": "Overslaan"

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "«Hugs aldri historikk» er aktivert."
},
"downloadOnlyDetailsESR": {
"message": "Du brukar Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Opplastingar er slått av."
},
"notificationLinkCopiedTitle": {
"message": "Lenke kopiert"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Zachowywanie historii jest wyłączone."
},
"downloadOnlyDetailsESR": {
"message": "Używany jest Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Wysyłanie zostało wyłączone."
},
"notificationLinkCopiedTitle": {
"message": "Skopiowano odnośnik"
},

Просмотреть файл

@ -6,7 +6,7 @@
"message": "Mozilla <screenshots-feedback@mozilla.com>"
},
"contextMenuLabel": {
"message": "Tirar uma captura de tela"
"message": "Capturar tela"
},
"myShotsLink": {
"message": "Minhas capturas"
@ -30,10 +30,10 @@
"message": "Baixar"
},
"downloadOnlyNotice": {
"message": "Você está atualmente no modo somente download."
"message": "Você está atualmente no modo somente baixar."
},
"downloadOnlyDetails": {
"message": "O Firefox Screenshots alterna automaticamente para o modo somente download nessas situações:"
"message": "O Firefox Screenshots muda automaticamente para o modo Somente baixar nessas situações:"
},
"downloadOnlyDetailsPrivate": {
"message": "Em uma janela de navegação privativa."
@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“Nunca lembrar o histórico” está habilitado."
},
"downloadOnlyDetailsESR": {
"message": "Você está usando o Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Os envios foram desabilitados."
},
"notificationLinkCopiedTitle": {
"message": "Link copiado"
},
@ -81,7 +87,7 @@
"message": "Oops! Fora do ar."
},
"requestErrorDetails": {
"message": "Desculpe! Não conseguimos salvar a sua captura de tela. Por favor, tente novamente mais tarde."
"message": "Desculpe! Não conseguimos salvar a sua captura de tela. Tente novamente mais tarde."
},
"connectionErrorTitle": {
"message": "Não conseguimos nos conectar às suas capturas de tela."
@ -105,13 +111,13 @@
"message": "Sua seleção é muito pequena"
},
"privateWindowErrorTitle": {
"message": "As capturas de tela estão desabilitadas no modo de navegação privativa"
"message": "As capturas de tela é desativadas no modo de navegação privativa"
},
"privateWindowErrorDetails": {
"message": "Lamentamos o inconveniente. Estamos trabalhando neste recurso para lançamentos futuros."
},
"genericErrorTitle": {
"message": "Uau! Algo correu mal com a capturas de tela do Firefox."
"message": "Uoou! O Firefox Screenshot enlouqueceu."
},
"genericErrorDetails": {
"message": "Não temos certeza do que acabou de acontecer. Tentar novamente ou fazer uma captura de uma página diferente?"
@ -123,7 +129,7 @@
"message": "Um novo jeito de salvar"
},
"tourBodyPageAction": {
"message": "Expandir o menu de ações de página na barra de endereços sempre que você quiser fazer uma captura de tela."
"message": "Abra o menu de ações da página na barra de endereços sempre que quiser fazer uma captura de tela."
},
"tourHeaderClickAndDrag": {
"message": "Capture apenas o que você quer"
@ -141,7 +147,7 @@
"message": "Como você quiser"
},
"tourBodyDownloadUpload": {
"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."
"message": "Salve 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 encontrar todas as capturas que tirou."
},
"tourSkip": {
"message": "Pular"
@ -156,7 +162,7 @@
"message": "Concluído"
},
"termsAndPrivacyNotice2": {
"message": "Ao usar o Firefox Screenshots, você concorda com os $TERMSANDPRIVACYNOTICETERMSLINK$ e $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
"message": "Ao usar o Firefox Screenshots, você concorda com os $TERMSANDPRIVACYNOTICETERMSLINK$ e a $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
"placeholders": {
"termsandprivacynoticetermslink": {
"content": "$1"

Просмотреть файл

@ -27,13 +27,13 @@
"message": "Cancelar"
},
"downloadScreenshot": {
"message": "Descarregar"
"message": "Transferir"
},
"downloadOnlyNotice": {
"message": "Está atualmente no modo descargas-apenas."
"message": "Está atualmente no modo Transferências-apenas."
},
"downloadOnlyDetails": {
"message": "O Firefox Screenshots muda automaticamente para o modo descargas-apenas nestas situações:"
"message": "O Firefox Screenshots muda automaticamente para o modo Transferências-apenas nestas situações:"
},
"downloadOnlyDetailsPrivate": {
"message": "Numa janela de navegação privada."
@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“Nunca memorizar histórico” está ativado."
},
"downloadOnlyDetailsESR": {
"message": "Está a utilizar o Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Os carregamentos foram desativados."
},
"notificationLinkCopiedTitle": {
"message": "Ligação copiada"
},
@ -87,7 +93,7 @@
"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 Firefox Screenshots."
"message": "Por favor, verifique a sua ligação à Internet. Se consegue ligar-se à Internet, pode existir um problema temporário com o serviço Firefox Screenshots."
},
"loginErrorDetails": {
"message": "Não conseguimos guardar a sua captura porque existe um problema com o serviço Firefox Screenshots. Por favor tente novamente mais tarde."
@ -141,7 +147,7 @@
"message": "Como você gosta"
},
"tourBodyDownloadUpload": {
"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 encontrar todas as capturas que tirou."
"message": "Guarde as suas capturas na web para partilhar mais facilmente ou transfira-as para o seu computador. Pode também clicar no botão Minhas capturas para encontrar todas as capturas que tirou."
},
"tourSkip": {
"message": "SALTAR"

Просмотреть файл

@ -29,6 +29,21 @@
"downloadScreenshot": {
"message": "Telechargiar"
},
"downloadOnlyNotice": {
"message": "Ti es actualmain en il modus che permetta mo da telechargiar."
},
"downloadOnlyDetails": {
"message": "En suandantas situaziuns permetta Firefox Screenshots mo da telechargiar:"
},
"downloadOnlyDetailsPrivate": {
"message": "Sche ti navigheschas en il modus privat."
},
"downloadOnlyDetailsThirdParty": {
"message": "Sche cookies da terzs èn deactivads."
},
"downloadOnlyDetailsNeverRemember": {
"message": "Sche l'opziun «Mai memorisar la cronologia» è activada."
},
"notificationLinkCopiedTitle": {
"message": "Copià la colliaziun"
},
@ -40,6 +55,28 @@
}
}
},
"copyScreenshot": {
"message": "Copiar"
},
"notificationImageCopiedTitle": {
"message": "Copià il maletg"
},
"notificationImageCopiedDetails": {
"message": "Tes maletg dal visur è vegnì copià en l'archiv provisoric. Smatga $META_KEY$-V per l'encollar.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "Quest maletg è vegnì retaglià a $PIXELS$px.",
"placeholders": {
"pixels": {
"content": "$1"
}
}
},
"requestErrorTitle": {
"message": "Ord funcziun."
},
@ -82,6 +119,12 @@
"tourBodyIntro": {
"message": "Far, memorisar e cundivider maletgs da visur senza bandunar Firefox."
},
"tourHeaderPageAction": {
"message": "Ina nova pussaivladad da memorisar"
},
"tourBodyPageAction": {
"message": "Vuls far in maletg dal visur? Avra il menu da las «Acziuns da pagina» en la trav d'adressas."
},
"tourHeaderClickAndDrag": {
"message": "Far maletgs da visur da tut che vi vuls"
},

Просмотреть файл

@ -29,6 +29,27 @@
"downloadScreenshot": {
"message": "Descarcă"
},
"downloadOnlyNotice": {
"message": "Momentan sunteți în modul descărcare."
},
"downloadOnlyDetails": {
"message": "Firefox Screenshots se mută automat în modul descărcare în aceste situații:"
},
"downloadOnlyDetailsPrivate": {
"message": "În fereastra de navigare privată."
},
"downloadOnlyDetailsThirdParty": {
"message": "Atunci când cookie-urile terțe sunt dezactivate."
},
"downloadOnlyDetailsNeverRemember": {
"message": "Atunci când este activată opțiunea „Nu memora istoricul”."
},
"downloadOnlyDetailsESR": {
"message": "Acum folosești Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Încărcările au fost dezactivate."
},
"notificationLinkCopiedTitle": {
"message": "Link copiat"
},
@ -40,12 +61,49 @@
}
}
},
"copyScreenshot": {
"message": "Copiază"
},
"notificationImageCopiedTitle": {
"message": "Captura copiată"
},
"notificationImageCopiedDetails": {
"message": "Captura a fost copiată în clipboard. Apasă $META_KEY$-V pentru a lipi.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "Imaginea a fost decupată la $PIXELS$ px.",
"placeholders": {
"pixels": {
"content": "$1"
}
}
},
"requestErrorTitle": {
"message": "Nefuncțional."
},
"requestErrorDetails": {
"message": "Ne pare rău! Nu am putut salva captura. Încearcă din nou mai târziu."
},
"connectionErrorTitle": {
"message": "Nu ne putem conecta la capturile tale."
},
"connectionErrorDetails": {
"message": "Verifică conexiunea la internet. Dacă te poți conecta la internet ar putea fi o problemă temporară cu serviciul Firefox Screenshots."
},
"loginErrorDetails": {
"message": "Nu am putut să salvăm captura ta deoarece este o problemă cu serviciul Firefox Screenshots. Încearcă din nou mai târziu."
},
"unshootablePageErrorTitle": {
"message": "Nu putem realiza o captură de ecran a acestei pagini."
},
"unshootablePageErrorDetails": {
"message": "Aceasta este o pagină web standard așa că nu putem lua o captură a acesteia."
},
"selfScreenshotErrorTitle": {
"message": "Nu poți realiza o captură a paginii Firefox Screenshots!"
},
@ -55,9 +113,24 @@
"privateWindowErrorTitle": {
"message": "Capturile de ecran sunt dezactivate în modul de navigare privată"
},
"privateWindowErrorDetails": {
"message": "Ne pare rău pentru inconveniență. Lucrăm să introducem această funcție în versiunile viitoare."
},
"genericErrorTitle": {
"message": "Ups! Firefox Screenshots a întâmpinat o eroare."
},
"genericErrorDetails": {
"message": "Nu știm ce s-a întâmplat. Poți încerca să iei o nouă captură sau o captură de la o altă pagină?"
},
"tourBodyIntro": {
"message": "Preia, salvează și împărtășește capturi de ecran fără a părăsi Firefox."
},
"tourHeaderPageAction": {
"message": "O nouă modalitate de salvare"
},
"tourBodyPageAction": {
"message": "Extinde meniul de acțiuni ale paginii în bara de adrese oricând dorești să faci o captură."
},
"tourHeaderClickAndDrag": {
"message": "Capturează întocmai ceea ce dorești"
},
@ -73,6 +146,9 @@
"tourHeaderDownloadUpload": {
"message": "Pe placul tău"
},
"tourBodyDownloadUpload": {
"message": "Salvează capturile decupate pe web pentru a le împărtăși mai ușor, sau descarcă-le pe calculator. Mai poți face clic pe butonul My Shots (capturile mele) pentru a găsi toate capturile luate."
},
"tourSkip": {
"message": "OMITE"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Включено «не будет запоминать историю»."
},
"downloadOnlyDetailsESR": {
"message": "Вы используете Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Загрузка была отключена."
},
"notificationLinkCopiedTitle": {
"message": "Ссылка скопирована"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Máte úplne zakázané ukladanie histórie prehliadania."
},
"downloadOnlyDetailsESR": {
"message": "Používate Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Nahrávanie bolo zakázané."
},
"notificationLinkCopiedTitle": {
"message": "Odkaz bol skopírovaný"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Ko je Firefox nastavljen, naj ne shranjuje zgodovine."
},
"downloadOnlyDetailsESR": {
"message": "Uporabljate Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Nalaganje je onemogočeno."
},
"notificationLinkCopiedTitle": {
"message": "Povezava kopirana"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Опција „Никада не памти историјат“ је омогућена."
},
"downloadOnlyDetailsESR": {
"message": "Користите Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Отпремања су онемогућена."
},
"notificationLinkCopiedTitle": {
"message": "Веза копирана"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "\"Inte spara någon historik\" är aktiverad."
},
"downloadOnlyDetailsESR": {
"message": "Du använder Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Uppladdningar har inaktiverats."
},
"notificationLinkCopiedTitle": {
"message": "Länk kopierad"
},

Просмотреть файл

@ -23,9 +23,18 @@
"downloadScreenshot": {
"message": "దింపుకోండి"
},
"downloadOnlyNotice": {
"message": "మీరు ప్రస్తుతం దింపుకోలు-మాత్రమే రీతిలో ఉన్నారు."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "ఎక్కింపులు అచేతమై ఉన్నాయి."
},
"notificationLinkCopiedTitle": {
"message": "లంకె కాపీ అయింది"
},
"copyScreenshot": {
"message": "కాపీచెయ్యి"
},
"requestErrorTitle": {
"message": "పని చెయుట లేదు."
},
@ -51,7 +60,10 @@
"message": "అయ్యో! Firefox స్క్రీన్షాట్లు haywire వెళ్ళింది."
},
"tourBodyIntro": {
"message": "Firefox ను వదలకుండా స్క్రీన్షాట్లను తీసుకోండి, సేవ్ చేయండి మరియు పంచండి."
"message": "Firefoxను వదలకుండానే తెరపట్లు తీసుకోండి, భద్రపరచుకోండి, పంచుకోండి."
},
"tourHeaderPageAction": {
"message": "భద్రపరచుకోడానికి కొత్త మార్గం"
},
"tourHeaderClickAndDrag": {
"message": ""

Просмотреть файл

@ -29,6 +29,15 @@
"downloadScreenshot": {
"message": "ดาวน์โหลด"
},
"downloadOnlyNotice": {
"message": "คุณกำลังอยู่ในโหมดดาวน์โหลดเท่านั้น"
},
"downloadOnlyDetailsPrivate": {
"message": "ในหน้าต่างการท่องเว็บแบบส่วนตัว"
},
"downloadOnlyDetailsThirdParty": {
"message": "คุกกี้บุคคลที่สามถูกปิดการใช้งาน"
},
"notificationLinkCopiedTitle": {
"message": "คัดลอกลิงก์แล้ว"
},
@ -40,6 +49,14 @@
}
}
},
"imageCroppedWarning": {
"message": "ภาพนี้ถูกตัดเป็น $PIXELS$ พิกเซล",
"placeholders": {
"pixels": {
"content": "$1"
}
}
},
"requestErrorTitle": {
"message": "ใช้งานไม่ได้"
},
@ -82,6 +99,9 @@
"tourBodyIntro": {
"message": "จับ บันทึก และแบ่งปันภาพหน้าจอโดยไม่ต้องออกจาก Firefox"
},
"tourHeaderPageAction": {
"message": "หนทางใหม่ในการบันทึก"
},
"tourHeaderClickAndDrag": {
"message": "จับภาพแค่สิ่งที่คุณต้องการ"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "“Geçmişi asla hatırlama” devredeyken"
},
"downloadOnlyDetailsESR": {
"message": "Firefox ESR kullanıyorsunuz."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Gönderme işlemi devre dışı bırakılmıştır."
},
"notificationLinkCopiedTitle": {
"message": "Bağlantı kopyalandı"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "Активовано налаштування “Ніколи не пам'ятати історії”."
},
"downloadOnlyDetailsESR": {
"message": "Ви використовуєте Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Вивантаження було вимкнено."
},
"notificationLinkCopiedTitle": {
"message": "Посилання скопійовано"
},

Просмотреть файл

@ -29,6 +29,27 @@
"downloadScreenshot": {
"message": "Tải xuống"
},
"downloadOnlyNotice": {
"message": "Bạn hiện đang ở chế độ chỉ tải về."
},
"downloadOnlyDetails": {
"message": "Ảnh chụp màn hình của Firefox sẽ tự động chuyển sang chế độ chỉ tải về trong các tình huống:"
},
"downloadOnlyDetailsPrivate": {
"message": "Trong một cửa sổ duyệt web riêng tư."
},
"downloadOnlyDetailsThirdParty": {
"message": "Cookies của bên thứ ba đã bị vô hiệu hóa."
},
"downloadOnlyDetailsNeverRemember": {
"message": "“Không bao giờ ghi nhớ lược sử” đã được kích hoạt."
},
"downloadOnlyDetailsESR": {
"message": "Bạn đang sử dụng Firefox ESR."
},
"downloadOnlyDetailsNoUploadPref": {
"message": "Tải lên đã bị vô hiệu hóa."
},
"notificationLinkCopiedTitle": {
"message": "Đã sao chép liên kết"
},
@ -40,6 +61,20 @@
}
}
},
"copyScreenshot": {
"message": "Sao chép"
},
"notificationImageCopiedTitle": {
"message": "Ảnh chụp màn hình đã được sao chép"
},
"notificationImageCopiedDetails": {
"message": "Ảnh chụp màn hình của bạn đã được sao chép vào clipboard. Nhấn $META_KEY$-V để dán.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"imageCroppedWarning": {
"message": "Ảnh này đã được cắt đến $PIXELS$px.",
"placeholders": {
@ -84,6 +119,9 @@
"genericErrorTitle": {
"message": "Whoa! Ảnh chụp màn hình của Firefox đã bị hoãn."
},
"genericErrorDetails": {
"message": "Chúng tôi không chắc những gì vừa xảy ra. Thử lại hoặc chụp một trang khác?"
},
"tourBodyIntro": {
"message": "Tạo, lưu và chia sẻ ảnh chụp màn hình mà không rời khỏi Firefox."
},
@ -99,6 +137,9 @@
"tourBodyClickAndDrag": {
"message": "Nhấp và kéo để chụp một phần của một trang. Bạn cũng có thể di chuột để làm nổi bật lựa chọn của bạn."
},
"tourHeaderFullPage": {
"message": "Chụp Windows hoặc Toàn bộ trang"
},
"tourBodyFullPage": {
"message": "Chọn các nút ở phía trên bên phải để chụp khu vực nhìn thấy được trong cửa sổ hoặc để chụp toàn bộ trang."
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "已启用“不记录历史记录”。"
},
"downloadOnlyDetailsESR": {
"message": "您目前使用 Firefox ESR 版本。"
},
"downloadOnlyDetailsNoUploadPref": {
"message": "上传已被禁用。"
},
"notificationLinkCopiedTitle": {
"message": "链接已复制"
},

Просмотреть файл

@ -44,6 +44,12 @@
"downloadOnlyDetailsNeverRemember": {
"message": "開啟「永不保留瀏覽紀錄」時。"
},
"downloadOnlyDetailsESR": {
"message": "您使用的是 Firefox ESR 版本。"
},
"downloadOnlyDetailsNoUploadPref": {
"message": "已停用上傳。"
},
"notificationLinkCopiedTitle": {
"message": "已複製鏈結"
},

Просмотреть файл

@ -6,36 +6,71 @@ this.analytics = (function() {
let exports = {};
let telemetryPrefKnown = false;
let telemetryPref;
let telemetryEnabled;
const EVENT_BATCH_DURATION = 1000; // ms for setTimeout
let pendingEvents = [];
let pendingTimings = [];
let eventsTimeoutHandle, timingsTimeoutHandle;
const fetchOptions = {
method: "POST",
mode: "cors",
headers: { "content-type": "application/json" },
credentials: "include"
};
function flushEvents() {
if (pendingEvents.length === 0) {
return;
}
let eventsUrl = `${main.getBackend()}/event`;
let deviceId = auth.getDeviceId();
let sendTime = Date.now();
pendingEvents.forEach(event => {
event.queueTime = sendTime - event.eventTime
log.info(`sendEvent ${event.event}/${event.action}/${event.label || 'none'} ${JSON.stringify(event.options)}`);
});
let body = JSON.stringify({deviceId, events: pendingEvents});
let fetchRequest = fetch(eventsUrl, Object.assign({body}, fetchOptions));
fetchWatcher(fetchRequest);
pendingEvents = [];
}
function flushTimings() {
if (pendingTimings.length === 0) {
return;
}
let timingsUrl = `${main.getBackend()}/timing`;
let deviceId = auth.getDeviceId();
let body = JSON.stringify({deviceId, timings: pendingTimings});
let fetchRequest = fetch(timingsUrl, Object.assign({body}, fetchOptions));
fetchWatcher(fetchRequest);
pendingTimings.forEach(t => {
log.info(`sendTiming ${t.timingCategory}/${t.timingLabel}/${t.timingVar}: ${t.timingValue}`);
});
pendingTimings = [];
}
function sendTiming(timingLabel, timingVar, timingValue) {
// sendTiming is only called in response to sendEvent, so no need to check
// the telemetry pref again here.
let timingCategory = "addon";
return new Promise((resolve, reject) => {
let url = main.getBackend() + "/timing";
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 /timing");
exc.status = req.status;
exc.statusText = req.statusText;
reject(exc);
} else {
resolve();
}
});
log.info(`sendTiming ${timingCategory}/${timingLabel}/${timingVar}: ${timingValue}`);
req.send(JSON.stringify({
deviceId: auth.getDeviceId(),
timingCategory,
timingLabel,
timingVar,
timingValue
}));
pendingTimings.push({
timingCategory,
timingLabel,
timingVar,
timingValue
});
if (!timingsTimeoutHandle) {
timingsTimeoutHandle = setTimeout(() => {
timingsTimeoutHandle = null;
flushTimings();
}, EVENT_BATCH_DURATION);
}
}
exports.sendEvent = function(action, label, options) {
@ -44,7 +79,7 @@ this.analytics = (function() {
log.warn("sendEvent called before we were able to refresh");
return Promise.resolve();
}
if (!telemetryPref) {
if (!telemetryEnabled) {
log.info(`Cancelled sendEvent ${eventCategory}/${action}/${label || 'none'} ${JSON.stringify(options)}`);
return Promise.resolve();
}
@ -59,61 +94,62 @@ this.analytics = (function() {
label = undefined;
}
options = options || {};
// Don't send events if in private browsing.
if (options.incognito) {
return Promise.resolve();
}
// Don't include in event data.
delete options.incognito;
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
}));
options.applicationName = di.appName;
options.applicationVersion = di.addonVersion;
let abTests = auth.getAbTests();
for (let [gaField, value] of Object.entries(abTests)) {
options[gaField] = value;
}
pendingEvents.push({
eventTime: Date.now(),
event: eventCategory,
action,
label,
options
});
if (!eventsTimeoutHandle) {
eventsTimeoutHandle = setTimeout(() => {
eventsTimeoutHandle = null;
flushEvents();
}, EVENT_BATCH_DURATION);
}
// This function used to return a Promise that was not used at any of the
// call sites; doing this simply maintains that interface.
return Promise.resolve();
};
exports.refreshTelemetryPref = function() {
return communication.sendToBootstrap("getTelemetryPref").then((result) => {
return communication.sendToBootstrap("isTelemetryEnabled").then((result) => {
telemetryPrefKnown = true;
if (result === communication.NO_BOOTSTRAP) {
telemetryPref = true;
telemetryEnabled = true;
} else {
telemetryPref = result;
telemetryEnabled = result;
}
}, (error) => {
// If there's an error reading the pref, we should assume that we shouldn't send data
telemetryPrefKnown = true;
telemetryPref = false;
telemetryEnabled = false;
throw error;
});
};
exports.getTelemetryPrefSync = function() {
exports.isTelemetryEnabled = function() {
catcher.watchPromise(exports.refreshTelemetryPref());
return !!telemetryPref;
return telemetryEnabled;
};
let timingData = {};
let timingData = new Map();
// Configuration for filtering the sendEvent stream on start/end events.
// When start or end events occur, the time is recorded.
@ -197,7 +233,7 @@ this.analytics = (function() {
}
function anyMatches(filters, action, label) {
return !!filters.find(filter => match(filter, action, label));
return filters.some(filter => match(filter, action, label));
}
function measureTiming(action, label) {
@ -209,11 +245,23 @@ this.analytics = (function() {
} else if (timingData[r.name] && match(r.end, action, label)) {
let endTime = Date.now();
let elapsed = endTime - timingData[r.name];
catcher.watchPromise(sendTiming("perf-response-time", r.name, elapsed), true);
sendTiming("perf-response-time", r.name, elapsed);
delete timingData[r.name];
}
});
}
function fetchWatcher(request) {
catcher.watchPromise(
request.then(response => {
if (!response.ok) {
throw new Error(`Bad response from ${request.url}: ${response.status} ${response.statusText}`);
}
return response;
}),
true
);
}
return exports;
})();

Просмотреть файл

@ -176,28 +176,6 @@ this.auth = (function() {
return registrationInfo.registered;
};
exports.setDeviceInfoFromOldAddon = function(newDeviceInfo) {
return registrationInfoFetched.then(() => {
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) => {
return registrationInfoFetched.then(() => {
return exports.authHeaders();

Просмотреть файл

@ -63,7 +63,6 @@ this.communication = (function() {
};
function isBootstrapMissingError(error) {
// Note: some of this logic is copied into startBackground.js's getOldDeviceInfo call
if (!error) {
return false;
}

Просмотреть файл

@ -80,7 +80,7 @@ this.main = (function() {
return selectorLoader.testIfLoaded(tab.id);
}).then((isLoaded) => {
if (!isLoaded) {
sendEvent("start-shot", "site-request");
sendEvent("start-shot", "site-request", {incognito: tab.incognito});
setIconActive(true, tab.id);
selectorLoader.toggle(tab.id, false);
}
@ -96,13 +96,13 @@ this.main = (function() {
if (shouldOpenMyShots(tab.url)) {
if (!hasSeenOnboarding) {
catcher.watchPromise(analytics.refreshTelemetryPref().then(() => {
sendEvent("goto-onboarding", "selection-button");
sendEvent("goto-onboarding", "selection-button", {incognito: tab.incognito});
return forceOnboarding();
}));
return;
}
catcher.watchPromise(analytics.refreshTelemetryPref().then(() => {
sendEvent("goto-myshots", "about-newtab");
sendEvent("goto-myshots", "about-newtab", {incognito: tab.incognito});
}));
catcher.watchPromise(
auth.authHeaders()
@ -112,10 +112,10 @@ this.main = (function() {
toggleSelector(tab)
.then(active => {
const event = active ? "start-shot" : "cancel-shot";
sendEvent(event, "toolbar-button");
sendEvent(event, "toolbar-button", {incognito: tab.incognito});
}, (error) => {
if ((!hasSeenOnboarding) && error.popupMessage == "UNSHOOTABLE_PAGE") {
sendEvent("goto-onboarding", "selection-button");
sendEvent("goto-onboarding", "selection-button", {incognito: tab.incognito});
return forceOnboarding();
}
throw error;
@ -140,7 +140,7 @@ this.main = (function() {
}
catcher.watchPromise(
toggleSelector(tab)
.then(() => sendEvent("start-shot", "context-menu")));
.then(() => sendEvent("start-shot", "context-menu", {incognito: tab.incognito})));
});
function urlEnabled(url) {
@ -209,7 +209,7 @@ this.main = (function() {
return blobConverters.blobToArray(blob).then(buffer => {
return browser.clipboard.setImageData(
buffer, blob.type.split("/", 2)[1]).then(() => {
catcher.watchPromise(communication.sendToBootstrap('incrementCopyCount'));
catcher.watchPromise(communication.sendToBootstrap("incrementCount", {scalar: "copy"}));
return browser.notifications.create({
type: "basic",
iconUrl: "../icons/copy.png",
@ -236,13 +236,11 @@ this.main = (function() {
}
});
browser.downloads.onChanged.addListener(onChangedCallback)
catcher.watchPromise(communication.sendToBootstrap("incrementDownloadCount"));
catcher.watchPromise(communication.sendToBootstrap("incrementCount", {scalar: "download"}));
return browser.windows.getLastFocused().then(windowInfo => {
return windowInfo.incognito;
}).then((incognito) => {
return browser.downloads.download({
url,
incognito,
incognito: windowInfo.incognito,
filename: info.filename
}).then((id) => {
downloadId = id;
@ -254,23 +252,6 @@ this.main = (function() {
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}));
@ -281,8 +262,7 @@ this.main = (function() {
});
});
communication.register("abortFrameset", () => {
sendEvent("abort-start-shot", "frame-page");
communication.register("abortStartShot", () => {
// 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({
@ -290,37 +270,11 @@ this.main = (function() {
});
});
communication.register("abortNoDocumentBody", (sender, tagName) => {
tagName = String(tagName || "").replace(/[^a-z0-9]/ig, "");
sendEvent("abort-start-shot", `document-is-${tagName}`);
// 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"
});
});
// Note: this signal is only needed until bug 1357589 is fixed.
communication.register("openTermsPage", () => {
return catcher.watchPromise(browser.tabs.create({url: "https://www.mozilla.org/about/legal/terms/services/"}));
});
// Note: this signal is also only needed until bug 1357589 is fixed.
communication.register("openPrivacyPage", () => {
return catcher.watchPromise(browser.tabs.create({url: "https://www.mozilla.org/privacy/firefox-cloud/"}));
});
// A Screenshots page wants us to start/force onboarding
communication.register("requestOnboarding", (sender) => {
return startSelectionWithOnboarding(sender.tab);
});
communication.register("isHistoryEnabled", () => {
return catcher.watchPromise(communication.sendToBootstrap("getHistoryPref").then(historyEnabled => {
return historyEnabled;
}));
});
communication.register("getPlatformOs", () => {
return catcher.watchPromise(browser.runtime.getPlatformInfo().then(platformInfo => {
return platformInfo.os;

Просмотреть файл

@ -88,15 +88,17 @@ this.selectorLoader = (function() {
// TODO: since bootstrap communication is now required, would this function
// make more sense inside background/main?
function downloadOnlyCheck(tabId) {
return communication.sendToBootstrap("getHistoryPref").then((historyEnabled) => {
return browser.tabs.get(tabId).then(tab => {
let downloadOnly = !historyEnabled || tab.incognito;
return browser.tabs.executeScript(tabId, {
// Note: `window` here refers to a global accessible to content
// scripts, but not the scripts in the underlying page. For more
// details, see https://mdn.io/WebExtensions/Content_scripts#Content_script_environment
code: `window.downloadOnly = ${downloadOnly}`,
runAt: "document_start"
return communication.sendToBootstrap("isHistoryEnabled").then((historyEnabled) => {
return communication.sendToBootstrap("isUploadDisabled").then((uploadDisabled) => {
return browser.tabs.get(tabId).then(tab => {
let downloadOnly = !historyEnabled || uploadDisabled || tab.incognito;
return browser.tabs.executeScript(tabId, {
// Note: `window` here refers to a global accessible to content
// scripts, but not the scripts in the underlying page. For more
// details, see https://mdn.io/WebExtensions/Content_scripts#Content_script_environment
code: `window.downloadOnly = ${downloadOnly}`,
runAt: "document_start"
});
});
});
});

Просмотреть файл

@ -88,7 +88,7 @@ this.senderror = (function() {
};
exports.reportError = function(e) {
if (!analytics.getTelemetryPrefSync()) {
if (!analytics.isTelemetryEnabled()) {
log.error("Telemetry disabled. Not sending critical error:", e);
return;
}

Просмотреть файл

@ -22,15 +22,13 @@ this.startBackground = (function() {
"background/senderror.js",
"build/raven.js",
"build/shot.js",
"build/thumbnailGenerator.js",
"background/analytics.js",
"background/deviceInfo.js",
"background/takeshot.js",
"background/main.js"
];
// Maximum milliseconds to wait before checking for migration possibility
const CHECK_MIGRATION_DELAY = 2000;
browser.contextMenus.create({
id: "create-screenshot",
title: browser.i18n.getMessage("contextMenuLabel"),
@ -58,30 +56,6 @@ this.startBackground = (function() {
let photonPageActionPort = null;
initPhotonPageAction();
// We delay this check (by CHECK_MIGRATION_DELAY) just to avoid piling too
// many things onto browser/add-on startup
requestIdleCallback(() => {
browser.runtime.sendMessage({funcName: "getOldDeviceInfo"}).then((result) => {
if (result && result.type == "success" && result.value) {
// There is a possible migration to run, so we'll load the entire background
// page and continue the process
return loadIfNecessary();
}
if (!result) {
throw new Error("Got no result from getOldDeviceInfo");
}
if (result.type == "error") {
throw new Error(`Error from getOldDeviceInfo: ${result.name}`);
}
}).catch((error) => {
if (error && error.message == "Could not establish connection. Receiving end does not exist") {
// Just a missing bootstrap.js, ignore
} else {
console.error("Screenshots error checking for Page Shot migration:", error);
}
});
}, {timeout: CHECK_MIGRATION_DELAY});
let loadedPromise;
function loadIfNecessary() {

Просмотреть файл

@ -1,4 +1,4 @@
/* globals communication, shot, main, auth, catcher, analytics, buildSettings, blobConverters */
/* globals communication, shot, main, auth, catcher, analytics, buildSettings, blobConverters, thumbnailGenerator */
"use strict";
@ -13,6 +13,7 @@ this.takeshot = (function() {
shot.favicon = sender.tab.favIconUrl;
let capturePromise = Promise.resolve();
let openedTab;
let thumbnailBlob;
if (!shot.clipNames().length) {
// canvas.drawWindow isn't available, so we fall back to captureVisibleTab
capturePromise = screenshotPage(selectedPos, scroll).then((dataUrl) => {
@ -33,8 +34,9 @@ this.takeshot = (function() {
}
let convertBlobPromise = Promise.resolve();
if (buildSettings.uploadBinary && !imageBlob) {
imageBlob = blobConverters.dataUrlToBlob(shot.getClip(shot.clipNames()[0]).image.url);
shot.getClip(shot.clipNames()[0]).image.url = "";
let clipImage = shot.getClip(shot.clipNames()[0]).image;
imageBlob = blobConverters.dataUrlToBlob(clipImage.url);
clipImage.url = "";
} else if (!buildSettings.uploadBinary && imageBlob) {
convertBlobPromise = blobConverters.blobToDataUrl(imageBlob).then((dataUrl) => {
shot.getClip(shot.clipNames()[0]).image.url = dataUrl;
@ -53,14 +55,26 @@ this.takeshot = (function() {
}
return catcher.watchPromise(capturePromise.then(() => {
return convertBlobPromise;
}).then(() => {
if (buildSettings.uploadBinary) {
let blobToUrlPromise = blobConverters.blobToDataUrl(imageBlob);
return thumbnailGenerator.createThumbnailBlobFromPromise(shot, blobToUrlPromise);
}
return thumbnailGenerator.createThumbnailUrl(shot);
}).then((thumbnailImage) => {
if (buildSettings.uploadBinary) {
thumbnailBlob = thumbnailImage;
} else {
shot.thumbnail = thumbnailImage;
}
}).then(() => {
return browser.tabs.create({url: shot.creatingUrl})
}).then((tab) => {
openedTab = tab;
sendEvent('internal', 'open-shot-tab');
return uploadShot(shot, imageBlob);
return uploadShot(shot, imageBlob, thumbnailBlob);
}).then(() => {
return browser.tabs.update(openedTab.id, {url: shot.viewUrl}).then(
return browser.tabs.update(openedTab.id, {url: shot.viewUrl, loadReplace: true}).then(
null,
(error) => {
// FIXME: If https://bugzilla.mozilla.org/show_bug.cgi?id=1365718 is resolved,
@ -73,7 +87,7 @@ this.takeshot = (function() {
}
);
}).then(() => {
catcher.watchPromise(communication.sendToBootstrap('incrementUploadCount'));
catcher.watchPromise(communication.sendToBootstrap("incrementCount", {scalar: "upload"}));
return shot.viewUrl;
}).catch((error) => {
browser.tabs.remove(openedTab.id);
@ -131,29 +145,43 @@ this.takeshot = (function() {
}
/** Creates a multipart TypedArray, given {name: value} fields
and {name: blob} files
and a files array in the format of
[{fieldName: "NAME", filename: "NAME.png", blob: fileBlob}, {...}, ...]
Returns {body, "content-type"}
*/
function createMultipart(fields, fileField, fileFilename, blob) {
function createMultipart(fields, files) {
let boundary = "---------------------------ScreenshotBoundary" + Date.now();
return blobConverters.blobToArray(blob).then((blobAsBuffer) => {
let body = [];
for (let name in fields) {
body.push("--" + boundary);
body.push(`Content-Disposition: form-data; name="${name}"`);
body.push("");
body.push(fields[name]);
}
let body = [];
for (let name in fields) {
body.push("--" + boundary);
body.push(`Content-Disposition: form-data; name="${fileField}"; filename="${fileFilename}"`);
body.push(`Content-Type: ${blob.type}`);
body.push(`Content-Disposition: form-data; name="${name}"`);
body.push("");
body.push("");
body = body.join("\r\n");
let enc = new TextEncoder("utf-8");
body = enc.encode(body);
body = concatBuffers(body.buffer, blobAsBuffer);
body.push(fields[name]);
}
body.push("");
body = body.join("\r\n");
let enc = new TextEncoder("utf-8");
body = enc.encode(body).buffer;
let blobToArrayPromises = files.map(f => {
return blobConverters.blobToArray(f.blob);
});
return Promise.all(blobToArrayPromises).then(buffers => {
for (let i = 0; i < buffers.length; i++) {
let filePart = [];
filePart.push("--" + boundary);
filePart.push(`Content-Disposition: form-data; name="${files[i].fieldName}"; filename="${files[i].filename}"`);
filePart.push(`Content-Type: ${files[i].blob.type}`);
filePart.push("");
filePart.push("");
filePart = filePart.join("\r\n");
filePart = concatBuffers(enc.encode(filePart).buffer, buffers[i]);
body = concatBuffers(body, filePart);
body = concatBuffers(body, enc.encode("\r\n").buffer);
}
let tail = `\r\n--${boundary}--`;
tail = enc.encode(tail);
body = concatBuffers(body, tail.buffer);
@ -164,14 +192,18 @@ this.takeshot = (function() {
});
}
function uploadShot(shot, blob) {
function uploadShot(shot, blob, thumbnail) {
let headers;
return auth.authHeaders().then((_headers) => {
headers = _headers;
if (blob) {
let files = [ {fieldName: "blob", filename: "screenshot.png", blob} ];
if (thumbnail) {
files.push({fieldName: "thumbnail", filename: "thumbnail.png", blob: thumbnail});
}
return createMultipart(
{shot: JSON.stringify(shot.asJson())},
"blob", "screenshot.png", blob
files
);
}
return {

Просмотреть файл

@ -76,6 +76,8 @@ window.inlineSelectionCss = `
background-color: #ededf0; }
.button.share.active, .share.active.highlight-button-cancel, .share.active.highlight-button-save, .share.active.highlight-button-download, .share.active.highlight-button-copy, .share.active.preview-button-save, .button.share:active, .share.highlight-button-cancel:active, .share.highlight-button-save:active, .share.highlight-button-download:active, .share.highlight-button-copy:active, .share.preview-button-save:active {
background-color: #dedede; }
.button.share.newicon, .share.newicon.highlight-button-cancel, .share.newicon.highlight-button-save, .share.newicon.highlight-button-download, .share.newicon.highlight-button-copy, .share.newicon.preview-button-save {
background-image: url("../img/icon-share-alternate.svg"); }
.button.trash, .trash.highlight-button-cancel, .trash.highlight-button-save, .trash.highlight-button-download, .trash.highlight-button-copy, .trash.preview-button-save {
background-image: url("../img/icon-trash.svg"); }
.button.trash:hover, .trash.highlight-button-cancel:hover, .trash.highlight-button-save:hover, .trash.highlight-button-download:hover, .trash.highlight-button-copy:hover, .trash.preview-button-save:hover {
@ -480,49 +482,36 @@ window.inlineSelectionCss = `
left: 5px; }
.highlight-button-cancel {
background-image: url("MOZ_EXTENSION/icons/cancel.svg");
background-position: center center;
background-repeat: no-repeat;
background-size: 18px 18px;
border: 1px solid #dedede;
margin: 5px;
width: 40px; }
.highlight-button-save {
background-image: url("MOZ_EXTENSION/icons/cloud.svg");
background-repeat: no-repeat;
background-size: 20px 18px;
font-size: 18px;
margin: 5px;
min-width: 80px; }
html[dir="ltr"] .highlight-button-save {
background-position: 8px center; }
html[dir="rtl"] .highlight-button-save {
background-position: right 10px center; }
html[dir="ltr"] .highlight-button-save {
padding-left: 34px; }
html[dir="rtl"] .highlight-button-save {
padding-right: 40px; }
html[dir="ltr"] .highlight-button-save img {
padding-right: 8px; }
html[dir="rtl"] .highlight-button-save img {
padding-left: 8px; }
.highlight-button-download {
background-image: url("MOZ_EXTENSION/icons/download.svg");
background-position: center center;
background-repeat: no-repeat;
background-size: 18px 18px;
border: 1px solid #dedede;
display: block;
margin: 5px;
width: 40px; }
.highlight-button-download.download-only-button {
width: auto;
background-position: 7px;
padding-left: 32px; }
font-size: 18px;
width: auto; }
.highlight-button-download.download-only-button img {
height: 16px;
width: 16px; }
html[dir="ltr"] .highlight-button-download.download-only-button img {
padding-right: 8px; }
html[dir="rtl"] .highlight-button-download.download-only-button img {
padding-left: 8px; }
.highlight-button-copy {
background-image: url("MOZ_EXTENSION/icons/copy.svg");
background-position: center center;
background-repeat: no-repeat;
background-size: 18px 18px;
border: 1px solid #dedede;
display: block;
margin: 5px;
@ -542,8 +531,11 @@ window.inlineSelectionCss = `
align-items: center;
justify-content: center;
position: absolute;
right: 0;
top: -2px; }
html[dir="rtl"] .preview-buttons {
left: 0; }
html[dir="ltr"] .preview-buttons {
right: 0; }
.preview-image {
position: relative;
@ -554,7 +546,7 @@ window.inlineSelectionCss = `
animation-delay: 50ms;
animation: bounce-in 300ms forwards ease-in-out; }
.preview-image img {
.preview-image > img {
display: block;
width: auto;
height: auto;
@ -564,14 +556,13 @@ window.inlineSelectionCss = `
border: 1px solid rgba(255, 255, 255, 0.8); }
.preview-button-save {
background-image: url("MOZ_EXTENSION/icons/cloud.svg");
background-position: 8px center;
background-repeat: no-repeat;
background-size: 20px 18px;
font-size: 18px;
margin: 5px;
min-width: 80px;
padding-left: 34px; }
min-width: 80px; }
html[dir="ltr"] .preview-button-save img {
padding-right: 8px; }
html[dir="rtl"] .preview-button-save img {
padding-left: 8px; }
.fixed-container {
align-items: center;
@ -638,6 +629,20 @@ window.inlineSelectionCss = `
width: 400px;
user-select: none; }
.cancel-shot {
background-color: transparent;
cursor: pointer;
outline: none;
border-radius: 3px;
border: 1px #9b9b9b solid;
color: #fff;
cursor: pointer;
font-family: -apple-system, BlinkMacSystemFont, "segoe ui", "helvetica neue", helvetica, ubuntu, roboto, noto, arial, sans-serif;
font-size: 16px;
margin-top: 40px;
padding: 10px 25px;
pointer-events: all; }
.myshots-all-buttons-container {
display: flex;
flex-direction: row-reverse;

Просмотреть файл

@ -237,7 +237,7 @@ class AbstractShot {
this.openGraph = attrs.openGraph || null;
this.twitterCard = attrs.twitterCard || null;
this.documentSize = attrs.documentSize || null;
this.fullScreenThumbnail = attrs.fullScreenThumbnail || null;
this.thumbnail = attrs.thumbnail || null;
this.abTests = attrs.abTests || null;
this._clips = {};
if (attrs.clips) {
@ -247,9 +247,15 @@ class AbstractShot {
}
}
let isProd = typeof process !== "undefined" && process.env.NODE_ENV === "production";
for (let attr in attrs) {
if (attr !== "clips" && attr !== "id" && !this.REGULAR_ATTRS.includes(attr) && !this.DEPRECATED_ATTRS.includes(attr)) {
throw new Error("Unexpected attribute: " + attr);
if (isProd) {
console.warn("Unexpected attribute: " + attr);
} else {
throw new Error("Unexpected attribute: " + attr);
}
} else if (attr === "id") {
console.warn("passing id in attrs in AbstractShot constructor");
console.trace();
@ -569,16 +575,16 @@ class AbstractShot {
}
}
get fullScreenThumbnail() {
return this._fullScreenThumbnail;
get thumbnail() {
return this._thumbnail;
}
set fullScreenThumbnail(val) {
set thumbnail(val) {
assert(typeof val == "string" || !val);
if (val) {
assert(isUrl(val));
this._fullScreenThumbnail = val;
this._thumbnail = val;
} else {
this._fullScreenThumbnail = null;
this._thumbnail = null;
}
}
@ -603,18 +609,19 @@ class AbstractShot {
AbstractShot.prototype.REGULAR_ATTRS = (`
origin fullUrl docTitle userTitle createdDate favicon images
siteName openGraph twitterCard documentSize
fullScreenThumbnail abTests
thumbnail 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
fullScreenThumbnail
`).split(/\s+/g);
AbstractShot.prototype.RECALL_ATTRS = (`
url docTitle userTitle createdDate favicon
openGraph twitterCard images fullScreenThumbnail
openGraph twitterCard images thumbnail
`).split(/\s+/g);
AbstractShot.prototype._OPENGRAPH_PROPERTIES = (`

Просмотреть файл

@ -0,0 +1,150 @@
this.thumbnailGenerator = (function () {let exports={}; // This is used in addon/webextension/background/takeshot.js,
// server/src/pages/shot/controller.js, and
// server/scr/pages/shotindex/view.js. It is used in a browser
// environment.
// Resize down 1/2 at a time produces better image quality.
// Not quite as good as using a third-party filter (which will be
// slower), but good enough.
const maxResizeScaleFactor = 0.5
// The shot will be scaled or cropped down to 210px on x, and cropped or
// scaled down to a maximum of 280px on y.
// x: 210
// y: <= 280
const maxThumbnailWidth = 210;
const maxThumbnailHeight = 280;
/**
* @param {int} imageHeight Height in pixels of the original image.
* @param {int} imageWidth Width in pixels of the original image.
* @returns {width, height, scaledX, scaledY}
*/
function getThumbnailDimensions(imageWidth, imageHeight) {
const displayAspectRatio = 3 / 4;
let imageAspectRatio = imageWidth / imageHeight;
let thumbnailImageWidth, thumbnailImageHeight;
let scaledX, scaledY;
if (imageAspectRatio > displayAspectRatio) {
// "Landscape" mode
// Scale on y, crop on x
let yScaleFactor = (imageHeight > maxThumbnailHeight) ? (maxThumbnailHeight / imageHeight) : 1.0;
thumbnailImageHeight = scaledY = Math.round(imageHeight * yScaleFactor);
scaledX = Math.round(imageWidth * yScaleFactor);
thumbnailImageWidth = Math.min(scaledX, maxThumbnailWidth);
} else {
// "Portrait" mode
// Scale on x, crop on y
let xScaleFactor = (imageWidth > maxThumbnailWidth) ? (maxThumbnailWidth / imageWidth) : 1.0;
thumbnailImageWidth = scaledX = Math.round(imageWidth * xScaleFactor);
scaledY = Math.round(imageHeight * xScaleFactor);
// The CSS could widen the image, in which case we crop more off of y.
thumbnailImageHeight = Math.min(scaledY, maxThumbnailHeight,
maxThumbnailHeight / (maxThumbnailWidth / imageWidth));
}
return {
width: thumbnailImageWidth,
height: thumbnailImageHeight,
scaledX,
scaledY
}
}
/**
* @param {dataUrl} String Data URL of the original image.
* @param {int} imageHeight Height in pixels of the original image.
* @param {int} imageWidth Width in pixels of the original image.
* @param {String} urlOrBlob 'blob' for a blob, otherwise data url.
* @returns A promise that resolves to the data URL or blob of the thumbnail image, or null.
*/
function createThumbnail(dataUrl, imageWidth, imageHeight, urlOrBlob) {
// There's cost associated with generating, transmitting, and storing
// thumbnails, so we'll opt out if the image size is below a certain threshold
const thumbnailThresholdFactor = 1.20;
const thumbnailWidthThreshold = maxThumbnailWidth * thumbnailThresholdFactor;
const thumbnailHeightThreshold = maxThumbnailHeight * thumbnailThresholdFactor;
if (imageWidth <= thumbnailWidthThreshold &&
imageHeight <= thumbnailHeightThreshold) {
// Do not create a thumbnail.
return Promise.resolve(null);
}
let thumbnailDimensions = getThumbnailDimensions(imageWidth, imageHeight);
return new Promise((resolve, reject) => {
let thumbnailImage = new Image();
let srcWidth = imageWidth;
let srcHeight = imageHeight;
let destWidth, destHeight;
thumbnailImage.onload = function() {
destWidth = Math.round(srcWidth * maxResizeScaleFactor);
destHeight = Math.round(srcHeight * maxResizeScaleFactor);
if (destWidth <= thumbnailDimensions.scaledX || destHeight <= thumbnailDimensions.scaledY) {
srcWidth = Math.round(srcWidth * (thumbnailDimensions.width / thumbnailDimensions.scaledX));
srcHeight = Math.round(srcHeight * (thumbnailDimensions.height / thumbnailDimensions.scaledY));
destWidth = thumbnailDimensions.width;
destHeight = thumbnailDimensions.height;
}
const thumbnailCanvas = document.createElement('canvas');
thumbnailCanvas.width = destWidth;
thumbnailCanvas.height = destHeight;
const ctx = thumbnailCanvas.getContext("2d");
ctx.imageSmoothingEnabled = false;
ctx.drawImage(
thumbnailImage,
0, 0, srcWidth, srcHeight,
0, 0, destWidth, destHeight);
if (thumbnailCanvas.width <= thumbnailDimensions.width ||
thumbnailCanvas.height <= thumbnailDimensions.height) {
if (urlOrBlob === "blob") {
thumbnailCanvas.toBlob((blob) => {
resolve(blob);
});
} else {
resolve(thumbnailCanvas.toDataURL("image/png"))
}
return;
}
srcWidth = destWidth;
srcHeight = destHeight;
thumbnailImage.src = thumbnailCanvas.toDataURL();
}
thumbnailImage.src = dataUrl;
});
}
function createThumbnailUrl(shot) {
const image = shot.getClip(shot.clipNames()[0]).image;
if (!image.url) {
return Promise.resolve(null);
}
return createThumbnail(
image.url, image.dimensions.x, image.dimensions.y, "dataurl");
}
function createThumbnailBlobFromPromise(shot, blobToUrlPromise) {
return blobToUrlPromise.then(dataUrl => {
const image = shot.getClip(shot.clipNames()[0]).image;
return createThumbnail(
dataUrl, image.dimensions.x, image.dimensions.y, "blob");
});
}
if (typeof exports != "undefined") {
exports.getThumbnailDimensions = getThumbnailDimensions;
exports.createThumbnailUrl = createThumbnailUrl;
exports.createThumbnailBlobFromPromise = createThumbnailBlobFromPromise;
}
return exports;
})();
null;

Просмотреть файл

@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Firefox Screenshots",
"version": "25.0.0",
"version": "29.0.0",
"description": "__MSG_addonDescription__",
"author": "__MSG_addonAuthorsList__",
"homepage_url": "https://github.com/mozilla-services/screenshots",

Просмотреть файл

@ -144,16 +144,6 @@ this.slides = (function() {
shooter.sendEvent("finish-slides", "done");
callbacks.onEnd();
})));
// Note: e10s breaks the terms and privacy anchor tags. Work around this by
// manually opening the correct URLs on click until bug 1357589 is fixed.
doc.querySelector("#terms").addEventListener("click", watchFunction(assertIsTrusted((event) => {
event.preventDefault();
callBackground("openTermsPage");
})));
doc.querySelector("#privacy").addEventListener("click", watchFunction(assertIsTrusted((event) => {
event.preventDefault();
callBackground("openPrivacyPage");
})));
doc.querySelector("#slide-overlay").addEventListener("click", watchFunction(assertIsTrusted((event) => {
if (event.target == doc.querySelector("#slide-overlay")) {
shooter.sendEvent("cancel-slides", "background-click");

Просмотреть файл

@ -60,11 +60,7 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
}
ui.iframe.hide();
try {
ctx.drawWindow(window, selectedPos.left, selectedPos.top, width, height, "#fff");
} finally {
ui.iframe.unhide();
}
ctx.drawWindow(window, selectedPos.left, selectedPos.top, width, height, "#fff");
let limit = buildSettings.pngToJpegCutoff;
let dataUrl = canvas.toDataURL();
if (limit && dataUrl.length > limit) {
@ -152,6 +148,8 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars
// The error has been signaled to the user, but unlike other errors (or
// success) we should not abort the selection
deactivateAfterFinish = false;
// We need to unhide the UI since screenshotPage() hides it.
ui.iframe.unhide();
return;
}
if (error.name != "BackgroundError") {
@ -165,8 +163,8 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars
}));
};
exports.downloadShot = function(selectedPos) {
let dataUrl = screenshotPage(selectedPos);
exports.downloadShot = function(selectedPos, previewDataUrl) {
let dataUrl = previewDataUrl || screenshotPage(selectedPos);
let promise = Promise.resolve(dataUrl);
if (!dataUrl) {
promise = callBackground(
@ -196,15 +194,41 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars
}));
};
exports.copyShot = function(selectedPos) {
let dataUrl = screenshotPage(selectedPos);
let copyInProgress = null;
exports.copyShot = function(selectedPos, previewDataUrl) {
// This is pretty slow. We'll ignore additional user triggered copy events
// while it is in progress.
if (copyInProgress) {
return;
}
// A max of five seconds in case some error occurs.
copyInProgress = setTimeout(() => {
copyInProgress = null;
}, 5000);
let unsetCopyInProgress = () => {
if (copyInProgress) {
clearTimeout(copyInProgress);
copyInProgress = null;
}
}
let dataUrl = previewDataUrl || screenshotPage(selectedPos);
let blob = blobConverters.dataUrlToBlob(dataUrl);
catcher.watchPromise(callBackground("copyShotToClipboard", blob).then(() => {
uicontrol.deactivate();
}));
unsetCopyInProgress();
}, unsetCopyInProgress));
};
exports.sendEvent = function(...args) {
let maybeOptions = args[args.length - 1];
if (typeof maybeOptions === "object") {
maybeOptions.incognito = browser.extension.inIncognitoContext;
} else {
args.push({incognito: browser.extension.inIncognitoContext});
}
callBackground("sendEvent", ...args);
};

Просмотреть файл

@ -1,4 +1,4 @@
/* globals log, util, catcher, inlineSelectionCss, callBackground, assertIsTrusted, assertIsBlankDocument, buildSettings */
/* globals log, util, catcher, inlineSelectionCss, callBackground, assertIsTrusted, assertIsBlankDocument, buildSettings blobConverters */
"use strict";
@ -8,34 +8,13 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
const { watchFunction } = catcher;
// The <body> 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"))) {
el.classList.contains("full-page") ||
el.classList.contains("cancel-shot"))) {
return true;
}
el = el.parentNode;
@ -68,11 +47,22 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
}
function localizeText(doc) {
let els = doc.querySelectorAll("[data-l10n-id]");
let els = doc.querySelectorAll("[data-l10n-id], [data-l10n-title]");
for (let el of els) {
let id = el.getAttribute("data-l10n-id");
let text = browser.i18n.getMessage(id);
el.textContent = text;
if (id) {
let text = browser.i18n.getMessage(id);
el.textContent = text;
}
let title = el.getAttribute("data-l10n-title");
if (title) {
let titleText = browser.i18n.getMessage(title);
let sanitized = titleText && titleText.replace("&", "&amp;")
.replace('"', "&quot;")
.replace("<", "&lt;")
.replace(">", "&gt;");
el.setAttribute("title", sanitized);
}
}
}
@ -105,6 +95,8 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
<ul>
<li data-l10n-id="downloadOnlyDetailsPrivate"></li>
<li data-l10n-id="downloadOnlyDetailsNeverRemember"></li>
<li data-l10n-id="downloadOnlyDetailsESR"></li>
<li data-l10n-id="downloadOnlyDetailsNoUploadPref"></li>
</ul>
</div>
<tbody>
@ -220,13 +212,15 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
this.sizeTracking.lastWidth = width;
this.element.style.width = width + "px";
// Since this frame has an absolute position relative to the parent
// document, if the parent document has a max-width that is narrower
// than the viewport, then the x of the parent document is not at 0 of
// the viewport. That makes the frame shifted to the right. This left
// margin negates that.
let boundingRect = document.body.getBoundingClientRect();
if (boundingRect.x) {
this.element.style.marginLeft = `-${boundingRect.x}px`;
// document, if the parent document's body has a relative position and
// left and/or top not at 0, then the left and/or top of the parent
// document's body is not at (0, 0) of the viewport. That makes the
// frame shifted relative to the viewport. These margins negates that.
if (window.getComputedStyle(document.body).position === "relative") {
let docBoundingRect = document.documentElement.getBoundingClientRect();
let bodyBoundingRect = document.body.getBoundingClientRect();
this.element.style.marginLeft = `-${bodyBoundingRect.right - docBoundingRect.right}px`;
this.element.style.marginTop = `-${bodyBoundingRect.bottom - docBoundingRect.bottom}px`;
}
}
if (force && visible) {
@ -302,13 +296,14 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
<div class="face"></div>
</div>
<div class="preview-instructions" data-l10n-id="screenshotInstructions"></div>
<button class="cancel-shot">${browser.i18n.getMessage("cancelScreenshot")}</button>
<div class="myshots-all-buttons-container">
${isDownloadOnly() ? '' : `
<button class="myshots-button myshots-link" tabindex="1" data-l10n-id="myShotsLink"></button>
<button class="myshots-button" tabindex="1" data-l10n-id="myShotsLink"></button>
<div class="spacer"></div>
`}
<button class="myshots-button visible" tabindex="2" data-l10n-id="saveScreenshotVisibleArea"></button>
<button class="myshots-button full-page" tabindex="3" data-l10n-id="saveScreenshotFullPage"></button>
<button class="visible" tabindex="2" data-l10n-id="saveScreenshotVisibleArea"></button>
<button class="full-page" tabindex="3" data-l10n-id="saveScreenshotFullPage"></button>
</div>
</div>
</div>
@ -329,6 +324,8 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
"click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onClickVisible)));
overlay.querySelector(".full-page").addEventListener(
"click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onClickFullPage)));
overlay.querySelector(".cancel-shot").addEventListener(
"click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onClickCancel)));
resolve();
}), {once: true});
document.body.appendChild(this.element);
@ -382,7 +379,11 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
function getAttributeText(l10nID) {
let text = browser.i18n.getMessage(l10nID);
return text && text.replace('"', "&quot;");
return text &&
text.replace("&", "&amp;")
.replace('"', "&quot;")
.replace("<", "&lt;")
.replace(">", "&gt;");
}
let iframePreview = exports.iframePreview = {
@ -410,18 +411,23 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
<div class="preview-image">
<div class="preview-buttons">
<button class="highlight-button-cancel"
title="${getAttributeText("cancelScreenshot")}"></button>
data-l10n-title="cancelScreenshot"><img
src="${browser.extension.getURL("icons/cancel.svg")}" /></button>
<button class="highlight-button-copy"
title="${getAttributeText("copyScreenshot")}"></button>
data-l10n-title="copyScreenshot"><img
src="${browser.extension.getURL("icons/copy.svg")}" /></button>
${isDownloadOnly() ?
`<button class="highlight-button-download download-only-button"
title="${getAttributeText("downloadScreenshot")}"
data-l10n-id="downloadScreenshot"></button>` :
data-l10n-title="downloadScreenshot"><img
src="${browser.extension.getURL("icons/download.svg")}"
/>${browser.i18n.getMessage("downloadScreenshot")}</button>` :
`<button class="highlight-button-download"
title="${getAttributeText("downloadScreenshot")}"></button>
data-l10n-title="downloadScreenshot"><img
src="${browser.extension.getURL("icons/download.svg")}" /></button>
<button class="preview-button-save"
title="${getAttributeText("saveScreenshotSelectedArea")}"
data-l10n-id="saveScreenshotSelectedArea"></button>`
data-l10n-title="saveScreenshotSelectedArea"><img
src="${browser.extension.getURL("icons/cloud.svg")}"
/>${browser.i18n.getMessage("saveScreenshotSelectedArea")}</button>`
}
</div>
</div>
@ -582,7 +588,6 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
} else {
this.copy.style.display = "none";
}
let bodyRect = getBodyRect();
let winBottom = window.innerHeight;
let pageYOffset = window.pageYOffset;
@ -606,25 +611,25 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
} else {
this.el.classList.remove("left-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.el.style.top = `${pos.top}px`;
this.el.style.left = `${pos.left}px`;
this.el.style.height = `${pos.bottom - pos.top}px`;
this.el.style.width = `${pos.right - pos.left}px`;
this.bgTop.style.top = "0px";
this.bgTop.style.height = (pos.top - bodyRect.top) + "px";
this.bgTop.style.height = `${pos.top}px`;
this.bgTop.style.left = "0px";
this.bgTop.style.width = "100%";
this.bgBottom.style.top = (pos.bottom - bodyRect.top) + "px";
this.bgBottom.style.top = `${pos.bottom}px`;
this.bgBottom.style.height = "100vh";
this.bgBottom.style.left = "0px";
this.bgBottom.style.width = "100%";
this.bgLeft.style.top = (pos.top - bodyRect.top) + "px";
this.bgLeft.style.height = pos.bottom - pos.top + "px";
this.bgLeft.style.top = `${pos.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.bgLeft.style.width = `${pos.left}px`;
this.bgRight.style.top = `${pos.top}px`;
this.bgRight.style.height = `${pos.bottom - pos.top}px`;
this.bgRight.style.left = `${pos.right}px`;
this.bgRight.style.width = "100%";
// the download notice is injected into an iframe that matches the document size
// in order to reposition it on scroll we need to bind an updated positioning
@ -644,27 +649,6 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
window.addEventListener('scroll', watchFunction(assertIsTrusted(this.windowChangeStop)));
window.addEventListener('resize', watchFunction(assertIsTrusted(this.windowChangeStop)));
}
if (!(this.isElementInViewport(this.buttons))) {
this.cancel.style.position = this.download.style.position = "fixed";
this.cancel.style.left = (pos.left - bodyRect.left - 50) + "px";
this.download.style.left = ((pos.left - bodyRect.left - 100)) + "px";
this.cancel.style.top = this.download.style.top = (pos.top - bodyRect.top) + "px";
if (this.save) {
this.save.style.position = "fixed";
this.save.style.left = ((pos.left - bodyRect.left) - 190) + "px";
this.save.style.top = (pos.top - bodyRect.top) + "px";
}
} else {
this.cancel.style.position = this.download.style.position = "initial";
this.cancel.style.top = this.download.style.top = 0;
this.cancel.style.left = this.download.style.left = 0;
if (this.save) {
this.save.style.position = "initial";
this.save.style.top = 0;
this.save.style.left = 0;
}
}
},
// used to eventually move the download-only warning
@ -700,31 +684,45 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
boxEl = makeEl("div", "highlight");
let buttons = makeEl("div", "highlight-buttons");
let cancel = makeEl("button", "highlight-button-cancel");
let cancelImg = makeEl("img");
cancelImg.src = browser.extension.getURL("icons/cancel.svg");
cancel.title = browser.i18n.getMessage("cancelScreenshot");
cancel.appendChild(cancelImg);
buttons.appendChild(cancel);
let copy = makeEl("button", "highlight-button-copy");
copy.title = browser.i18n.getMessage("copyScreenshot");
let copyImg = makeEl("img");
copyImg.src = browser.extension.getURL("icons/copy.svg");
copy.appendChild(copyImg);
buttons.appendChild(copy);
let download, save;
if (isDownloadOnly()) {
download = makeEl("button", "highlight-button-download download-only-button");
let downloadImg = makeEl("img");
downloadImg.src = browser.extension.getURL("icons/download.svg");
download.appendChild(downloadImg);
download.append(browser.i18n.getMessage("downloadScreenshot"));
download.title = browser.i18n.getMessage("downloadScreenshot");
download.textContent = browser.i18n.getMessage("downloadScreenshot");
} else {
download = makeEl("button", "highlight-button-download");
download.title = browser.i18n.getMessage("downloadScreenshot");
let downloadImg = makeEl("img");
downloadImg.src = browser.extension.getURL("icons/download.svg");
download.appendChild(downloadImg);
save = makeEl("button", "highlight-button-save");
save.textContent = browser.i18n.getMessage("saveScreenshotSelectedArea");
let saveImg = makeEl("img");
saveImg.src = browser.extension.getURL("icons/cloud.svg");
save.appendChild(saveImg);
save.append(browser.i18n.getMessage("saveScreenshotSelectedArea"));
save.title = browser.i18n.getMessage("saveScreenshotSelectedArea");
}
buttons.appendChild(download);
if (save) {
buttons.appendChild(save);
}
this.buttons = buttons;
this.cancel = cancel;
this.download = download;
this.copy = copy;
@ -793,11 +791,6 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
return false;
},
isElementInViewport(el) {
let rect = el.getBoundingClientRect();
return (rect.right <= window.innerWidth);
},
clearSaveDisabled() {
if (!this.save) {
// Happens if we try to remove the disabled status after the worker
@ -872,7 +865,8 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
exports.Preview = {
display(dataUrl, showCropWarning) {
let img = makeEl("IMG");
img.src = dataUrl;
let imgBlob = blobConverters.dataUrlToBlob(dataUrl);
img.src = URL.createObjectURL(imgBlob);
iframe.document().querySelector(".preview-image").appendChild(img);
if (showCropWarning && !(isDownloadOnly())) {
let imageCroppedEl = makeEl("table", "notice");

Просмотреть файл

@ -124,6 +124,15 @@ this.uicontrol = (function() {
let captureType;
function removeDimensionLimitsOnFullPageShot() {
if (captureType === "fullPageTruncated") {
captureType = "fullPage";
selectedPos = new Selection(
0, 0,
getDocumentWidth(), getDocumentHeight());
}
}
let standardDisplayCallbacks = {
cancel: () => {
sendEvent("cancel-shot", "overlay-cancel-button");
@ -145,6 +154,12 @@ this.uicontrol = (function() {
sendEvent("cancel-shot", "cancel-preview-button");
exports.deactivate();
},
onClickCancel: e => {
sendEvent("cancel-shot", "cancel-selection-button");
e.preventDefault();
e.stopPropagation();
exports.deactivate();
},
onOpenMyShots: () => {
sendEvent("goto-myshots", "selection-button");
callBackground("openMyShots")
@ -185,20 +200,17 @@ this.uicontrol = (function() {
},
onDownloadPreview: () => {
sendEvent(`download-${captureType.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}`, "download-preview-button");
// Downloaded shots don't have dimension limits
if (captureType === "fullPageTruncated") {
captureType = "fullPage";
selectedPos = new Selection(
0, 0,
getDocumentWidth(), getDocumentHeight());
}
shooter.downloadShot(selectedPos);
let previewDataUrl = (captureType === "fullPageTruncated") ? null : dataUrl;
removeDimensionLimitsOnFullPageShot();
shooter.downloadShot(selectedPos, previewDataUrl);
},
onCopyPreview: () => {
sendEvent(`copy-${captureType.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}`, "copy-preview-button");
shooter.copyShot(selectedPos);
// Copied shots don't have dimension limits
let previewDataUrl = (captureType === "fullPageTruncated") ? null : dataUrl;
removeDimensionLimitsOnFullPageShot();
shooter.copyShot(selectedPos, previewDataUrl);
}
};
@ -368,7 +380,7 @@ this.uicontrol = (function() {
}
distanceTo(x, y) {
return Math.sqrt(Math.pow(this.x - x, 2), Math.pow(this.y - y));
return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));
}
}
@ -416,9 +428,12 @@ this.uicontrol = (function() {
watchPromise(ui.iframe.display(installHandlersOnDocument, standardOverlayCallbacks).then(() => {
ui.iframe.usePreSelection();
ui.Box.remove();
const handler = watchFunction(assertIsTrusted(keyupHandler));
document.addEventListener("keyup", handler);
registeredDocumentHandlers.push({name: "keyup", doc: document, handler, useCapture: false});
const upHandler = watchFunction(assertIsTrusted(keyupHandler));
document.addEventListener("keyup", upHandler);
registeredDocumentHandlers.push({name: "keyup", doc: document, upHandler, useCapture: false});
const downHandler = watchFunction(assertIsTrusted(keydownHandler));
document.addEventListener("keydown", downHandler);
registeredDocumentHandlers.push({name: "keydown", doc: document, downHandler, useCapture: false});
}));
},
@ -428,6 +443,9 @@ this.uicontrol = (function() {
(!event.target.classList.contains("preview-overlay"))) {
// User is hovering over a toolbar button or control
autoDetectRect = null;
if (this.cachedEl) {
this.cachedEl = null;
}
ui.HoverBox.hide();
return;
}
@ -870,12 +888,15 @@ this.uicontrol = (function() {
exports.activate = function() {
if (!document.body) {
callBackground("abortNoDocumentBody", document.documentElement.tagName);
callBackground("abortStartShot");
let tagName = String(document.documentElement.tagName || "").replace(/[^a-z0-9]/ig, "");
sendEvent("abort-start-shot", `document-is-${tagName}`);
selectorLoader.unloadModules();
return;
}
if (isFrameset()) {
callBackground("abortFrameset");
callBackground("abortStartShot");
sendEvent("abort-start-shot", "frame-page");
selectorLoader.unloadModules();
return;
}
@ -940,6 +961,7 @@ this.uicontrol = (function() {
primedDocumentHandlers.set(eventName, fn);
});
primedDocumentHandlers.set("keyup", watchFunction(assertIsTrusted(keyupHandler)));
primedDocumentHandlers.set("keydown", watchFunction(assertIsTrusted(keydownHandler)));
window.addEventListener('beforeunload', beforeunloadHandler);
}
@ -965,11 +987,8 @@ this.uicontrol = (function() {
exports.deactivate();
}
function keyupHandler(event) {
if (event.shiftKey || event.altKey) {
// unused modifier keys
return;
}
function keydownHandler(event) {
// In MacOS, the keyup event for 'c' is not fired when performing cmd+c.
if (event.code === "KeyC" && (event.ctrlKey || event.metaKey)) {
callBackground("getPlatformOs").then(os => {
if ((event.ctrlKey && os !== "mac") ||
@ -981,11 +1000,22 @@ this.uicontrol = (function() {
// handled by catcher.watchPromise
});
}
}
function keyupHandler(event) {
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) {
// unused modifier keys
return;
}
if ((event.key || event.code) === "Escape") {
sendEvent("cancel-shot", "keyboard-escape");
exports.deactivate();
}
if ((event.key || event.code) === "Enter" && getState.state === "selected") {
// Enter to trigger Save or Download by default. But if the user tabbed to
// select another button, then we do not want this.
if ((event.key || event.code) === "Enter"
&& getState.state === "selected"
&& ui.iframe.document().activeElement.tagName === "BODY") {
if (ui.isDownloadOnly()) {
sendEvent("download-shot", "keyboard-enter");
shooter.downloadShot(selectedPos);

Просмотреть файл

@ -58,7 +58,7 @@ this.sitehelper = (function() {
let shotId = event.detail;
catcher.watchPromise(callBackground("getAuthInfo", shotId || null).then((info) => {
sendBackupCookieRequest(info.authHeaders);
sendCustomEvent("login-successful", {deviceId: info.deviceId, isOwner: info.isOwner});
sendCustomEvent("login-successful", {deviceId: info.deviceId, isOwner: info.isOwner, backupCookieRequest: true});
}));
}));

Просмотреть файл

@ -187,7 +187,7 @@ this.PreferenceExperiments = {
// if not, stop the experiment, and skip the remaining steps
log.info(`Stopping experiment "${experiment.name}" because its value changed`);
await this.stop(experiment.name, {
didResetValue: false,
resetValue: false,
reason: "user-preference-changed-sideload",
});
continue;
@ -466,7 +466,7 @@ this.PreferenceExperiments = {
* experiment has already expired.
*/
async stop(experimentName, {resetValue = true, reason = "unknown"} = {}) {
log.debug(`PreferenceExperiments.stop(${experimentName})`);
log.debug(`PreferenceExperiments.stop(${experimentName}, {resetValue: ${resetValue}, reason: ${reason}})`);
if (reason === "unknown") {
log.warn(`experiment ${experimentName} ending for unknown reason`);
}

Просмотреть файл

@ -794,8 +794,7 @@ decorate_task(
withMockExperiments,
withMockPreferences,
withStub(PreferenceExperiments, "stop"),
withStub(TelemetryEvents, "sendEvent"),
async function testInitChanges(experiments, mockPreferences, stopStub, sendEventStub) {
async function testInitChanges(experiments, mockPreferences, stopStub) {
mockPreferences.set("fake.preference", "experiment value", "default");
experiments.test = experimentFactory({
name: "test",
@ -804,8 +803,17 @@ decorate_task(
});
mockPreferences.set("fake.preference", "changed value");
await PreferenceExperiments.init();
ok(stopStub.calledWith("test"), "Experiment is stopped because value changed");
is(Preferences.get("fake.preference"), "changed value", "Preference value was not changed");
Assert.deepEqual(
stopStub.getCall(0).args,
["test", {
resetValue: false,
reason: "user-preference-changed-sideload",
}],
"Experiment is stopped correctly because value changed"
);
},
);
@ -1006,3 +1014,32 @@ decorate_task(
);
}
);
// stop should pass along the value for resetValue to Telemetry Events as didResetValue
decorate_task(
withMockExperiments,
withMockPreferences,
withStub(PreferenceExperiments, "stopObserver"),
withStub(TelemetryEvents, "sendEvent"),
async function testStopResetValue(experiments, mockPreferences, stopObserverStub, sendEventStub) {
mockPreferences.set("fake.preference1", "default value", "default");
experiments.test1 = experimentFactory({ name: "test1", preferenceName: "fake.preference1" });
await PreferenceExperiments.stop("test1", {resetValue: true});
is(sendEventStub.callCount, 1);
is(
sendEventStub.getCall(0).args[3].didResetValue,
"true",
"PreferenceExperiments.stop() should pass true values of resetValue as didResetValue",
);
mockPreferences.set("fake.preference2", "default value", "default");
experiments.test2 = experimentFactory({ name: "test2", preferenceName: "fake.preference2" });
await PreferenceExperiments.stop("test2", {resetValue: false});
is(sendEventStub.callCount, 2);
is(
sendEventStub.getCall(1).args[3].didResetValue,
"false",
"PreferenceExperiments.stop() should pass false values of resetValue as didResetValue",
);
}
);

Просмотреть файл

@ -11,6 +11,9 @@ createEnum([
// Update the extension sidebar with an object TreeView.
"EXTENSION_SIDEBAR_OBJECT_TREEVIEW_UPDATE",
// Update the extension sidebar with an object value grip preview.
"EXTENSION_SIDEBAR_OBJECT_GRIP_VIEW_UPDATE",
// Remove an extension sidebar from the inspector store.
"EXTENSION_SIDEBAR_REMOVE"

Просмотреть файл

@ -6,6 +6,7 @@
const {
EXTENSION_SIDEBAR_OBJECT_TREEVIEW_UPDATE,
EXTENSION_SIDEBAR_OBJECT_GRIP_VIEW_UPDATE,
EXTENSION_SIDEBAR_REMOVE,
} = require("./index");
@ -22,6 +23,18 @@ module.exports = {
};
},
/**
* Update the sidebar with an object actor preview.
*/
updateObjectValueGripView(sidebarId, objectValueGrip, rootTitle) {
return {
type: EXTENSION_SIDEBAR_OBJECT_GRIP_VIEW_UPDATE,
sidebarId,
objectValueGrip,
rootTitle,
};
},
/**
* Remove the extension sidebar from the inspector store.
*/

Просмотреть файл

@ -10,13 +10,17 @@ const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const ObjectTreeView = createFactory(require("./ObjectTreeView"));
const ObjectValueGripView = createFactory(require("./ObjectValueGripView"));
const Types = require("../types");
/**
* The ExtensionSidebar is a React component with 2 supported viewMode:
* - an ObjectTreeView UI, used to show the JS objects (used by the sidebar.setObject
* and sidebar.setExpression WebExtensions APIs)
* - an ExtensionPage UI used to show an extension page (used by the sidebar.setPage
* WebExtensions APIs).
* - an ObjectTreeView UI, used to show the JS objects
* (used by the sidebar.setObject WebExtensions APIs)
* - an ObjectValueGripView UI, used to show the objects value grips
* (used by sidebar.setExpression WebExtensions APIs)
* - an ExtensionPage UI used to show an extension page
* (used by the sidebar.setPage WebExtensions APIs).
*
* TODO: implement the ExtensionPage viewMode.
*/
@ -25,15 +29,23 @@ class ExtensionSidebar extends PureComponent {
return {
id: PropTypes.string.isRequired,
extensionsSidebar: PropTypes.object.isRequired,
// Helpers injected as props by extension-sidebar.js.
serviceContainer: PropTypes.shape(Types.serviceContainer).isRequired,
};
}
render() {
const { id, extensionsSidebar } = this.props;
const {
id,
extensionsSidebar,
serviceContainer,
} = this.props;
let {
viewMode = "empty-sidebar",
object
object,
objectValueGrip,
rootTitle
} = extensionsSidebar[id] || {};
let sidebarContentEl;
@ -42,6 +54,13 @@ class ExtensionSidebar extends PureComponent {
case "object-treeview":
sidebarContentEl = ObjectTreeView({ object });
break;
case "object-value-grip-view":
sidebarContentEl = ObjectValueGripView({
objectValueGrip,
serviceContainer,
rootTitle,
});
break;
case "empty-sidebar":
break;
default:

Просмотреть файл

@ -0,0 +1,88 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const Accordion = createFactory(require("devtools/client/inspector/layout/components/Accordion"));
const reps = require("devtools/client/shared/components/reps/reps");
const Types = require("../types");
const { REPS, MODE } = reps;
const { Grip } = REPS;
const ObjectInspector = createFactory(reps.ObjectInspector);
class ObjectValueGripView extends PureComponent {
static get propTypes() {
return {
rootTitle: PropTypes.string,
objectValueGrip: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.object,
]).isRequired,
// Helpers injected as props by extension-sidebar.js.
serviceContainer: PropTypes.shape(Types.serviceContainer).isRequired,
};
}
render() {
const {
objectValueGrip,
serviceContainer,
rootTitle,
} = this.props;
const objectInspectorProps = {
autoExpandDepth: 1,
mode: MODE.SHORT,
// TODO: we disable focus since it's not currently working well in ObjectInspector.
// Let's remove the property below when problem are fixed in OI.
disabledFocus: true,
roots: [{
path: objectValueGrip && objectValueGrip.actor || JSON.stringify(objectValueGrip),
contents: {
value: objectValueGrip,
}
}],
createObjectClient: serviceContainer.createObjectClient,
releaseActor: serviceContainer.releaseActor,
// TODO: evaluate if there should also be a serviceContainer.openLink.
};
if (objectValueGrip && objectValueGrip.actor) {
Object.assign(objectInspectorProps, {
onDOMNodeMouseOver: serviceContainer.highlightDomElement,
onDOMNodeMouseOut: serviceContainer.unHighlightDomElement,
onInspectIconClick(object, e) {
// Stop the event propagation so we don't trigger ObjectInspector
// expand/collapse.
e.stopPropagation();
serviceContainer.openNodeInInspector(object);
},
defaultRep: Grip,
});
}
if (rootTitle) {
return Accordion({
items: [
{
component: ObjectInspector,
componentProps: objectInspectorProps,
header: rootTitle,
opened: true,
},
],
});
}
return ObjectInspector(objectInspectorProps);
}
}
module.exports = ObjectValueGripView;

Просмотреть файл

@ -7,4 +7,5 @@
DevToolsModules(
'ExtensionSidebar.js',
'ObjectTreeView.js',
'ObjectValueGripView.js',
)

Просмотреть файл

@ -7,10 +7,12 @@
const { createElement, createFactory } = require("devtools/client/shared/vendor/react");
const { Provider } = require("devtools/client/shared/vendor/react-redux");
const ObjectClient = require("devtools/shared/client/object-client");
const ExtensionSidebarComponent = createFactory(require("./components/ExtensionSidebar"));
const {
updateObjectTreeView,
updateObjectValueGripView,
removeExtensionSidebar,
} = require("./actions/sidebar");
@ -52,6 +54,50 @@ class ExtensionSidebar {
title: this.title,
}, ExtensionSidebarComponent({
id: this.id,
serviceContainer: {
createObjectClient: (object) => {
return new ObjectClient(this.inspector.toolbox.target.client, object);
},
releaseActor: (actor) => {
if (!actor) {
return;
}
this.inspector.toolbox.target.client.release(actor);
},
highlightDomElement: (grip, options = {}) => {
const { highlighterUtils } = this.inspector.toolbox;
if (!highlighterUtils) {
return null;
}
return highlighterUtils.highlightDomValueGrip(grip, options);
},
unHighlightDomElement: (forceHide = false) => {
const { highlighterUtils } = this.inspector.toolbox;
if (!highlighterUtils) {
return null;
}
return highlighterUtils.unhighlight(forceHide);
},
openNodeInInspector: async (grip) => {
const { highlighterUtils } = this.inspector.toolbox;
if (!highlighterUtils) {
return null;
}
let front = await highlighterUtils.gripToNodeFront(grip);
let onInspectorUpdated = this.inspector.once("inspector-updated");
let onNodeFrontSet = this.inspector.toolbox.selection.setNodeFront(
front, "inspector-extension-sidebar"
);
return Promise.all([onNodeFrontSet, onInspectorUpdated]);
}
},
}));
}
@ -93,6 +139,19 @@ class ExtensionSidebar {
this.store.dispatch(updateObjectTreeView(this.id, object));
}
/**
* Dispatch an objectPreview action to change the SidebarComponent into an
* ObjectPreview React Component, which shows the passed value grip
* in the sidebar.
*/
setObjectValueGrip(objectValueGrip, rootTitle) {
if (this.removed) {
throw new Error("Unable to set an object preview on a removed ExtensionSidebar");
}
this.store.dispatch(updateObjectValueGripView(this.id, objectValueGrip, rootTitle));
}
}
module.exports = ExtensionSidebar;

Просмотреть файл

@ -12,6 +12,7 @@ DIRS += [
DevToolsModules(
'extension-sidebar.js',
'types.js',
)
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']

Просмотреть файл

@ -6,6 +6,7 @@
const {
EXTENSION_SIDEBAR_OBJECT_TREEVIEW_UPDATE,
EXTENSION_SIDEBAR_OBJECT_GRIP_VIEW_UPDATE,
EXTENSION_SIDEBAR_REMOVE,
} = require("../actions/index");
@ -24,6 +25,20 @@ let reducers = {
});
},
[EXTENSION_SIDEBAR_OBJECT_GRIP_VIEW_UPDATE](
sidebar, {sidebarId, objectValueGrip, rootTitle}
) {
// Update the sidebar to a "object-treeview" which shows
// the passed object.
return Object.assign({}, sidebar, {
[sidebarId]: {
viewMode: "object-value-grip-view",
objectValueGrip,
rootTitle,
}
});
},
[EXTENSION_SIDEBAR_REMOVE](sidebar, {sidebarId}) {
// Remove the sidebar from the Redux store.
delete sidebar[sidebarId];

Просмотреть файл

@ -3,6 +3,7 @@ tags = devtools
subsuite = devtools
support-files =
head.js
head_devtools_inspector_sidebar.js
!/devtools/client/commandline/test/helpers.js
!/devtools/client/framework/test/shared-head.js
!/devtools/client/inspector/test/head.js

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше