зеркало из https://github.com/mozilla/gecko-dev.git
merge mozilla-central to mozilla-inbound. CLOSED TREE
--HG-- extra : amend_source : 0e657a5dd4f6c8893d3f5ab7b173e3c4178e9e61
This commit is contained in:
Коммит
87f465d8f7
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 can’t 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 l’historique » est activé."
|
||||
},
|
||||
"downloadOnlyDetailsESR": {
|
||||
"message": "Vous utilisez Firefox ESR."
|
||||
},
|
||||
"downloadOnlyDetailsNoUploadPref": {
|
||||
"message": "L’envoi 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 l’opzione “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("&", "&")
|
||||
.replace('"', """)
|
||||
.replace("<", "<")
|
||||
.replace(">", ">");
|
||||
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('"', """);
|
||||
return text &&
|
||||
text.replace("&", "&")
|
||||
.replace('"', """)
|
||||
.replace("<", "<")
|
||||
.replace(">", ">");
|
||||
}
|
||||
|
||||
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
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче