Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-04-20 11:52:05 +02:00
commit 4d6108d41a
408 changed files with 15715 additions and 8595 deletions

View File

@ -11,7 +11,7 @@ origin uitour 1 https://www.mozilla.org
origin uitour 1 https://self-repair.mozilla.org
origin uitour 1 https://support.mozilla.org
origin uitour 1 https://addons.mozilla.org
origin uitour 1 https://services.addons.mozilla.org
origin uitour 1 https://discovery.addons.mozilla.org
origin uitour 1 about:home
# XPInstall

View File

@ -23,6 +23,10 @@ var gEMEHandler = {
Services.prefs.getPrefType("media.eme.clearkey.enabled") &&
!Services.prefs.getBoolPref("media.eme.clearkey.enabled")) {
Services.prefs.setBoolPref("media.eme.clearkey.enabled", true);
} else if (keySystem == "com.widevine.alpha" &&
Services.prefs.getPrefType("media.gmp-widevinecdm.enabled") &&
!Services.prefs.getBoolPref("media.gmp-widevinecdm.enabled")) {
Services.prefs.setBoolPref("media.gmp-widevinecdm.enabled", true);
}
}
browser.reload();

View File

@ -140,7 +140,12 @@
<panel type="autocomplete" id="PopupSearchAutoComplete" noautofocus="true" hidden="true"/>
<!-- for url bar autocomplete -->
<panel type="autocomplete-richlistbox" id="PopupAutoCompleteRichResult" noautofocus="true" hidden="true" flip="none">
<panel type="autocomplete-richlistbox"
id="PopupAutoCompleteRichResult"
noautofocus="true"
hidden="true"
flip="none"
level="parent">
#ifdef NIGHTLY_BUILD
<hbox id="urlbar-search-footer" flex="1" align="stretch" pack="end">
<button id="urlbar-search-settings" label="&changeSearchSettings.button;"

View File

@ -251,15 +251,32 @@ var AboutReaderListener = {
addEventListener("DOMContentLoaded", this, false);
addEventListener("pageshow", this, false);
addEventListener("pagehide", this, false);
addMessageListener("Reader:ParseDocument", this);
addMessageListener("Reader:ToggleReaderMode", this);
addMessageListener("Reader:PushState", this);
},
receiveMessage: function(message) {
switch (message.name) {
case "Reader:ParseDocument":
this._articlePromise = ReaderMode.parseDocument(content.document).catch(Cu.reportError);
content.document.location = "about:reader?url=" + encodeURIComponent(message.data.url);
case "Reader:ToggleReaderMode":
let url = content.document.location.href;
if (!this.isAboutReader) {
this._articlePromise = ReaderMode.parseDocument(content.document).catch(Cu.reportError);
content.document.location = "about:reader?url=" + encodeURIComponent(url);
} else {
let originalURL = ReaderMode.getOriginalUrl(url);
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
let sh = webNav.sessionHistory;
if (webNav.canGoBack) {
let prevEntry = sh.getEntryAtIndex(sh.index - 1, false);
let prevURL = prevEntry.URI.spec;
if (prevURL && (prevURL == originalURL || !originalURL)) {
webNav.goBack();
break;
}
}
content.document.location = originalURL;
}
break;
case "Reader:PushState":

View File

@ -13,7 +13,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=575561
<a href="http://example.org/browser/browser/base/content/test/general/dummy_page.html" target="foo">different domain (with target)</a>
<a href="http://www.example.com/browser/browser/base/content/test/general/dummy_page.html">same domain (www prefix)</a>
<a href="data:text/html,<!DOCTYPE html><html><body>Another Page</body></html>">data: URI</a>
<a href="about:mozilla">about: URI</a>
<iframe src="app_subframe_bug575561.html"></iframe>
</body>
</html>

View File

@ -39,7 +39,6 @@ add_task(function*() {
isnot(result, null, "Expect a keyword result");
let types = new Set(result.getAttribute("type").split(/\s+/));
Assert.ok(types.has("action"));
Assert.ok(types.has("keyword"));
is(result.getAttribute("actiontype"), "keyword", "Expect correct `actiontype` attribute");
is(result.getAttribute("title"), "example.com", "Expect correct title");

View File

@ -23,7 +23,7 @@ add_task(function* switchToTab() {
ok(gURLBar.popup.richlistbox.children.length > 1, "Should get at least 2 results");
let result = gURLBar.popup.richlistbox.children[1];
is(result.getAttribute("type"), "action switchtab", "Expect right type attribute");
is(result.getAttribute("type"), "switchtab", "Expect right type attribute");
is(result.label, "about:about about:about Tab", "Result a11y label should be: <title> <url> Tab");
gURLBar.popup.hidePopup();

View File

@ -30,7 +30,7 @@ add_task(function*() {
},
input: "tagtest1",
expected: {
type: "bookmark-tag",
type: "bookmark",
typeImageVisible: true,
},
}, {
@ -52,7 +52,7 @@ add_task(function*() {
},
input: "tagtest3",
expected: {
type: "bookmark-tag",
type: "bookmark",
typeImageVisible: true,
},
}, {
@ -63,7 +63,7 @@ add_task(function*() {
},
input: "* tagtest4",
expected: {
type: "bookmark-tag",
type: "bookmark",
typeImageVisible: true,
},
}, {

View File

@ -38,7 +38,7 @@ add_task(function* () {
is(gBrowser.tabs.length, initialTabsCount, "No additional tabs were opened.");
readerButton.click();
yield promiseTabLoadEvent(tab);
yield BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "pageshow");
// Ensure no new tabs are opened when exiting reader mode in a pinned tab
is(gBrowser.tabs.length, initialTabsCount, "No additional tabs were opened.");

View File

@ -38,7 +38,13 @@ add_task(function*() {
// Pinned: Link to an about: URI should not open a new tab
// Tests link to about:mozilla
yield testLink(6, true, false);
yield testLink(function(doc) {
let link = doc.createElement("a");
link.textContent = "Link to Mozilla";
link.href = "about:mozilla";
doc.body.appendChild(link);
return link;
}, true, false, false, "about:robots");
});
var waitForPageLoad = Task.async(function*(browser, linkLocation) {
@ -57,8 +63,8 @@ var waitForTabOpen = Task.async(function*() {
gBrowser.removeCurrentTab();
});
var testLink = Task.async(function*(aLinkIndex, pinTab, expectNewTab, testSubFrame) {
let appTab = gBrowser.addTab(TEST_URL, {skipAnimation: true});
var testLink = Task.async(function*(aLinkIndexOrFunction, pinTab, expectNewTab, testSubFrame, aURL = TEST_URL) {
let appTab = gBrowser.addTab(aURL, {skipAnimation: true});
if (pinTab)
gBrowser.pinTab(appTab);
gBrowser.selectedTab = appTab;
@ -69,7 +75,12 @@ var testLink = Task.async(function*(aLinkIndex, pinTab, expectNewTab, testSubFra
if (testSubFrame)
browser = browser.contentDocument.querySelector("iframe");
let link = browser.contentDocument.querySelectorAll("a")[aLinkIndex];
let link;
if (typeof aLinkIndexOrFunction == "function") {
link = aLinkIndexOrFunction(browser.contentDocument);
} else {
link = browser.contentDocument.querySelectorAll("a")[aLinkIndexOrFunction];
}
let promise;
if (expectNewTab)

View File

@ -4,9 +4,22 @@
function test() {
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab("data:text/html,<iframe width='700' height='700' src='about:certerror?e=nssBadCert&u='></iframe>");
gBrowser.selectedTab = gBrowser.addTab("data:text/html,<iframe width='700' height='700'></iframe>");
// Open a html page with about:certerror in an iframe
BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(testIframeCert);
BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(function() {
return ContentTask.spawn(gBrowser.selectedBrowser, "", function() {
return new Promise(resolve => {
info("Running content task");
let listener = e => {
removeEventListener('AboutNetErrorLoad', listener, false, true);
resolve();
};
addEventListener('AboutNetErrorLoad', listener, false, true);
let iframe = content.document.querySelector("iframe");
iframe.src = "https://expired.example.com/";
});
}).then(testIframeCert);
});
}
function testIframeCert(e) {

View File

@ -71,9 +71,11 @@ add_task(function* test_reader_button() {
// Switch page back out of reader mode.
readerButton.click();
yield promiseTabLoadEvent(tab);
yield BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "pageshow");
is(gBrowser.selectedBrowser.currentURI.spec, url,
"Original page loaded after clicking active reader mode button");
"Back to the original page after clicking active reader mode button");
ok(gBrowser.selectedBrowser.canGoForward,
"Moved one step back in the session history.");
// Load a new tab that is NOT reader-able.
let newTab = gBrowser.selectedTab = gBrowser.addTab();

View File

@ -40,7 +40,7 @@ add_task(function*() {
let result = gURLBar.popup.richlistbox.children[1];
isnot(result, null, "Expect a search result");
is(result.getAttribute("type"), "action searchengine favicon", "Expect correct `type` attribute");
is(result.getAttribute("type"), "searchengine", "Expect correct `type` attribute");
let titleHbox = result._titleText.parentNode.parentNode;
ok(titleHbox.classList.contains("ac-title"), "Title hbox sanity check");

View File

@ -87,11 +87,8 @@ function* checkInput(inputStr) {
Assert.equal(item.getAttribute("title"), inputStr.replace("\\","/"), "title");
Assert.equal(item.getAttribute("text"), inputStr, "text");
let itemTypeStr = item.getAttribute("type");
let itemTypes = itemTypeStr.split(" ").sort();
Assert.equal(itemTypes.toString(),
["action", "heuristic", "visiturl"].toString(),
"type");
let itemType = item.getAttribute("type");
Assert.equal(itemType, "visiturl");
Assert.equal(item._titleText.textContent, inputStr.replace("\\","/"), "Visible title");
Assert.equal(item._actionText.textContent, "Visit", "Visible action");

View File

@ -602,8 +602,6 @@ var FullZoomHelper = {
* The tab to load into.
* @param [optional] url
* The url to load, or the current url.
* @param [optional] event
* The load event type to wait for. Defaults to "load".
* @return {Promise} resolved when the event is handled.
* @resolves to the received event
* @rejects if a valid load event is not received within a meaningful interval

View File

@ -34,10 +34,8 @@ struct RedirEntry {
URI_SAFE_FOR_UNTRUSTED_CONTENT in the third argument to each map item below
unless your about: page really needs chrome privileges. Security review is
required before adding new map entries without
URI_SAFE_FOR_UNTRUSTED_CONTENT. Also note, however, that adding
URI_SAFE_FOR_UNTRUSTED_CONTENT will allow random web sites to link to that
URI. If you want to prevent this, add MAKE_UNLINKABLE as well.
*/
URI_SAFE_FOR_UNTRUSTED_CONTENT.
*/
static RedirEntry kRedirMap[] = {
#ifdef MOZ_SAFE_BROWSING
{ "blocked", "chrome://browser/content/blockedSite.xhtml",
@ -81,10 +79,12 @@ static RedirEntry kRedirMap[] = {
nsIAboutModule::ALLOW_SCRIPT },
{ "sync-tabs", "chrome://browser/content/sync/aboutSyncTabs.xul",
nsIAboutModule::ALLOW_SCRIPT },
// Linkable because of indexeddb use (bug 1228118)
{ "home", "chrome://browser/content/abouthome/aboutHome.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::MAKE_LINKABLE |
nsIAboutModule::ENABLE_INDEXED_DB },
// the newtab's actual URL will be determined when the channel is created
{ "newtab", "about:blank",
@ -99,15 +99,19 @@ static RedirEntry kRedirMap[] = {
#endif
{ "accounts", "chrome://browser/content/aboutaccounts/aboutaccounts.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
// Linkable because of indexeddb use (bug 1228118)
{ "loopconversation", "chrome://loop/content/panels/conversation.html",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT |
nsIAboutModule::MAKE_LINKABLE |
nsIAboutModule::ENABLE_INDEXED_DB },
// Linkable because of indexeddb use (bug 1228118)
{ "looppanel", "chrome://loop/content/panels/panel.html",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT |
nsIAboutModule::MAKE_LINKABLE |
nsIAboutModule::ENABLE_INDEXED_DB,
// Shares an IndexedDB origin with about:loopconversation.
"loopconversation" },
@ -115,7 +119,6 @@ static RedirEntry kRedirMap[] = {
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
nsIAboutModule::MAKE_UNLINKABLE |
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
};
static const int kRedirTotal = ArrayLength(kRedirMap);

View File

@ -74,9 +74,10 @@ CommandList.prototype = {
let os = PlatformInfo.os == "win" ? "windows" : PlatformInfo.os;
for (let name of Object.keys(manifest.commands)) {
let command = manifest.commands[name];
let shortcut = command.suggested_key[os] || command.suggested_key.default;
commands.set(name, {
description: command.description,
shortcut: command.suggested_key[os] || command.suggested_key.default,
shortcut: shortcut.replace(/\s+/g, ""),
});
}
return commands;

View File

@ -40,7 +40,7 @@ var gMenuBuilder = {
this.xulMenu = xulMenu;
for (let [, root] of gRootItems) {
let rootElement = this.buildElementWithChildren(root, contextData);
if (!rootElement.childNodes.length) {
if (!rootElement.firstChild.childNodes.length) {
// If the root has no visible children, there is no reason to show
// the root menu item itself either.
continue;

View File

@ -907,39 +907,44 @@ global.AllWindowEvents = {
}
},
removeListener(type, listener) {
if (type == "domwindowopened") {
removeListener(eventType, listener) {
if (eventType == "domwindowopened") {
return WindowListManager.removeOpenListener(listener);
} else if (type == "domwindowclosed") {
} else if (eventType == "domwindowclosed") {
return WindowListManager.removeCloseListener(listener);
}
let listeners = this._listeners.get(type);
let listeners = this._listeners.get(eventType);
listeners.delete(listener);
if (listeners.size == 0) {
this._listeners.delete(type);
this._listeners.delete(eventType);
if (this._listeners.size == 0) {
WindowListManager.removeOpenListener(this.openListener);
}
}
// Unregister listener from all existing windows.
let useCapture = eventType === "focus" || eventType === "blur";
for (let window of WindowListManager.browserWindows()) {
if (type == "progress") {
if (eventType == "progress") {
window.gBrowser.removeTabsProgressListener(listener);
} else {
window.removeEventListener(type, listener);
window.removeEventListener(eventType, listener, useCapture);
}
}
},
/* eslint-disable mozilla/balanced-listeners */
addWindowListener(window, eventType, listener) {
let useCapture = eventType === "focus" || eventType === "blur";
if (eventType == "progress") {
window.gBrowser.addTabsProgressListener(listener);
} else {
window.addEventListener(eventType, listener);
window.addEventListener(eventType, listener, useCapture);
}
},
/* eslint-enable mozilla/balanced-listeners */
// Runs whenever the "load" event fires for a new window.
openListener(window) {

View File

@ -29,11 +29,16 @@ extensions.registerSchemaAPI("windows", null, (extension, context) => {
}).api(),
onFocusChanged: new EventManager(context, "windows.onFocusChanged", fire => {
// FIXME: This will send multiple messages for a single focus change.
// Keep track of the last windowId used to fire an onFocusChanged event
let lastOnFocusChangedWindowId;
let listener = event => {
let window = WindowManager.topWindow;
let windowId = window ? WindowManager.getId(window) : WindowManager.WINDOW_ID_NONE;
fire(windowId);
if (windowId !== lastOnFocusChangedWindowId) {
fire(windowId);
lastOnFocusChangedWindowId = windowId;
}
};
AllWindowEvents.addListener("focus", listener);
AllWindowEvents.addListener("blur", listener);

View File

@ -11,7 +11,7 @@
"choices": [
{
"type": "string",
"pattern": "^(Alt|Ctrl|Command|MacCtr)\\+(Shift\\+)?([A-Z0-9]|Comma|Period|Home|End|PageUp|PageDown|Space|Insert|Delete|Up|Down|Left|Right)$"
"pattern": "^\\s*(Alt|Ctrl|Command|MacCtr)\\s*\\+\\s*(Shift\\s*\\+\\s*)?([A-Z0-9]|Comma|Period|Home|End|PageUp|PageDown|Space|Insert|Delete|Up|Down|Left|Right)\\s*$"
},
{
"type": "string",

View File

@ -12,6 +12,7 @@ support-files =
file_bypass_cache.sjs
file_language_fr_en.html
file_language_ja.html
file_dummy.html
[browser_ext_browserAction_context.js]
[browser_ext_browserAction_disabled.js]

View File

@ -22,11 +22,16 @@ add_task(function* test_user_defined_commands() {
},
"unrecognized_property": "with-a-random-value",
},
"toggle-feature-with-whitespace-in-suggested-key": {
"suggested_key": {
"default": " Alt + Shift + 2 ",
},
},
},
},
background: function() {
browser.commands.onCommand.addListener((commandName) => {
browser.commands.onCommand.addListener(commandName => {
browser.test.sendMessage("oncommand", commandName);
});
browser.test.sendMessage("ready");
@ -53,11 +58,11 @@ add_task(function* test_user_defined_commands() {
let keysetID = `ext-keyset-id-${makeWidgetId(extension.id)}`;
let keyset = win1.document.getElementById(keysetID);
ok(keyset != null, "Expected keyset to exist");
is(keyset.childNodes.length, 2, "Expected keyset to have 2 children");
is(keyset.childNodes.length, 3, "Expected keyset to have 3 children");
keyset = win2.document.getElementById(keysetID);
ok(keyset != null, "Expected keyset to exist");
is(keyset.childNodes.length, 2, "Expected keyset to have 2 children");
is(keyset.childNodes.length, 3, "Expected keyset to have 3 children");
// Confirm that the commands are registered to both windows.
yield focusWindow(win1);
@ -70,6 +75,10 @@ add_task(function* test_user_defined_commands() {
message = yield extension.awaitMessage("oncommand");
is(message, "toggle-feature-using-alt-shift-comma", "Expected onCommand listener to fire with correct message");
EventUtils.synthesizeKey("2", {altKey: true, shiftKey: true});
message = yield extension.awaitMessage("oncommand");
is(message, "toggle-feature-with-whitespace-in-suggested-key", "Expected onCommand listener to fire with correct message");
yield extension.unload();
// Confirm that the keysets have been removed from both windows after the extension is unloaded.

View File

@ -2,6 +2,67 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* () {
let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
"http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
gBrowser.selectedTab = tab1;
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["contextMenus"],
},
background: function() {
browser.contextMenus.create({
id: "clickme",
title: "Click me!",
contexts: ["image"],
});
browser.test.notifyPass();
},
});
yield extension.startup();
yield extension.awaitFinish();
let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
yield BrowserTestUtils.synthesizeMouseAtCenter("#img1", {
type: "contextmenu",
button: 2,
}, gBrowser.selectedBrowser);
yield popupShownPromise;
let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
is(item.length, 1, "contextMenu item for image was found");
let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
EventUtils.synthesizeMouseAtCenter(item[0], {});
yield popupHiddenPromise;
contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
yield BrowserTestUtils.synthesizeMouseAtCenter("body", {
type: "contextmenu",
button: 2,
}, gBrowser.selectedBrowser);
yield popupShownPromise;
item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
is(item.length, 0, "no contextMenu item for image was found");
// click something to close the context menu
popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
EventUtils.synthesizeMouseAtCenter(document.getElementById("context-selectall"), {});
yield popupHiddenPromise;
yield extension.unload();
yield BrowserTestUtils.removeTab(tab1);
});
/* globals content */
/* eslint-disable mozilla/no-cpows-in-tests */
add_task(function* () {

View File

@ -119,7 +119,8 @@ add_task(function* tabsSendMessageNoExceptionOnNonExistentTab() {
},
background: function() {
browser.tabs.create({url: "about:robots"}, tab => {
let url = "http://example.com/mochitest/browser/browser/components/extensions/test/browser/file_dummy.html";
browser.tabs.create({url}, tab => {
let exception;
try {
browser.tabs.sendMessage(tab.id, "message");

View File

@ -5,7 +5,6 @@
add_task(function* testWindowsEvents() {
function background() {
browser.windows.onCreated.addListener(function listener(window) {
browser.windows.onCreated.removeListener(listener);
browser.test.assertTrue(Number.isInteger(window.id),
"Window object's id is an integer");
browser.test.assertEq("normal", window.type,
@ -13,8 +12,21 @@ add_task(function* testWindowsEvents() {
browser.test.sendMessage("window-created", window.id);
});
let lastWindowId;
browser.windows.onFocusChanged.addListener(function listener(windowId) {
browser.test.assertTrue(lastWindowId !== windowId,
"onFocusChanged fired once for the given window");
lastWindowId = windowId;
browser.test.assertTrue(Number.isInteger(windowId),
"windowId is an integer");
browser.windows.getLastFocused().then(window => {
browser.test.assertEq(windowId, window.id,
"Last focused window has the correct id");
browser.test.sendMessage(`window-focus-changed-${windowId}`);
});
});
browser.windows.onRemoved.addListener(function listener(windowId) {
browser.windows.onRemoved.removeListener(listener);
browser.test.assertTrue(Number.isInteger(windowId),
"windowId is an integer");
browser.test.sendMessage(`window-removed-${windowId}`);
@ -30,10 +42,29 @@ add_task(function* testWindowsEvents() {
yield extension.startup();
yield extension.awaitMessage("ready");
let {WindowManager} = Cu.import("resource://gre/modules/Extension.jsm", {});
let currentWindow = window;
let currentWindowId = WindowManager.getId(currentWindow);
let win1 = yield BrowserTestUtils.openNewBrowserWindow();
let windowId = yield extension.awaitMessage("window-created");
let win1Id = yield extension.awaitMessage("window-created");
yield extension.awaitMessage(`window-focus-changed-${win1Id}`);
let win2 = yield BrowserTestUtils.openNewBrowserWindow();
let win2Id = yield extension.awaitMessage("window-created");
yield extension.awaitMessage(`window-focus-changed-${win2Id}`);
yield focusWindow(win1);
yield extension.awaitMessage(`window-focus-changed-${win1Id}`);
yield BrowserTestUtils.closeWindow(win2);
yield extension.awaitMessage(`window-removed-${win2Id}`);
yield BrowserTestUtils.closeWindow(win1);
yield extension.awaitMessage(`window-removed-${windowId}`);
yield extension.awaitMessage(`window-removed-${win1Id}`);
yield extension.awaitMessage(`window-focus-changed-${currentWindowId}`);
yield extension.awaitFinish("windows.events");
yield extension.unload();
});

View File

@ -0,0 +1,9 @@
<html>
<head>
<title>Dummy test page</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
</head>
<body>
<p>Dummy test page</p>
</body>
</html>

View File

@ -9,14 +9,14 @@
*/
add_task(function () {
// This URL has the following frames:
// + about:mozilla (static)
// + about:robots (static)
// + about:rights (dynamic iframe)
// + data:text/html,A (static)
// + data:text/html,B (static)
// + data:text/html,C (dynamic iframe)
const URL = "data:text/html;charset=utf-8," +
"<frameset cols=50%25,50%25><frame src=about%3Amozilla>" +
"<frame src=about%3Arobots></frameset>" +
"<frameset cols=50%25,50%25><frame src='data:text/html,A'>" +
"<frame src='data:text/html,B'></frameset>" +
"<script>var i=document.createElement('iframe');" +
"i.setAttribute('src', 'about%3Arights');" +
"i.setAttribute('src', 'data:text/html,C');" +
"document.body.appendChild(i);</script>";
// Add a new tab with two "static" and one "dynamic" frame.
@ -29,8 +29,8 @@ add_task(function () {
// Check URLs.
ok(entries[0].url.startsWith("data:text/html"), "correct root url");
is(entries[0].children[0].url, "about:mozilla", "correct url for 1st frame");
is(entries[0].children[1].url, "about:robots", "correct url for 2nd frame");
is(entries[0].children[0].url, "data:text/html,A", "correct url for 1st frame");
is(entries[0].children[1].url, "data:text/html,B", "correct url for 2nd frame");
// Check the number of children.
is(entries.length, 1, "there is one root entry ...");
@ -47,13 +47,13 @@ add_task(function () {
*/
add_task(function () {
// This URL has the following frames:
// + about:mozilla (static iframe)
// + about:rights (dynamic iframe)
// + data:text/html,A (static)
// + data:text/html,C (dynamic iframe)
const URL = "data:text/html;charset=utf-8," +
"<iframe name=t src=about%3Amozilla></iframe>" +
"<a id=lnk href=about%3Arobots target=t>clickme</a>" +
"<iframe name=t src='data:text/html,A'></iframe>" +
"<a id=lnk href='data:text/html,B' target=t>clickme</a>" +
"<script>var i=document.createElement('iframe');" +
"i.setAttribute('src', 'about%3Arights');" +
"i.setAttribute('src', 'data:text/html,C');" +
"document.body.appendChild(i);</script>";
// Add a new tab with one "static" and one "dynamic" frame.

View File

@ -55,7 +55,7 @@ var WindowListener = {
*/
setupBrowserUI: function(window) {
let document = window.document;
let gBrowser = window.gBrowser;
let { gBrowser, gURLBar } = window;
let xhrClass = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"];
let FileReader = window.FileReader;
let menuItem = null;
@ -176,6 +176,15 @@ var WindowListener = {
});
},
/**
* Called when a closing room has just been created, so we offer the
* user the chance to modify the name. For that we need to open the panel.
* Showing the proper layout is done on panel.jsx
*/
renameRoom: function() {
this.openPanel();
},
/**
* Opens the panel for Loop and sizes it appropriately.
*
@ -332,6 +341,7 @@ var WindowListener = {
Services.obs.addObserver(this, "loop-status-changed", false);
this.maybeAddCopyPanel();
this.updateToolbarState();
},
@ -364,6 +374,113 @@ var WindowListener = {
}
},
/**
* Maybe add the copy panel if it's not throttled and passes other checks.
* @return {Promise} Resolved when decided and maybe panel-added.
*/
maybeAddCopyPanel() {
// Don't bother adding the copy panel if we're in private browsing or
// we've already shown it.
if (PrivateBrowsingUtils.isWindowPrivate(window) ||
Services.prefs.getBoolPref("loop.copy.shown")) {
return Promise.resolve();
}
return Throttler.check("loop.copy").then(() => this.addCopyPanel());
},
/**
* Hook into the location bar copy command to open up the copy panel.
* @param {Function} onClickHandled Optional callback for finished clicks.
*/
addCopyPanel(onClickHandled) {
// Make a copy of the loop panel as a starting point for the copy panel.
let copy = this.panel.cloneNode(false);
copy.id = "loop-copy-notification-panel";
this.panel.parentNode.appendChild(copy);
// Record a telemetry copy panel action.
let addTelemetry = bucket => {
this.LoopAPI.sendMessageToHandler({
data: ["LOOP_COPY_PANEL_ACTIONS", this.constants.COPY_PANEL[bucket]],
name: "TelemetryAddValue"
});
};
// Handle events from the copy panel iframe content.
let onIframe = iframe => {
// Watch for events from the copy panel when loaded.
iframe.addEventListener("DOMContentLoaded", function onLoad() {
iframe.removeEventListener("DOMContentLoaded", onLoad);
// Size the panel to fit the rendered content adjusting for borders.
iframe.contentWindow.requestAnimationFrame(() => {
let height = iframe.contentDocument.documentElement.offsetHeight;
height += copy.boxObject.height - iframe.boxObject.height;
copy.style.height = height + "px";
});
// Hide the copy panel then show the loop panel.
iframe.contentWindow.addEventListener("CopyPanelClick", event => {
iframe.parentNode.hidePopup();
// Show the Loop panel if the user wants it.
let { accept, stop } = event.detail;
if (accept) {
LoopUI.openPanel();
}
// Stop showing the panel if the user says so.
if (stop) {
LoopUI.removeCopyPanel();
Services.prefs.setBoolPref("loop.copy.shown", true);
}
// Generate the appropriate NO_AGAIN, NO_NEVER, YES_AGAIN,
// YES_NEVER probe based on the user's action.
let probe = (accept ? "YES" : "NO") + "_" + (stop ? "NEVER" : "AGAIN");
addTelemetry(probe);
// For testing, indicate that handling the click has finished.
try {
onClickHandled(event.detail);
} catch (ex) {
// Do nothing.
}
});
});
};
// Override the default behavior of the copy command.
let controller = gURLBar._copyCutController;
controller._doCommand = controller.doCommand;
controller.doCommand = () => {
// Do the normal behavior first.
controller._doCommand.apply(controller, arguments);
// Open up the copy panel at the loop button.
addTelemetry("SHOWN");
LoopUI.PanelFrame.showPopup(window, LoopUI.toolbarButton.anchor, "loop-copy",
null, "chrome://loop/content/panels/copy.html", null, onIframe);
};
},
/**
* Removes the copy panel copy hook and the panel.
*/
removeCopyPanel() {
let controller = gURLBar && gURLBar._copyCutController;
if (controller && controller._doCommand) {
controller.doCommand = controller._doCommand;
delete controller._doCommand;
}
let copy = document.getElementById("loop-copy-notification-panel");
if (copy) {
copy.parentNode.removeChild(copy);
}
},
// Implements nsIObserver
observe: function(subject, topic, data) {
if (topic != "loop-status-changed") {
@ -519,23 +636,29 @@ var WindowListener = {
this.activeSound.load();
this.activeSound.play();
this.activeSound.addEventListener("ended", () => this.activeSound = undefined, false);
this.activeSound.addEventListener("ended", () => { this.activeSound = undefined; }, false);
},
/**
* Start listening to selected tab changes and notify any content page that's
* listening to 'BrowserSwitch' push messages.
* listening to 'BrowserSwitch' push messages. Also sets up a "joined"
* and "left" listener for LoopRooms so that we can toggle the infobar
* sharing messages when people come and go.
*
* Push message parameters:
* - {Integer} windowId The new windowId for the browser.
* @param {(String)} roomToken The current room that the link generator is connecting to.
*/
startBrowserSharing: function() {
startBrowserSharing: function(roomToken) {
if (!this._listeningToTabSelect) {
gBrowser.tabContainer.addEventListener("TabSelect", this);
this._listeningToTabSelect = true;
titleChangedListener = this.handleDOMTitleChanged.bind(this);
this._roomsListener = this.handleRoomJoinedOrLeft.bind(this);
this.LoopRooms.on("joined", this._roomsListener);
this.LoopRooms.on("left", this._roomsListener);
// Watch for title changes as opposed to location changes as more
// metadata about the page is available when this event fires.
this.mm.addMessageListener("loop@mozilla.org:DOMTitleChanged",
@ -549,7 +672,8 @@ var WindowListener = {
gBrowser.addEventListener("click", this);
}
this._maybeShowBrowserSharingInfoBar();
this._currentRoomToken = roomToken;
this._maybeShowBrowserSharingInfoBar(roomToken);
// Get the first window Id for the listener.
let browser = gBrowser.selectedBrowser;
@ -577,6 +701,8 @@ var WindowListener = {
this._hideBrowserSharingInfoBar();
gBrowser.tabContainer.removeEventListener("TabSelect", this);
this.LoopRooms.off("joined", this._roomsListener);
this.LoopRooms.off("left", this._roomsListener);
if (titleChangedListener) {
this.mm.removeMessageListener("loop@mozilla.org:DOMTitleChanged",
@ -591,6 +717,8 @@ var WindowListener = {
this._listeningToTabSelect = false;
this._browserSharePaused = false;
this._currentRoomToken = null;
this._sendTelemetryEventsIfNeeded();
},
@ -719,27 +847,53 @@ var WindowListener = {
return str;
},
/**
* Set correct strings for infobar notification based on if paused or empty.
*/
_setInfoBarStrings: function(nonOwnerParticipants, sharePaused) {
let message;
if (nonOwnerParticipants) {
// More than just the owner in the room.
message = this._getString(
sharePaused ? "infobar_screenshare_stop_sharing_message2" :
"infobar_screenshare_browser_message3");
} else {
// Just the owner in the room.
message = this._getString(
sharePaused ? "infobar_screenshare_stop_no_guest_message" :
"infobar_screenshare_no_guest_message");
}
let label = this._getString(
sharePaused ? "infobar_button_restart_label2" : "infobar_button_stop_label2");
let accessKey = this._getString(
sharePaused ? "infobar_button_restart_accesskey" : "infobar_button_stop_accesskey");
return { message: message, label: label, accesskey: accessKey };
},
/**
* Indicates if tab sharing is paused.
* Set by tab pause button, startBrowserSharing and stopBrowserSharing.
* Defaults to false as link generator(owner) enters room we are sharing tabs.
*/
_browserSharePaused: false,
/**
* Shows an infobar notification at the top of the browser window that warns
* the user that their browser tabs are being broadcasted through the current
* conversation.
* @param {String} currentRoomToken Room we are currently joined.
* @return {void}
*/
_maybeShowBrowserSharingInfoBar: function() {
// Pre-load strings
let pausedStrings = {
label: this._getString("infobar_button_restart_label2"),
accesskey: this._getString("infobar_button_restart_accesskey"),
message: this._getString("infobar_screenshare_stop_sharing_message")
};
let unpausedStrings = {
label: this._getString("infobar_button_stop_label2"),
accesskey: this._getString("infobar_button_stop_accesskey"),
message: this._getString("infobar_screenshare_browser_message2")
};
let initStrings =
this._browserSharePaused ? pausedStrings : unpausedStrings;
_maybeShowBrowserSharingInfoBar: function(currentRoomToken) {
this._hideBrowserSharingInfoBar();
let participantsCount = this.LoopRooms.getNumParticipants(currentRoomToken);
let initStrings = this._setInfoBarStrings(participantsCount > 1, this._browserSharePaused);
let box = gBrowser.getNotificationBox();
let bar = box.appendNotification(
initStrings.message, // label
@ -749,11 +903,12 @@ var WindowListener = {
box.PRIORITY_WARNING_LOW, // priority
[{ // buttons (Pause, Stop)
label: initStrings.label,
accessKey: initStrings.accessKey,
accessKey: initStrings.accesskey,
isDefault: false,
callback: (event, buttonInfo, buttonNode) => {
this._browserSharePaused = !this._browserSharePaused;
let stringObj = this._browserSharePaused ? pausedStrings : unpausedStrings;
let guestPresent = this.LoopRooms.getNumParticipants(this._currentRoomToken) > 1;
let stringObj = this._setInfoBarStrings(guestPresent, this._browserSharePaused);
bar.label = stringObj.message;
bar.classList.toggle("paused", this._browserSharePaused);
buttonNode.label = stringObj.label;
@ -820,6 +975,18 @@ var WindowListener = {
gBrowser.selectedBrowser.outerWindowID);
},
/**
* Handles updating of the sharing infobar when the room participants
* change.
*/
handleRoomJoinedOrLeft: function() {
// Don't attempt to show it if we're not actively sharing.
if (!this._listeningToTabSelect) {
return;
}
this._maybeShowBrowserSharingInfoBar(this._currentRoomToken);
},
/**
* Handles events from the frame script.
*
@ -840,8 +1007,9 @@ var WindowListener = {
* Handles events from gBrowser.
*/
handleEvent: function(event) {
switch (event.type) {
case "TabSelect":
case "TabSelect": {
let wasVisible = false;
// Hide the infobar from the previous tab.
if (event.detail.previousTab) {
@ -857,9 +1025,10 @@ var WindowListener = {
if (wasVisible) {
// If the infobar was visible before, we should show it again after the
// switch.
this._maybeShowBrowserSharingInfoBar();
this._maybeShowBrowserSharingInfoBar(this._currentRoomToken);
}
break;
}
case "mousemove":
this.handleMousemove(event);
break;
@ -971,6 +1140,9 @@ var WindowListener = {
LoopUI.init();
window.LoopUI = LoopUI;
// Export the Throttler to allow tests to overwrite parts of it.
window.LoopThrottler = Throttler;
},
/**
@ -981,6 +1153,7 @@ var WindowListener = {
*/
tearDownBrowserUI: function(window) {
if (window.LoopUI) {
window.LoopUI.removeCopyPanel();
window.LoopUI.removeMenuItem();
// This stops the frame script being loaded to new tabs, but doesn't
@ -1015,6 +1188,77 @@ var WindowListener = {
}
};
/**
* Provide a way to throttle functionality using DNS to distribute 3 numbers for
* various distributions channels. DNS is used to scale distribution of the
* numbers as an A record pointing to a loopback address (127.*.*.*). Prefs are
* used to control behavior (what domain to check) and keep state (a ticket
* number to track if it needs to initialize, to wait for its turn, or is
* completed).
*/
let Throttler = {
// Each 8-bit block of the IP address allows for 0% rollout (value 0) to 100%
// rollout (value 255).
TICKET_LIMIT: 255,
// Allow the DNS service to be overwritten for testing.
_dns: Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService),
/**
* Check if a given feature should be throttled or not.
* @param {string} [prefPrefix] Start of the preference name for the feature.
* @return {Promise} Resolved on success, and rejected on throttled.
*/
check(prefPrefix) {
return new Promise((resolve, reject) => {
// Initialize the ticket (0-254) if it doesn't have a valid value yet.
let prefTicket = prefPrefix + ".ticket";
let ticket = Services.prefs.getIntPref(prefTicket);
if (ticket < 0) {
ticket = Math.floor(Math.random() * this.TICKET_LIMIT);
Services.prefs.setIntPref(prefTicket, ticket);
}
// Short circuit if the special ticket value indicates we're good to go.
else if (ticket >= this.TICKET_LIMIT) {
resolve();
return;
}
// Handle responses from the DNS resolution service request.
let onDNS = (request, record) => {
// Use a specific part of the A-record IP address depending on the
// channel. I.e., 127.[release/other].[beta].[aurora/nightly].
let index = 1;
switch (Services.prefs.getCharPref("app.update.channel")) {
case "beta":
index = 2;
break;
case "aurora":
case "nightly":
index = 3;
break;
}
// Select the 1 out of 4 parts of the "."-separated IP address to check
// if the 8-bit threshold (0-255) exceeds the ticket (0-254).
let threshold = record && record.getNextAddrAsString().split(".")[index];
if (threshold && ticket < threshold) {
// Remember that we're good to go to avoid future DNS checks.
Services.prefs.setIntPref(prefTicket, this.TICKET_LIMIT);
resolve();
}
else {
reject();
}
};
// Look up the DNS A-record of a throttler hostname to decide to show.
this._dns.asyncResolve(Services.prefs.getCharPref(prefPrefix + ".throttler"),
this._dns.RESOLVE_DISABLE_IPV6, onDNS, Services.tm.mainThread);
});
}
};
/**
* Creates the loop button on the toolbar. Due to loop being a system-addon
* CustomizableUI already has a placement location for the button, so that

View File

@ -567,11 +567,11 @@ var LoopRoomsInternal = {
eventEmitter.emit("delete", room);
eventEmitter.emit("delete:" + room.roomToken, room);
} else {
yield this.addOrUpdateRoom(room, !!orig);
if (orig) {
checkForParticipantsUpdate(orig, room);
}
yield this.addOrUpdateRoom(room, !!orig);
}
}
@ -594,6 +594,27 @@ var LoopRoomsInternal = {
return gGetAllPromise;
},
/**
* Request information about number of participants joined to a specific room from the server.
* Used to determine infobar message state on the number of participants in the room.
*
* @param {String} roomToken Room identifier
* @return {Number} Count of participants in the room.
*/
getNumParticipants: function(roomToken) {
try {
if (this.rooms && this.rooms.has(roomToken)) {
return this.rooms.get(roomToken).participants.length;
}
return 0;
}
catch (ex) {
// No room, log error and send back 0 to indicate none in room.
MozLoopService.log.error("No room found in current session: ", ex);
return 0;
}
},
/**
* Request information about a specific room from the server. It will be
* returned from the cache if it's already in it.
@ -630,10 +651,9 @@ var LoopRoomsInternal = {
eventEmitter.emit("delete", room);
eventEmitter.emit("delete:" + room.roomToken, room);
} else {
checkForParticipantsUpdate(room, data);
extend(room, data);
yield this.addOrUpdateRoom(data, !needsUpdate);
yield this.addOrUpdateRoom(room, !needsUpdate);
checkForParticipantsUpdate(room, data);
}
callback(null, room);
}.bind(this)).catch(callback);
@ -1173,6 +1193,10 @@ this.LoopRooms = {
return LoopRoomsInternal.maybeRefresh(user);
},
getNumParticipants: function(roomToken) {
return LoopRoomsInternal.getNumParticipants(roomToken);
},
/**
* This method is only useful for unit tests to set the rooms cache to contain
* a list of fake room data that can be asserted in tests.
@ -1223,14 +1247,18 @@ this.LoopRooms = {
* @param {Map} roomsCache The new cache data to set for testing purposes. If
* not specified, it will reset the cache.
*/
_setRoomsCache: function(roomsCache) {
_setRoomsCache: function(roomsCache, orig) {
LoopRoomsInternal.rooms.clear();
gDirty = true;
if (roomsCache) {
// Need a clone as the internal map is read-only.
for (let [key, value] of roomsCache) {
LoopRoomsInternal.rooms.set(key, value);
if (orig) {
checkForParticipantsUpdate(orig, value);
}
}
gGetAllPromise = null;
gDirty = false;

View File

@ -154,5 +154,7 @@ LoopRoomsCache.prototype = {
cache[sessionType][roomToken].key = roomKey;
return yield this._setCache(cache);
}
return Promise.resolve();
})
};

View File

@ -124,6 +124,16 @@ const updateSocialProvidersCache = function() {
return gSocialProviders;
};
/**
* Checks that [browser.js]'s global variable `gMultiProcessBrowser` is active,
* instead of checking on first available browser element.
* :see bug 1257243 comment 5:
*/
const isMultiProcessActive = function() {
let win = Services.wm.getMostRecentWindow("navigator:browser");
return !!win.gMultiProcessBrowser;
};
var gAppVersionInfo = null;
var gBrowserSharingListeners = new Set();
var gBrowserSharingWindows = new Set();
@ -160,7 +170,7 @@ const kMessageHandlers = {
* @param {Object} message Message meant for the handler function, containing
* the following parameters in its `data` property:
* [
* {Number} windowId The window ID of the chat window
* {String} roomToken The room ID to start browser sharing and listeners.
* ]
* @param {Function} reply Callback function, invoked with the result of this
* message handler. The result will be sent back to
@ -187,9 +197,11 @@ const kMessageHandlers = {
return;
}
// get room token from message
let [windowId] = message.data;
win.LoopUI.startBrowserSharing();
// For rooms, the windowId === roomToken. If we change the type of place we're
// sharing from in the future, we may need to change this.
win.LoopUI.startBrowserSharing(windowId);
// Point new tab to load about:home to avoid accidentally sharing top sites.
NewTabURL.override("about:home");
@ -422,6 +434,7 @@ const kMessageHandlers = {
*/
GetAllConstants: function(message, reply) {
reply({
COPY_PANEL: COPY_PANEL,
LOOP_SESSION_TYPE: LOOP_SESSION_TYPE,
LOOP_MAU_TYPE: LOOP_MAU_TYPE,
ROOM_CREATE: ROOM_CREATE,
@ -638,8 +651,23 @@ const kMessageHandlers = {
*/
GetSelectedTabMetadata: function(message, reply) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
win.messageManager.addMessageListener("PageMetadata:PageDataResult", function onPageDataResult(msg) {
win.messageManager.removeMessageListener("PageMetadata:PageDataResult", onPageDataResult);
let browser = win && win.gBrowser.selectedBrowser;
if (!win || !browser) {
MozLoopService.log.error("Error occurred whilst fetching page metadata");
reply();
return;
}
// non-remote pages have no metadata
if (!browser.getAttribute("remote") === "true") {
reply(null);
}
win.messageManager.addMessageListener("PageMetadata:PageDataResult",
function onPageDataResult(msg) {
win.messageManager.removeMessageListener("PageMetadata:PageDataResult",
onPageDataResult);
let pageData = msg.json;
win.LoopUI.getFavicon(function(err, favicon) {
if (err && err !== "favicon not found for uri") {
@ -754,9 +782,31 @@ const kMessageHandlers = {
* the senders' channel.
*/
IsMultiProcessActive: function(message, reply) {
reply(isMultiProcessActive());
},
/**
* Checks that the current tab can be shared.
* Non-shareable tabs are the non-remote ones when e10s is enabled.
*
* @param {Object} message Message meant for the handler function,
* with no data attached.
* @param {Function} reply Callback function, invoked with the result of
* the check. The result will be sent back to
* the senders' channel.
*/
IsTabShareable: function(message, reply) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
let browser = win && win.gBrowser.selectedBrowser;
reply(!!(browser && browser.getAttribute("remote") == "true"));
if (!win || !browser) {
reply(false);
return;
}
let e10sActive = isMultiProcessActive();
let tabRemote = browser.getAttribute("remote") === "true";
reply(!e10sActive || (e10sActive && tabRemote));
},
/**
@ -985,6 +1035,23 @@ const kMessageHandlers = {
reply();
},
/**
* Called when a closing room has just been created, so user can change
* the name of the room to be stored.
*
* @param {Object} message Message meant for the handler function, shouldn't
contain any data.
* @param {Function} reply Callback function, invoked with the result of this
* message handler. The result will be sent back to
* the senders' channel.
*/
SetNameNewRoom: function(message, reply) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
win && win.LoopUI.renameRoom();
reply();
},
/**
* Used to record the screen sharing state for a window so that it can
* be reflected on the toolbar button.

View File

@ -408,6 +408,8 @@ var MozLoopPushHandler = {
this._mockWebSocket = options.mockWebSocket;
}
this.pushServerUri = Services.prefs.getCharPref("dom.push.serverURL");
this._openSocket();
},
@ -764,62 +766,12 @@ var MozLoopPushHandler = {
// For tests, use the mock instance.
this._pushSocket = new PushSocket(this._mockWebSocket);
let performOpen = () => {
consoleLog.info("PushHandler: attempt to open websocket to PushServer: ", this.pushServerUri);
this._pushSocket.connect(this.pushServerUri,
(aMsg) => this._onMsg(aMsg),
() => this._onStart(),
(aCode, aReason) => this._onClose(aCode, aReason));
};
consoleLog.info("PushHandler: attempt to open websocket to PushServer: ", this.pushServerUri);
this._pushSocket.connect(this.pushServerUri,
(aMsg) => this._onMsg(aMsg),
() => this._onStart(),
(aCode, aReason) => this._onClose(aCode, aReason));
let pushServerURLFetchError = () => {
consoleLog.warn("PushHandler: Could not retrieve push server URL from Loop server, will retry");
this._pushSocket = undefined;
this._retryManager.retry(() => this._openSocket());
return;
};
try {
this.pushServerUri = Services.prefs.getCharPref("loop.debug.pushserver");
}
catch (e) {
// Do nothing
}
if (!this.pushServerUri) {
// Get push server to use from the Loop server
let pushUrlEndpoint = Services.prefs.getCharPref("loop.server") + "/push-server-config";
let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(
Ci.nsIXMLHttpRequest);
req.open("GET", pushUrlEndpoint);
req.onload = () => {
if (req.status >= 200 && req.status < 300) {
let pushServerConfig;
try {
pushServerConfig = JSON.parse(req.responseText);
} catch (e) {
consoleLog.warn("PushHandler: Error parsing JSON response for push server URL");
pushServerURLFetchError();
}
if (pushServerConfig.pushServerURI) {
this._retryManager.reset();
this.pushServerUri = pushServerConfig.pushServerURI;
performOpen();
} else {
consoleLog.warn("PushHandler: push server URL config lacks pushServerURI parameter");
pushServerURLFetchError();
}
} else {
consoleLog.warn("PushHandler: push server URL retrieve error: " + req.status);
pushServerURLFetchError();
}
};
req.onerror = pushServerURLFetchError;
req.send();
} else {
// this.pushServerUri already set -- just open the channel
performOpen();
}
},
/**

View File

@ -69,6 +69,24 @@ const SHARING_SCREEN = {
RESUMED: 1
};
/**
* Values that we segment copy panel action telemetry probes into.
*
* @enum {Number}
*/
const COPY_PANEL = {
// Copy panel was shown to the user.
SHOWN: 0,
// User selected "no" and to allow the panel to show again.
NO_AGAIN: 1,
// User selected "no" and to never show the panel.
NO_NEVER: 2,
// User selected "yes" and to allow the panel to show again.
YES_AGAIN: 3,
// User selected "yes" and to never show the panel.
YES_NEVER: 4
};
/**
* Values that we segment MAUs telemetry probes into.
*
@ -106,13 +124,14 @@ Cu.import("resource://gre/modules/FxAccountsOAuthClient.jsm");
Cu.importGlobalProperties(["URL"]);
this.EXPORTED_SYMBOLS = ["MozLoopService", "LOOP_SESSION_TYPE", "LOOP_MAU_TYPE",
"TWO_WAY_MEDIA_CONN_LENGTH", "SHARING_ROOM_URL", "SHARING_SCREEN",
"TWO_WAY_MEDIA_CONN_LENGTH", "SHARING_ROOM_URL", "SHARING_SCREEN", "COPY_PANEL",
"ROOM_CREATE", "ROOM_DELETE"];
XPCOMUtils.defineConstant(this, "LOOP_SESSION_TYPE", LOOP_SESSION_TYPE);
XPCOMUtils.defineConstant(this, "TWO_WAY_MEDIA_CONN_LENGTH", TWO_WAY_MEDIA_CONN_LENGTH);
XPCOMUtils.defineConstant(this, "SHARING_ROOM_URL", SHARING_ROOM_URL);
XPCOMUtils.defineConstant(this, "SHARING_SCREEN", SHARING_SCREEN);
XPCOMUtils.defineConstant(this, "COPY_PANEL", COPY_PANEL);
XPCOMUtils.defineConstant(this, "ROOM_CREATE", ROOM_CREATE);
XPCOMUtils.defineConstant(this, "ROOM_DELETE", ROOM_DELETE);
XPCOMUtils.defineConstant(this, "LOOP_MAU_TYPE", LOOP_MAU_TYPE);
@ -1335,7 +1354,7 @@ this.MozLoopService = {
*
* @return {Promise}
*/
initialize: Task.async(function*(addonVersion) {
initialize: Task.async(function* (addonVersion) {
// Ensure we don't setup things like listeners more than once.
if (gServiceInitialized) {
return Promise.resolve();
@ -1463,7 +1482,7 @@ this.MozLoopService = {
* Can be called more than once (e.g. if the initial setup fails at some phase).
* @param {Deferred} deferredInitialization
*/
delayedInitialize: Task.async(function*(deferredInitialization) {
delayedInitialize: Task.async(function* (deferredInitialization) {
log.debug("delayedInitialize");
// Set or clear an error depending on how deferredInitialization gets resolved.
// We do this first so that it can handle the early returns below.
@ -1815,7 +1834,7 @@ this.MozLoopService = {
*
* @return {Promise} that resolves when the FxA logout flow is complete.
*/
logOutFromFxA: Task.async(function*() {
logOutFromFxA: Task.async(function* () {
log.debug("logOutFromFxA");
try {
yield MozLoopServiceInternal.unregisterFromLoopServer(LOOP_SESSION_TYPE.FXA);

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<!-- 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/. -->
<html>
<head>
<meta charset="utf-8">
<base href="chrome://loop/content">
<link rel="stylesheet" type="text/css" href="shared/css/reset.css">
<link rel="stylesheet" type="text/css" href="shared/css/common.css">
<link rel="stylesheet" type="text/css" href="panels/css/copy.css">
</head>
<body class="panel">
<div id="main"></div>
<script type="text/javascript" src="panels/vendor/l10n.js"></script>
<script type="text/javascript" src="shared/vendor/react.js"></script>
<script type="text/javascript" src="panels/js/copy.js"></script>
</body>
</html>

View File

@ -0,0 +1,49 @@
/* 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/. */
html {
font-family: menu;
font-size: 10px;
}
.copy-body {
display: flex;
padding: 1.5rem;
}
.copy-logo {
height: 32px;
-moz-margin-end: 1.5rem;
}
.copy-message {
color: #000;
font-size: 1.2rem;
}
.copy-toggle-label {
display: block;
margin-top: 1.5rem;
}
.copy-toggle-label input {
-moz-margin-start: 0;
}
.copy-button {
background: #efefef;
border: 0;
line-height: 1rem;
outline: 1px solid #d1d1d1;
padding: 1.5rem;
width: 50%;
}
.copy-button:active,
.copy-button:focus,
.copy-button:hover {
background: #00a9dc;
color: #fff;
outline-color: #0097c5;
}

View File

@ -96,6 +96,70 @@ body {
border-radius: 3px;
}
/* Rename closing room panel */
.rename-newRoom > img {
margin-left: 16px;
position: absolute;
width: 32px;
}
html[dir="rtl"] .rename-newRoom > img {
margin-left: 0;
margin-right: 16px;
}
.rename-container {
margin: 16px;
/* margin is img-width + 16px separation */
padding-left: 48px;
}
html[dir="rtl"] .rename-container {
padding-left: 0;
padding-right: 48px;
}
.rename-container > .rename-header {
color: #000;
font-size: 1.2em;
padding-top: 5px;
}
.rename-container > .rename-prompt {
margin-bottom: 8px;
}
.rename-container > .input-group {
background: #fff;
border: 1px solid #d1d1d1;
border-radius: 2px;
margin-left: -2px;
width: 100%;
padding: 2px 4px;
}
html[dir="rtl"] .rename-container > .input-group {
margin-left: 0;
margin-right: -2px;
}
.rename-container > .input-group.focused {
border: 1px solid #0078f9;
box-shadow: 0 0 0 3px #9eccfe;
}
.input-group > .rename-input {
border: 0px none;
font-size: 1.4rem;
width: 100%;
}
.rename-button {
background: #efefef;
border-top: 1px solid #d1d1d1;
display: block;
padding: 12px;
width: 100%;
}
/* Content area and input fields */
.content-area {
@ -724,7 +788,6 @@ img.fte-hello-web-share {
}
.fte-get-started-container {
width: 330px; /* XXXremove me after this comment exists on 1.1 */
display: flex;
flex-flow: column nowrap;
background: #fbfbfb;

View File

@ -1,15 +0,0 @@
{
"ecmaFeatures": {
// These are on for this directory for .jsm and content/js files.
"blockBindings": true,
"arrowFunctions": true,
"destructuring": true,
"generators": true,
"spread": true,
"restParams": true,
"objectLiteralShorthandMethods": true
},
"rules": {
"generator-star-spacing": [2, "after"]
}
}

View File

@ -0,0 +1,120 @@
/* 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/. */
/* global Components */
let { utils: Cu } = Components;
var loop = loop || {};
loop.copy = (mozL10n => {
"use strict";
/**
* Copy panel view.
*/
let CopyView = React.createClass({
displayName: "CopyView",
mixins: [React.addons.PureRenderMixin],
getInitialState() {
return { checked: false };
},
/**
* Send a message to chrome/bootstrap to handle copy panel events.
* @param {Boolean} accept True if the user clicked accept.
*/
_dispatch(accept) {
window.dispatchEvent(new CustomEvent("CopyPanelClick", {
detail: {
accept,
stop: this.state.checked
}
}));
},
handleAccept() {
this._dispatch(true);
},
handleCancel() {
this._dispatch(false);
},
handleToggle() {
this.setState({ checked: !this.state.checked });
},
render() {
return React.createElement(
"div",
{ className: "copy-content" },
React.createElement(
"div",
{ className: "copy-body" },
React.createElement("img", { className: "copy-logo", src: "shared/img/helloicon.svg" }),
React.createElement(
"h1",
{ className: "copy-message" },
mozL10n.get("copy_panel_message"),
React.createElement(
"label",
{ className: "copy-toggle-label" },
React.createElement("input", { onChange: this.handleToggle, type: "checkbox" }),
mozL10n.get("copy_panel_dont_show_again_label")
)
)
),
React.createElement(
"button",
{ className: "copy-button", onClick: this.handleCancel },
mozL10n.get("copy_panel_cancel_button_label")
),
React.createElement(
"button",
{ className: "copy-button", onClick: this.handleAccept },
mozL10n.get("copy_panel_accept_button_label")
)
);
}
});
/**
* Copy panel initialization.
*/
function init() {
// Wait for all LoopAPI message requests to complete before continuing init.
let { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
let requests = ["GetAllStrings", "GetLocale", "GetPluralRule"];
return Promise.all(requests.map(name => new Promise(resolve => {
LoopAPI.sendMessageToHandler({ name }, resolve);
}))).then(results => {
// Extract individual requested values to initialize l10n.
let [stringBundle, locale, pluralRule] = results;
mozL10n.initialize({
getStrings(key) {
if (!(key in stringBundle)) {
console.error("No string found for key:", key);
}
return JSON.stringify({ textContent: stringBundle[key] || "" });
},
locale,
pluralRule
});
React.render(React.createElement(CopyView, null), document.querySelector("#main"));
document.documentElement.setAttribute("dir", mozL10n.language.direction);
document.documentElement.setAttribute("lang", mozL10n.language.code);
});
}
return {
CopyView,
init
};
})(document.mozL10n);
document.addEventListener("DOMContentLoaded", loop.copy.init);

View File

@ -242,6 +242,7 @@ loop.shared.desktopViews = function (mozL10n) {
locationForMetrics: this.props.locationForMetrics,
roomData: this.props.roomData });
}
return null;
})()
),
React.createElement(SocialShareDropdown, {

View File

@ -36,16 +36,18 @@ loop.panel = function (_, mozL10n) {
},
renderGettingStartedButton: function () {
if (!this.props.displayRoomListContent) {
return React.createElement(
"div",
{ className: "fte-button-container" },
React.createElement(Button, { additionalClass: "fte-get-started-button",
caption: mozL10n.get("first_time_experience_button_label2"),
htmlId: "fte-button",
onClick: this.handleButtonClick })
);
if (this.props.displayRoomListContent) {
return null;
}
return React.createElement(
"div",
{ className: "fte-button-container" },
React.createElement(Button, { additionalClass: "fte-get-started-button",
caption: mozL10n.get("first_time_experience_button_label2"),
htmlId: "fte-button",
onClick: this.handleButtonClick })
);
},
render: function () {
@ -444,6 +446,18 @@ loop.panel = function (_, mozL10n) {
}
});
/**
* Aux function to retrieve the name of a room
*/
function _getRoomTitle(room) {
if (!room) {
return mozL10n.get("room_name_untitled_page");
}
var urlData = (room.decryptedContext.urls || [])[0] || {};
return room.decryptedContext.roomName || urlData.description || urlData.location || mozL10n.get("room_name_untitled_page");
}
/**
* Room list entry.
*
@ -465,15 +479,10 @@ loop.panel = function (_, mozL10n) {
return {
editMode: false,
eventPosY: 0,
newRoomName: this._getRoomTitle()
newRoomName: _getRoomTitle(this.props.room)
};
},
_getRoomTitle: function () {
var urlData = (this.props.room.decryptedContext.urls || [])[0] || {};
return this.props.room.decryptedContext.roomName || urlData.description || urlData.location || mozL10n.get("room_name_untitled_page");
},
_isActive: function () {
return this.props.room.participants.length > 0;
},
@ -491,10 +500,6 @@ loop.panel = function (_, mozL10n) {
return;
}
this.props.dispatcher.dispatch(new sharedActions.OpenRoom({
roomToken: this.props.room.roomToken
}));
// Open url if needed.
loop.requestMulti(["getSelectedTabMetadata"], ["GettingStartedURL", null, {}]).then(function (results) {
var contextURL = this.props.room.decryptedContext.urls && this.props.room.decryptedContext.urls[0].location;
@ -505,6 +510,12 @@ loop.panel = function (_, mozL10n) {
loop.request("OpenURL", contextURL);
}
this.closeWindow();
// open the room after the (possible) tab change to be able to
// share when opening from non-remote tab.
this.props.dispatcher.dispatch(new sharedActions.OpenRoom({
roomToken: this.props.room.roomToken
}));
}.bind(this));
},
@ -566,7 +577,7 @@ loop.panel = function (_, mozL10n) {
"room-active": this._isActive(),
"room-opened": this.props.isOpenedRoom
});
var roomTitle = this._getRoomTitle();
var roomTitle = _getRoomTitle(this.props.room);
return React.createElement(
"div",
@ -791,6 +802,7 @@ loop.panel = function (_, mozL10n) {
if (Object.prototype.toString.call(props[propName]) !== "[object Object]" && !_.isNull(props[propName])) {
return new Error("Required prop `" + propName + "` was not correctly specified in `" + componentName + "`.");
}
return null;
}
/**
@ -860,6 +872,8 @@ loop.panel = function (_, mozL10n) {
if (this.state.rooms.length > 5) {
return React.createElement("div", { className: "room-list-blur" });
}
return null;
},
render: function () {
@ -957,8 +971,14 @@ loop.panel = function (_, mozL10n) {
},
handleCreateButtonClick: function () {
var createRoomAction = new sharedActions.CreateRoom();
// check that tab is remote, open about:home if not
loop.request("IsTabShareable").then(shareable => {
if (!shareable) {
loop.request("OpenURL", "about:home");
}
});
var createRoomAction = new sharedActions.CreateRoom();
createRoomAction.urls = [{
location: this.state.url,
description: this.state.description,
@ -1140,12 +1160,108 @@ loop.panel = function (_, mozL10n) {
}
});
var RenameRoomView = React.createClass({
displayName: "RenameRoomView",
mixins: [sharedMixins.WindowCloseMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
roomName: React.PropTypes.string.isRequired,
roomToken: React.PropTypes.string.isRequired
},
getInitialState: function () {
return { focused: false };
},
componentDidMount: function () {
this.getDOMNode().querySelector("input").focus();
},
handleBlur: function () {
this.setState({ focused: false });
},
handleFocus: function () {
this.setState({ focused: true });
this.getDOMNode().querySelector("input").select();
},
handleKeyDown: function (event) {
if (event.which === 13) {
this.handleNameChange();
}
},
handleNameChange: function () {
let token = this.props.roomToken,
name = this.getDOMNode().querySelector("input").value || "";
if (name !== this.props.roomName) {
this.props.dispatcher.dispatch(new sharedActions.UpdateRoomContext({
roomToken: token,
newRoomName: name
}));
}
this.closeWindow();
},
render: function () {
let inputClass = classNames({
"input-group": true,
"focused": this.state.focused
});
return React.createElement(
"div",
{ className: "rename-newRoom" },
React.createElement("img", { src: "shared/img/helloicon.svg" }),
React.createElement(
"div",
{ className: "rename-container" },
React.createElement(
"h2",
{ className: "rename-header" },
mozL10n.get("door_hanger_bye")
),
React.createElement(
"p",
{ className: "rename-subheader" },
mozL10n.get("door_hanger_return2")
),
React.createElement(
"p",
{ className: "rename-prompt" },
mozL10n.get("door_hanger_current")
),
React.createElement(
"div",
{ className: inputClass },
React.createElement("input", { className: "rename-input",
defaultValue: this.props.roomName,
onBlur: this.handleBlur,
onFocus: this.handleFocus,
onKeyDown: this.handleKeyDown,
type: "text" })
)
),
React.createElement(Button, { additionalClass: "rename-button",
caption: mozL10n.get("door_hanger_button2"),
onClick: this.handleNameChange })
);
}
});
/**
* Panel view.
*/
var PanelView = React.createClass({
displayName: "PanelView",
mixins: [Backbone.Events, sharedMixins.DocumentVisibilityMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
// Only used for the ui-showcase:
@ -1169,6 +1285,7 @@ loop.panel = function (_, mozL10n) {
gettingStartedSeen: loop.getStoredRequest(["GetLoopPref", "gettingStarted.latestFTUVersion"]) >= FTU_VERSION,
multiProcessActive: loop.getStoredRequest(["IsMultiProcessActive"]),
remoteAutoStart: loop.getStoredRequest(["GetLoopPref", "remote.autostart"]),
renameRoom: null,
sharePanelOpened: false
};
},
@ -1237,8 +1354,35 @@ loop.panel = function (_, mozL10n) {
}.bind(this));
},
_onClosingNewRoom: function () {
// If we close a recently created room, we offer the chance of renaming it
let storeState = this.props.roomStore.getStoreState();
if (!storeState.closingNewRoom || !storeState.openedRoom) {
return;
}
let closedRoom = storeState.rooms.filter(function (room) {
// closing room is the last that was opened.
return storeState.openedRoom === room.roomToken;
})[0];
this.setState({
renameRoom: storeState.closingNewRoom && {
token: storeState.openedRoom,
name: _getRoomTitle(closedRoom)
}
});
},
onDocumentHidden: function () {
// consider closing panel any other way than click OK button
// or Enter key the same as cancel renaming the room
this.setState({ renameRoom: null });
},
componentWillMount: function () {
this.updateServiceErrors();
this.listenTo(this.props.roomStore, "change:closingNewRoom", this._onClosingNewRoom);
},
componentDidMount: function () {
@ -1247,6 +1391,7 @@ loop.panel = function (_, mozL10n) {
componentWillUnmount: function () {
loop.unsubscribe("LoopStatusChanged", this._onStatusChanged);
this.stopListening(this.props.roomStore);
},
handleContextMenu: function (e) {
@ -1284,10 +1429,18 @@ loop.panel = function (_, mozL10n) {
React.createElement(ToSView, null)
);
}
if (!this.state.hasEncryptionKey) {
return React.createElement(SignInRequestView, null);
}
if (this.state.renameRoom) {
return React.createElement(RenameRoomView, {
dispatcher: this.props.dispatcher,
roomName: this.state.renameRoom.name,
roomToken: this.state.renameRoom.token });
}
var cssClasses = classNames({
"panel-content": true,
"showing-share-panel": this.state.sharePanelOpened
@ -1392,6 +1545,7 @@ loop.panel = function (_, mozL10n) {
init: init,
NewRoomView: NewRoomView,
PanelView: PanelView,
RenameRoomView: RenameRoomView,
RoomEntry: RoomEntry,
RoomEntryContextButtons: RoomEntryContextButtons,
RoomList: RoomList,

View File

@ -103,6 +103,7 @@ loop.store = loop.store || {};
this._notifications = options.notifications;
this._constants = options.constants;
this._gotAllRooms = false;
if (options.activeRoomStore) {
this.activeRoomStore = options.activeRoomStore;
@ -114,7 +115,9 @@ loop.store = loop.store || {};
getInitialStoreState: function() {
return {
activeRoom: this.activeRoomStore ? this.activeRoomStore.getStoreState() : {},
closingNewRoom: false,
error: null,
lastCreatedRoom: null,
openedRoom: null,
pendingCreation: false,
pendingInitialRetrieval: true,
@ -153,6 +156,7 @@ loop.store = loop.store || {};
_onRoomAdded: function(addedRoomData) {
addedRoomData.participants = addedRoomData.participants || [];
addedRoomData.ctime = addedRoomData.ctime || new Date().getTime();
this.dispatchAction(new sharedActions.UpdateRoomList({
// Ensure the room isn't part of the list already, then add it.
roomList: this._storeState.rooms.filter(function(room) {
@ -165,7 +169,20 @@ loop.store = loop.store || {};
* Clears the current active room.
*/
_onRoomClose: function() {
let state = this.getStoreState();
// If the room getting closed has been just created, then open the panel.
if (state.lastCreatedRoom && state.openedRoom === state.lastCreatedRoom) {
this.setStoreState({
closingNewRoom: true
});
loop.request("SetNameNewRoom");
}
// reset state for closed room
this.setStoreState({
closingNewRoom: false,
lastCreatedRoom: null,
openedRoom: null
});
},
@ -268,6 +285,11 @@ loop.store = loop.store || {};
return;
}
// Keep the token for the last created room.
this.setStoreState({
lastCreatedRoom: result.roomToken
});
this.dispatchAction(new sharedActions.CreatedRoom({
decryptedContext: result.decryptedContext,
roomToken: result.roomToken,
@ -467,6 +489,12 @@ loop.store = loop.store || {};
* Gather the list of all available rooms from the Loop API.
*/
getAllRooms: function() {
// XXX Ideally, we'd have a specific command to "start up" the room store
// to get the rooms. We should address this alongside bug 1074665.
if (this._gotAllRooms) {
return;
}
loop.request("Rooms:GetAll", null).then(function(result) {
var action;
@ -480,6 +508,8 @@ loop.store = loop.store || {};
this.dispatchAction(action);
this._gotAllRooms = true;
// We can only start listening to room events after getAll() has been
// called executed first.
this.startListeningToRoomEvents();

View File

@ -1,12 +1,4 @@
{
"ecmaFeatures": {
// since the code here is running only on known versions of
// Firefox, we can use newer ECMAscript features here
"arrowFunctions": true,
"blockBindings": true,
"destructuring": true,
"forOf": true
},
"rules": {
// This is useful for some of the tests, e.g.
// expect(new Foo()).to.Throw(/error/)

View File

@ -0,0 +1,125 @@
/* 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/. */
describe("loop.copy", function() {
"use strict";
var expect = chai.expect;
var CopyView = loop.copy.CopyView;
var l10n = navigator.mozL10n || document.mozL10n;
var TestUtils = React.addons.TestUtils;
var sandbox;
beforeEach(function() {
sandbox = LoopMochaUtils.createSandbox();
sandbox.stub(l10n, "get");
});
afterEach(function() {
loop.shared.mixins.setRootObject(window);
sandbox.restore();
LoopMochaUtils.restore();
});
describe("#init", function() {
beforeEach(function() {
sandbox.stub(React, "render");
sandbox.stub(document.mozL10n, "initialize");
});
it("should initalize L10n", function() {
loop.copy.init();
sinon.assert.calledOnce(document.mozL10n.initialize);
});
it("should render the copy view", function() {
loop.copy.init();
sinon.assert.calledOnce(React.render);
sinon.assert.calledWith(React.render, sinon.match(function(value) {
return value.type === CopyView;
}));
});
});
describe("#render", function() {
var view;
beforeEach(function() {
view = TestUtils.renderIntoDocument(React.createElement(CopyView));
});
it("should have an unchecked box", function() {
expect(view.getDOMNode().querySelector("input[type=checkbox]").checked).eql(false);
});
it("should have two buttons", function() {
expect(view.getDOMNode().querySelectorAll("button").length).eql(2);
});
});
describe("handleChanges", function() {
var view;
beforeEach(function() {
view = TestUtils.renderIntoDocument(React.createElement(CopyView));
});
it("should have default state !checked", function() {
expect(view.state.checked).to.be.false;
});
it("should have checked state after change", function() {
TestUtils.Simulate.change(view.getDOMNode().querySelector("input"), {
target: { checked: true }
});
expect(view.state.checked).to.be.true;
});
});
describe("handleClicks", function() {
var view;
beforeEach(function() {
sandbox.stub(window, "dispatchEvent");
view = TestUtils.renderIntoDocument(React.createElement(CopyView));
});
function checkDispatched(detail) {
sinon.assert.calledOnce(window.dispatchEvent);
sinon.assert.calledWith(window.dispatchEvent, sinon.match.has("detail", detail));
}
it("should dispatch accept !stop on accept", function() {
TestUtils.Simulate.click(view.getDOMNode().querySelector("button:last-child"));
checkDispatched({ accept: true, stop: false });
});
it("should dispatch !accept !stop on cancel", function() {
TestUtils.Simulate.click(view.getDOMNode().querySelector("button"));
checkDispatched({ accept: false, stop: false });
});
it("should dispatch accept stop on checked accept", function() {
TestUtils.Simulate.change(view.getDOMNode().querySelector("input"), {
target: { checked: true }
});
TestUtils.Simulate.click(view.getDOMNode().querySelector("button:last-child"));
checkDispatched({ accept: true, stop: true });
});
it("should dispatch !accept stop on checked cancel", function() {
TestUtils.Simulate.change(view.getDOMNode().querySelector("input"), {
target: { checked: true }
});
TestUtils.Simulate.click(view.getDOMNode().querySelector("button"));
checkDispatched({ accept: false, stop: true });
});
});
});

View File

@ -0,0 +1,29 @@
/* 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/. */
(function() {
"use strict";
// Create fake Components to test chrome-privileged React components.
window.Components = {
utils: {
import: function() {
return {
LoopAPI: {
sendMessageToHandler: function({ name }, callback) {
switch (name) {
case "GetLocale":
return callback("en-US");
case "GetPluralRule":
return callback(1);
default:
return callback();
}
}
}
};
}
}
};
})();

View File

@ -51,6 +51,9 @@
<script src="/add-on/shared/js/dispatcher.js"></script>
<script src="/add-on/shared/js/otSdkDriver.js"></script>
<!-- Load fake-components after loading utils to avoid chrome detection. -->
<script src="/add-on/panels/test/fake-components.js"></script>
<!-- Stores need to be loaded before the views that uses them -->
<script src="/add-on/shared/js/store.js"></script>
<script src="/add-on/panels/js/roomStore.js"></script>
@ -66,12 +69,14 @@
<script src="/add-on/panels/js/roomViews.js"></script>
<script src="/add-on/panels/js/feedbackViews.js"></script>
<script src="/add-on/panels/js/conversation.js"></script>
<script src="/add-on/panels/js/copy.js"></script>
<script src="/add-on/panels/js/panel.js"></script>
<script src="/add-on/panels/js/slideshow.js"></script>
<!-- Test scripts -->
<script src="conversationAppStore_test.js"></script>
<script src="conversation_test.js"></script>
<script src="copy_test.js"></script>
<script src="feedbackViews_test.js"></script>
<script src="panel_test.js"></script>
<script src="desktopViews_test.js"></script>
@ -83,6 +88,7 @@
// Stop the default init functions running to avoid conflicts in tests
document.removeEventListener('DOMContentLoaded', loop.panel.init);
document.removeEventListener('DOMContentLoaded', loop.conversation.init);
document.removeEventListener('DOMContentLoaded', loop.copy.init);
document.removeEventListener("DOMContentLoaded", loop.slideshow.init);
LoopMochaUtils.addErrorCheckingTests();

View File

@ -7,35 +7,105 @@ var expect = chai.expect;
describe("document.mozL10n", function() {
"use strict";
var l10nOptions;
var sandbox, l10nOptions;
beforeEach(function() {
l10nOptions = {
locale: "en-US",
getStrings: function(key) {
if (key === "plural") {
return '{"textContent":"{{num}} plural form;{{num}} plural forms"}';
sandbox = LoopMochaUtils.createSandbox();
});
afterEach(function() {
sandbox.restore();
});
describe("#initialize", function() {
beforeEach(function() {
sandbox.stub(console, "error");
});
it("should correctly set the plural form", function() {
l10nOptions = {
locale: "en-US",
getStrings: function(key) {
if (key === "plural") {
return '{"textContent":"{{num}} form;{{num}} forms;{{num}} form 2"}';
}
return '{"textContent":"' + key + '"}';
},
pluralRule: 14
};
document.mozL10n.initialize(l10nOptions);
expect(document.mozL10n.get("plural", { num: 39 })).eql("39 form 2");
});
it("should log an error if the rule number is invalid", function() {
l10nOptions = {
locale: "en-US",
getStrings: function(key) {
if (key === "plural") {
return '{"textContent":"{{num}} form;{{num}} forms;{{num}} form 2"}';
}
return '{"textContent":"' + key + '"}';
},
pluralRule: NaN
};
document.mozL10n.initialize(l10nOptions);
sinon.assert.calledOnce(console.error);
});
it("should use rule 0 if the rule number is invalid", function() {
l10nOptions = {
locale: "en-US",
getStrings: function(key) {
if (key === "plural") {
return '{"textContent":"{{num}} form;{{num}} forms;{{num}} form 2"}';
}
return '{"textContent":"' + key + '"}';
},
pluralRule: NaN
};
document.mozL10n.initialize(l10nOptions);
expect(document.mozL10n.get("plural", { num: 39 })).eql("39 form");
});
});
describe("#get", function() {
beforeEach(function() {
l10nOptions = {
locale: "en-US",
getStrings: function(key) {
if (key === "plural") {
return '{"textContent":"{{num}} plural form;{{num}} plural forms"}';
}
return '{"textContent":"' + key + '"}';
},
getPluralForm: function(num, string) {
return string.split(";")[num === 0 ? 0 : 1];
}
};
return '{"textContent":"' + key + '"}';
},
getPluralForm: function(num, string) {
return string.split(";")[num === 0 ? 0 : 1];
}
};
document.mozL10n.initialize(l10nOptions);
});
document.mozL10n.initialize(l10nOptions);
});
it("should get a simple string", function() {
expect(document.mozL10n.get("test")).eql("test");
});
it("should get a simple string", function() {
expect(document.mozL10n.get("test")).eql("test");
});
it("should get a plural form", function() {
expect(document.mozL10n.get("plural", { num: 10 })).eql("10 plural forms");
});
it("should get a plural form", function() {
expect(document.mozL10n.get("plural", { num: 10 })).eql("10 plural forms");
});
it("should correctly get a plural form for num = 0", function() {
expect(document.mozL10n.get("plural", { num: 0 })).eql("0 plural form");
it("should correctly get a plural form for num = 0", function() {
expect(document.mozL10n.get("plural", { num: 0 })).eql("0 plural form");
});
});
});

View File

@ -34,7 +34,10 @@ describe("loop.panel", function() {
close: sandbox.stub(),
addEventListener: function() {},
removeEventListener: function() {},
document: { addEventListener: function() {} },
document: {
addEventListener: function() {},
removeEventListener: function() {}
},
setTimeout: function(callback) { callback(); }
};
loop.shared.mixins.setRootObject(fakeWindow);
@ -77,6 +80,7 @@ describe("loop.panel", function() {
GetHasEncryptionKey: function() { return true; },
HangupAllChatWindows: function() {},
IsMultiProcessActive: sinon.stub(),
IsTabShareable: sinon.stub(),
LoginToFxA: sinon.stub(),
LogoutFromFxA: sinon.stub(),
NotifyUITour: sinon.stub(),
@ -300,6 +304,24 @@ describe("loop.panel", function() {
.to.have.length.of(0);
});
it("should reset renaming state when closing the panel", function() {
var view = createTestPanelView();
view.setState({
renameRoom: {
name: "fakeName",
token: "fakeToken"
}
});
view._onDocumentVisibilityChanged({
target: {
hidden: true
}
});
expect(view.state.renameRoom).eql(null);
});
describe("AccountLink", function() {
it("should trigger the FxA sign in/up process when clicking the link",
function() {
@ -660,7 +682,6 @@ describe("loop.panel", function() {
}).to.not.Throw();
});
it("should render a GettingStarted view when gettingStarted.latestFTUVersion is less than FTU_VERSION", function() {
loop.storedRequests["GetLoopPref|gettingStarted.latestFTUVersion"] = 0;
var view = createTestPanelView();
@ -712,6 +733,53 @@ describe("loop.panel", function() {
}).to.not.Throw();
});
it("should render an RenameRoomView when a new room is closed",
function() {
roomStore.setStoreState({
openedRoom: "fakeToken"
});
var view = createTestPanelView();
roomStore.setStoreState({
closingNewRoom: true
});
expect(function() {
TestUtils.findRenderedComponentWithType(view, loop.panel.RenameRoomView);
}).to.not.Throw();
});
it("should not render an RenameRoomView when no room open",
function() {
roomStore.setStoreState({
openedRoom: null
});
var view = createTestPanelView();
roomStore.setStoreState({
closingNewRoom: true
});
expect(function() {
TestUtils.findRenderedComponentWithType(view, loop.panel.RenameRoomView);
}).to.Throw();
});
it("should not render an RenameRoomView when no room closing",
function() {
roomStore.setStoreState({
openedRoom: "fakeToken"
});
var view = createTestPanelView();
roomStore.setStoreState({
closingNewRoom: false
});
expect(function() {
TestUtils.findRenderedComponentWithType(view, loop.panel.RenameRoomView);
}).to.Throw();
});
});
describe("GettingStartedView", function() {
@ -1116,6 +1184,89 @@ describe("loop.panel", function() {
expect(roomEntry.getDOMNode().textContent).eql("https://fakeurl.com");
});
});
describe("Room entry click", function() {
var roomEntry;
beforeEach(function() {
// Stub to prevent warnings due to stores not being set up to handle
// the actions we are triggering.
sandbox.stub(dispatcher, "dispatch");
});
it("should require MetaData", function() {
roomEntry = mountRoomEntry({
isOpenedRoom: false,
room: new loop.store.Room(roomData)
});
roomEntry.handleClickEntry(fakeEvent);
sinon.assert.calledOnce(requestStubs.GetSelectedTabMetadata);
});
it("should close the Panel", function() {
roomEntry = mountRoomEntry({
isOpenedRoom: false,
room: new loop.store.Room(roomData)
});
sandbox.stub(roomEntry, "closeWindow");
roomEntry.handleClickEntry(fakeEvent);
sinon.assert.calledOnce(roomEntry.closeWindow);
});
it("should dispatch the OpenRoom action", function() {
roomEntry = mountRoomEntry({
isOpenedRoom: false,
room: new loop.store.Room(roomData)
});
roomEntry.handleClickEntry(fakeEvent);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.OpenRoom({
roomToken: roomData.roomToken
}));
});
// if current URL same as ROOM, dont open TAB
it("should NOT open new tab if we already in same URL", function() {
requestStubs.GetSelectedTabMetadata.returns({
url: "fakeURL"
});
roomData.decryptedContext.urls = [{
location: "fakeURL"
}];
roomEntry = mountRoomEntry({
isOpenedRoom: false,
room: new loop.store.Room(roomData)
});
roomEntry.handleClickEntry(fakeEvent);
sinon.assert.calledOnce(requestStubs.GetSelectedTabMetadata);
sinon.assert.notCalled(requestStubs.OpenURL);
});
it("should open new tab if different URL", function() {
requestStubs.GetSelectedTabMetadata.returns({
url: "notTheSameURL"
});
roomData.decryptedContext.urls = [{
location: "fakeURL"
}];
roomEntry = mountRoomEntry({
isOpenedRoom: false,
room: new loop.store.Room(roomData)
});
roomEntry.handleClickEntry(fakeEvent);
sinon.assert.calledOnce(requestStubs.GetSelectedTabMetadata);
sinon.assert.calledOnce(requestStubs.OpenURL);
sinon.assert.calledWithExactly(requestStubs.OpenURL, "fakeURL");
});
});
});
describe("loop.panel.RoomList", function() {
@ -1353,7 +1504,6 @@ describe("loop.panel", function() {
expect(topPosTest).to.equal(false);
});
});
});
@ -1383,6 +1533,49 @@ describe("loop.panel", function() {
}, extraProps)));
}
it("should open new tab when Starting Conversation on a non-remote tab",
function() {
LoopMochaUtils.stubLoopRequest({
IsTabShareable: function() {
return false;
}
});
var view = createTestComponent({
inRoom: false,
pendingOperation: false
});
// Simulate being visible
view.onDocumentVisible();
// Simulate click on button
var node = view.getDOMNode();
TestUtils.Simulate.click(node.querySelector(".new-room-button"));
sinon.assert.calledOnce(requestStubs.OpenURL);
sinon.assert.calledWithExactly(requestStubs.OpenURL, "about:home");
});
it("should stay in same tab when Starting Conversation on a remote tab",
function() {
LoopMochaUtils.stubLoopRequest({
IsTabShareable: function() {
return true;
}
});
var view = createTestComponent({
inRoom: false,
pendingOperation: false
});
// Simulate being visible
view.onDocumentVisible();
// Simulate click on button
var node = view.getDOMNode();
TestUtils.Simulate.click(node.querySelector(".new-room-button"));
sinon.assert.notCalled(requestStubs.OpenURL);
});
it("should dispatch a CreateRoom action with context when clicking on the " +
"Start a conversation button", function() {
var favicon = "data:image/x-icon;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
@ -1494,7 +1687,7 @@ describe("loop.panel", function() {
});
});
describe("ConversationDropdown", function() {
describe("loop.panel.ConversationDropdown", function() {
var view;
function createTestComponent() {
@ -1541,7 +1734,7 @@ describe("loop.panel", function() {
});
});
describe("RoomEntryContextButtons", function() {
describe("loop.panel.RoomEntryContextButtons", function() {
var view, dispatcher;
function createTestComponent(extraProps) {
@ -1613,7 +1806,7 @@ describe("loop.panel", function() {
});
});
describe("SharePanelView", function() {
describe("loop.panel.SharePanelView", function() {
var view, dispatcher, roomStore;
function createTestComponent(extraProps) {
@ -1760,4 +1953,114 @@ describe("loop.panel", function() {
sinon.assert.calledOnce(view.props.onSharePanelDisplayChange);
});
});
describe("loop.panel.RenameRoomView", function() {
var dispatcher,
view;
function mountTestComponent(name, token) {
return TestUtils.renderIntoDocument(
React.createElement(
loop.panel.RenameRoomView, {
dispatcher: dispatcher,
roomName: name,
roomToken: token
}
)
);
}
beforeEach(function() {
dispatcher = new loop.Dispatcher();
sandbox.stub(dispatcher, "dispatch");
});
it("should highlight container and select text when input gets the focus",
function() {
view = mountTestComponent("fakeName", "fakeToken");
var input = view.getDOMNode().querySelector("input");
sandbox.stub(input, "select");
TestUtils.Simulate.focus(input);
expect(view.state.focused).eql(true);
sinon.assert.notCalled(dispatcher.dispatch);
sinon.assert.called(input.select);
});
it("should stop highlighting container when input lose focus", function() {
view = mountTestComponent("fakeName", "fakeToken");
var input = view.getDOMNode().querySelector("input");
TestUtils.Simulate.focus(input);
TestUtils.Simulate.blur(input);
expect(view.state.focused).eql(false);
sinon.assert.notCalled(dispatcher.dispatch);
});
it("should update the room name when OK button is pressed", function() {
view = mountTestComponent("fakeName", "fakeToken");
var input = view.getDOMNode().querySelector("input");
var button = view.getDOMNode().querySelector(".rename-button");
input.value = "notTheFakeName";
TestUtils.Simulate.change(input);
TestUtils.Simulate.click(button);
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomContext({
roomToken: "fakeToken",
newRoomName: "notTheFakeName"
})
);
});
it("should NOT update the room name when name is unchanged" +
" and OK button is pressed", function() {
view = mountTestComponent("fakeName", "fakeToken");
var input = view.getDOMNode().querySelector("input");
var button = view.getDOMNode().querySelector(".rename-button");
input.value = "fakeName";
TestUtils.Simulate.change(input);
TestUtils.Simulate.click(button);
sinon.assert.notCalled(dispatcher.dispatch);
});
it("should update the room name when Enter key is pressed", function() {
view = mountTestComponent("fakeName", "fakeToken");
var input = view.getDOMNode().querySelector("input");
input.value = "notTheFakeName";
TestUtils.Simulate.change(input);
TestUtils.Simulate.keyDown(input, {
key: "Enter",
which: 13
});
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch, new sharedActions.UpdateRoomContext({
roomToken: "fakeToken",
newRoomName: "notTheFakeName"
}));
});
it("should NOT update the room name when name is unchanged" +
" and Enter key is pressed", function() {
view = mountTestComponent("fakeName", "fakeToken");
var input = view.getDOMNode().querySelector("input");
input.value = "fakeName";
TestUtils.Simulate.change(input);
TestUtils.Simulate.keyDown(input, {
key: "Enter",
which: 13
});
sinon.assert.notCalled(dispatcher.dispatch);
});
});
});

View File

@ -59,6 +59,7 @@ describe("loop.store.RoomStore", function() {
if (prefName === "debug.dispatcher") {
return false;
}
return true;
},
NotifyUITour: function() {},
OpenURL: sinon.stub(),
@ -73,6 +74,7 @@ describe("loop.store.RoomStore", function() {
"Rooms:Rename": sinon.stub(),
"Rooms:PushSubscription": sinon.stub(),
"SetLoopPref": sinon.stub(),
"SetNameNewRoom": sinon.stub(),
TelemetryAddValue: sinon.stub()
});
@ -178,6 +180,49 @@ describe("loop.store.RoomStore", function() {
});
describe("close", function() {
it("should request rename if closing room is last created", function() {
store.setStoreState({
openedRoom: "fakeToken",
lastCreatedRoom: "fakeToken"
});
sinon.stub(store, "setStoreState");
LoopMochaUtils.publish("Rooms:Close");
sinon.assert.calledTwice(store.setStoreState);
sinon.assert.calledWithExactly(store.setStoreState.getCall(0),
{ closingNewRoom: true });
sinon.assert.calledOnce(requestStubs.SetNameNewRoom);
});
it("should not request rename if last created and opened room are null", function() {
store.setStoreState({
lastCreatedRoom: null,
openedRoom: null
});
sinon.stub(store, "setStoreState");
LoopMochaUtils.publish("Rooms:Close");
sinon.assert.notCalled(requestStubs.SetNameNewRoom);
});
it("should end up updating closingNewRoom state to false", function() {
store.setStoreState({ closingNewRoom: "true" });
LoopMochaUtils.publish("Rooms:Close");
expect(store.getStoreState().closingNewRoom).to.eql(false);
});
it("should update the lastCreatedRoom state to null", function() {
store.setStoreState({ lastCreatedRoom: "fake1234" });
LoopMochaUtils.publish("Rooms:Close");
expect(store.getStoreState().lastCreatedRoom).to.eql(null);
});
it("should update the openedRoom state to null", function() {
store.setStoreState({ openedRoom: "fake1234" });
LoopMochaUtils.publish("Rooms:Close");
@ -287,6 +332,12 @@ describe("loop.store.RoomStore", function() {
expect(store.getStoreState().pendingCreation).eql(true);
});
it("should store the room token as the last created", function() {
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
expect(store.getStoreState().lastCreatedRoom).eql("fakeToken");
});
it("should dispatch a CreatedRoom action once the operation is done",
function() {
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
@ -357,9 +408,9 @@ describe("loop.store.RoomStore", function() {
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
"LOOP_ROOM_CREATE", 1);
});
});
});
describe("#createdRoom", function() {
describe("#createdRoom", function() {
beforeEach(function() {
sandbox.stub(dispatcher, "dispatch");
});
@ -608,7 +659,6 @@ describe("loop.store.RoomStore", function() {
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0),
"LOOP_SHARING_ROOM_URL", 4);
});
});
describe("#shareRoomUrl", function() {
@ -699,6 +749,15 @@ describe("loop.store.RoomStore", function() {
expect(store.getStoreState().rooms).to.have.length.of(3);
});
it("should not fetch the room list if called a second time", function() {
requestStubs["Rooms:GetAll"].returns(fakeRoomList);
store.getAllRooms(new sharedActions.GetAllRooms());
store.getAllRooms(new sharedActions.GetAllRooms());
sinon.assert.calledOnce(requestStubs["Rooms:GetAll"]);
});
it("should order the room list using ctime desc", function() {
requestStubs["Rooms:GetAll"].returns(fakeRoomList);

View File

@ -120,7 +120,7 @@
if ((ret == undefined) || (ret == "")) {
// Display a message in the error console
console.error("Index #" + index + " of '" + str + "' for value " + num +
" is invalid -- plural rule #" + aRuleNum);
" is invalid -- plural rule #" + ret);
// Default to the first entry (which might be empty, but not undefined).
ret = words[0];
@ -210,7 +210,15 @@
// Fallback to a working - synchronous - implementation of retrieving the
// plural form of a string.
if (!gL10nDetails.getPluralForm && ("pluralRule" in gL10nDetails)) {
gPluralFunc = kPluralFunctions[gL10nDetails.pluralRule][1];
var ruleNum = gL10nDetails.pluralRule;
// Default to "all plural" if the value is out of bounds or invalid
if (ruleNum < 0 || ruleNum >= kPluralFunctions.length || isNaN(ruleNum)) {
console.error(["Invalid rule number: ", ruleNum, " -- defaulting to 0"]);
ruleNum = 0;
}
gPluralFunc = kPluralFunctions[ruleNum][1];
gL10nDetails.getPluralForm = fallbackGetPluralForm;
}

View File

@ -17,6 +17,9 @@ pref("loop.retry_delay.start", 60000);
pref("loop.retry_delay.limit", 300000);
pref("loop.ping.interval", 1800000);
pref("loop.ping.timeout", 10000);
pref("loop.copy.shown", false);
pref("loop.copy.throttler", "copy.loop.services.mozilla.com");
pref("loop.copy.ticket", -1);
pref("loop.debug.loglevel", "Error");
pref("loop.debug.dispatcher", false);
pref("loop.debug.sdk", false);

View File

@ -545,10 +545,6 @@ html[dir="rtl"] .context-content {
}
.context-wrapper {
border: 2px solid #ebebeb;
border-radius: 4px;
background: #fafafa;
padding: 1.1rem .8rem;
/* Use the flex row mode to position the elements next to each other. */
display: flex;
flex-flow: row nowrap;
@ -556,6 +552,15 @@ html[dir="rtl"] .context-content {
text-decoration: none;
}
.context-content > .context-wrapper {
border: 2px solid #ebebeb;
border-radius: 4px;
background: #fafafa;
padding: 1.1rem .8rem;
font-size: 1.3rem;
line-height: 1.4rem;
}
.context-wrapper > .context-preview {
float: left;
/* 16px is standard height/width for a favicon */

View File

@ -196,6 +196,7 @@ loop.shared.actions = (function() {
* A stream from local or remote media has been created.
*/
MediaStreamCreated: Action.define("mediaStreamCreated", {
hasAudio: Boolean,
hasVideo: Boolean,
isLocal: Boolean,
srcMediaElement: Object

View File

@ -109,6 +109,7 @@ loop.store.ActiveRoomStore = (function(mozL10n) {
"localVideoDimensions",
"mediaConnected",
"receivingScreenShare",
"remoteAudioEnabled",
"remotePeerDisconnected",
"remoteSrcMediaElement",
"remoteVideoDimensions",
@ -130,6 +131,7 @@ loop.store.ActiveRoomStore = (function(mozL10n) {
roomState: ROOM_STATES.INIT,
audioMuted: false,
videoMuted: false,
remoteAudioEnabled: false,
remoteVideoEnabled: false,
failureReason: undefined,
// Whether or not Firefox can handle this room in the conversation
@ -808,6 +810,7 @@ loop.store.ActiveRoomStore = (function(mozL10n) {
mediaStreamCreated: function(actionData) {
if (actionData.isLocal) {
this.setStoreState({
localAudioEnabled: actionData.hasAudio,
localVideoEnabled: actionData.hasVideo,
localSrcMediaElement: actionData.srcMediaElement
});
@ -815,6 +818,7 @@ loop.store.ActiveRoomStore = (function(mozL10n) {
}
this.setStoreState({
remoteAudioEnabled: actionData.hasAudio,
remoteVideoEnabled: actionData.hasVideo,
remoteSrcMediaElement: actionData.srcMediaElement
});
@ -1099,7 +1103,9 @@ loop.store.ActiveRoomStore = (function(mozL10n) {
* @param {sharedActions.LeaveRoom} actionData
*/
leaveRoom: function(actionData) {
this._leaveRoom(ROOM_STATES.ENDED, false, actionData && actionData.windowStayingOpen);
this._leaveRoom(ROOM_STATES.ENDED,
false,
actionData && actionData.windowStayingOpen);
},
/**

View File

@ -1,5 +1,3 @@
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
/* 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/. */
@ -91,10 +89,14 @@ loop.shared.views.LinkifiedTextView = function () {
// Bug 1196143 - formatURL sanitizes(decodes) the URL from IDN homographic attacks.
sanitizeURL = loop.shared.utils.formatURL(result[0]);
if (sanitizeURL && sanitizeURL.location) {
var linkAttributes = this._generateLinkAttributes(sanitizeURL.location);
elements.push(React.createElement(
"a",
_extends({}, this._generateLinkAttributes(sanitizeURL.location), {
key: reactElementsCounter++ }),
{ href: linkAttributes.href,
key: reactElementsCounter++,
onClick: linkAttributes.onClick,
rel: linkAttributes.rel,
target: linkAttributes.target },
sanitizeURL.location
));
} else {

View File

@ -640,6 +640,7 @@ loop.OTSdkDriver = (function() {
sdkSubscriberObject.on("videoDisabled", this._onVideoDisabled.bind(this));
this.dispatcher.dispatch(new sharedActions.MediaStreamCreated({
hasAudio: sdkSubscriberObject.stream[STREAM_PROPERTIES.HAS_AUDIO],
hasVideo: sdkSubscriberObject.stream[STREAM_PROPERTIES.HAS_VIDEO],
isLocal: false,
srcMediaElement: sdkSubscriberVideo
@ -850,8 +851,10 @@ loop.OTSdkDriver = (function() {
var sdkLocalVideo = this._mockPublisherEl.querySelector("video");
var hasVideo = event.stream[STREAM_PROPERTIES.HAS_VIDEO];
var hasAudio = event.stream[STREAM_PROPERTIES.HAS_AUDIO];
this.dispatcher.dispatch(new sharedActions.MediaStreamCreated({
hasAudio: hasAudio,
hasVideo: hasVideo,
isLocal: true,
srcMediaElement: sdkLocalVideo
@ -998,20 +1001,28 @@ loop.OTSdkDriver = (function() {
// Nothing to do for the success case.
return;
}
if (!(error.message && error.message === "DENIED")) {
// We free up the publisher here in case the store wants to try
// grabbing the media again.
if (this.publisher) {
this.publisher.off("accessAllowed accessDenied accessDialogOpened streamCreated");
this.publisher.destroy();
delete this.publisher;
delete this._mockPublisherEl;
}
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
}));
this._notifyMetricsEvent("sdk.exception." + error.code + "." + error.message);
if (error.message && error.message === "DENIED") {
// In the DENIED case, this will be handled by _onPublishDenied.
return;
}
if (!this.publisher) {
return;
}
// We free up the publisher here in case the store wants to try
// grabbing the media again.
this.publisher.off("accessAllowed accessDenied accessDialogOpened streamCreated");
this.publisher.destroy();
delete this.publisher;
delete this._mockPublisherEl;
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
}));
// Exceptions are logged via the _onOTException handler.
},
/**
@ -1030,6 +1041,11 @@ loop.OTSdkDriver = (function() {
delete this._mockPublisherEl;
},
/**
* Handles exceptions being raised by the OT SDK.
*
* @param {OT.Event} event
*/
_onOTException: function(event) {
switch (event.code) {
case OT.ExceptionCodes.PUBLISHER_ICE_WORKFLOW_FAILED:
@ -1047,6 +1063,17 @@ loop.OTSdkDriver = (function() {
// attempt failed.
this._notifyMetricsEvent("sdk.exception." + event.code);
break;
case OT.ExceptionCodes.UNABLE_TO_PUBLISH:
// Don't report errors for GetUserMedia events as these are expected if
// the user denies the prompt.
if (event.message !== "GetUserMedia") {
var baseException = "sdk.exception.";
if (event.target && event.target === this.screenshare) {
baseException += "screen.";
}
this._notifyMetricsEvent(baseException + event.code + "." + event.message);
}
break;
default:
this._notifyMetricsEvent("sdk.exception." + event.code);
break;
@ -1164,7 +1191,8 @@ loop.OTSdkDriver = (function() {
this.dispatcher.dispatch(new sharedActions.ScreenSharingState({
state: SCREEN_SHARE_STATES.INACTIVE
}));
this._notifyMetricsEvent("sdk.exception.screen." + error.code + "." + error.message);
// Exceptions are logged via the _onOTException handler.
},
/**

View File

@ -1,5 +1,3 @@
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
/* 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/. */
@ -60,13 +58,6 @@ loop.shared.views.chat = function (mozL10n) {
"text-chat-notif": this.props.contentType === CHAT_CONTENT_TYPES.NOTIFICATION
});
var optionalProps = {};
if (loop.shared.utils.isDesktop()) {
optionalProps.linkClickHandler = function (url) {
loop.request("OpenURL", url);
};
}
if (this.props.contentType === CHAT_CONTENT_TYPES.CONTEXT_TILE) {
return React.createElement(
"div",
@ -99,11 +90,19 @@ loop.shared.views.chat = function (mozL10n) {
);
}
var linkClickHandler;
if (loop.shared.utils.isDesktop()) {
linkClickHandler = function (url) {
loop.request("OpenURL", url);
};
}
return React.createElement(
"div",
{ className: classes },
React.createElement(sharedViews.LinkifiedTextView, _extends({}, optionalProps, {
rawText: this.props.message })),
React.createElement(sharedViews.LinkifiedTextView, {
linkClickHandler: linkClickHandler,
rawText: this.props.message }),
React.createElement("span", { className: "text-chat-arrow" }),
this.props.showTimestamp ? this._renderTimestamp() : null
);
@ -216,11 +215,6 @@ loop.shared.views.chat = function (mozL10n) {
React.createElement(
"div",
{ className: "text-chat-scroller" },
loop.shared.utils.isDesktop() ? null : React.createElement(
"p",
{ className: "welcome-message" },
mozL10n.get("rooms_welcome_text_chat_label", { clientShortname: mozL10n.get("clientShortname2") })
),
this.props.messageList.map(function (entry, i) {
if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
if (!this.props.showInitialContext) {

View File

@ -427,6 +427,37 @@ if (inChrome) {
}
}
/**
* Formats a url for context url links.
*
* @param {String} url The url to format.
* @return {Object} Sanitized url object containing the hostname,
* full location and protocol
*/
function formatSanitizedContextURL(url) {
if (!url) {
return null;
}
// Bug 1196143 - formatURL sanitizes(decodes) the URL from IDN homographic attacks.
// Try catch to not produce output if invalid url
try {
var sanitizedURL = loop.shared.utils.formatURL(url, true);
} catch (ex) {
return null;
}
// Only allow specific types of URLs.
if (!sanitizedURL ||
(sanitizedURL.protocol !== "http:" &&
sanitizedURL.protocol !== "https:" &&
sanitizedURL.protocol !== "ftp:")) {
return null;
}
return sanitizedURL;
}
/**
* Generates and opens a mailto: url with call URL information prefilled.
* Note: This only works for Desktop.
@ -795,6 +826,7 @@ if (inChrome) {
composeCallUrlEmail: composeCallUrlEmail,
findParentNode: findParentNode,
formatDate: formatDate,
formatSanitizedContextURL: formatSanitizedContextURL,
formatURL: formatURL,
getBoolPreference: getBoolPreference,
getOS: getOS,

View File

@ -1,5 +1,3 @@
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
/* 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/. */
@ -54,6 +52,7 @@ loop.shared.views = function (_, mozL10n) {
propTypes: {
action: React.PropTypes.func.isRequired,
disabled: React.PropTypes.bool,
muted: React.PropTypes.bool.isRequired,
scope: React.PropTypes.string.isRequired,
title: React.PropTypes.string,
@ -62,7 +61,11 @@ loop.shared.views = function (_, mozL10n) {
},
getDefaultProps: function () {
return { muted: false, visible: true };
return {
disabled: false,
muted: false,
visible: true
};
},
handleClick: function () {
@ -77,8 +80,9 @@ loop.shared.views = function (_, mozL10n) {
"media-control": true,
"transparent-button": true,
"local-media": this.props.scope === "local",
"muted": this.props.muted,
"hide": !this.props.visible
"muted": this.props.muted || this.props.disabled,
"hide": !this.props.visible,
"disabled": this.props.disabled
};
classesObj["btn-mute-" + this.props.type] = true;
return cx(classesObj);
@ -89,7 +93,7 @@ loop.shared.views = function (_, mozL10n) {
return this.props.title;
}
var prefix = this.props.muted ? "unmute" : "mute";
var prefix = this.props.muted || this.props.disabled ? "unmute" : "mute";
var suffix = this.props.type === "video" ? "button_title2" : "button_title";
var msgId = [prefix, this.props.scope, this.props.type, suffix].join("_");
return mozL10n.get(msgId);
@ -235,16 +239,28 @@ loop.shared.views = function (_, mozL10n) {
displayName: "AudioMuteButton",
propTypes: {
disabled: React.PropTypes.bool,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
muted: React.PropTypes.bool.isRequired
},
getDefaultProps: function () {
return {
disabled: false
};
},
toggleAudio: function () {
if (this.props.disabled) {
return;
}
this.props.dispatcher.dispatch(new sharedActions.SetMute({ type: "audio", enabled: this.props.muted }));
},
render: function () {
return React.createElement(MediaControlButton, { action: this.toggleAudio,
disabled: this.props.disabled,
muted: this.props.muted,
scope: "local",
type: "audio" });
@ -255,17 +271,29 @@ loop.shared.views = function (_, mozL10n) {
displayName: "VideoMuteButton",
propTypes: {
disabled: React.PropTypes.bool,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
muted: React.PropTypes.bool.isRequired
},
getDefaultProps: function () {
return {
disabled: false
};
},
toggleVideo: function () {
if (this.props.disabled) {
return;
}
this.props.dispatcher.dispatch(new sharedActions.SetMute({ type: "video", enabled: this.props.muted }));
},
render: function () {
return React.createElement(MediaControlButton, { action: this.toggleVideo,
muted: this.props.muted,
disabled: this.props.disabled,
muted: this.props.muted || this.props.disabled,
scope: "local",
type: "video" });
}
@ -560,6 +588,63 @@ loop.shared.views = function (_, mozL10n) {
}
});
/**
* Renders the a href link for context.
*
* @property {Boolean} allowClick Set to true to allow the url to be clicked.
* @property {String} description The description for the context url.
* @property {Function} handleClick Function for handling click operation when
* context link is clicked
* @property {String} thumbnail The thumbnail url (expected to be a data url) to
* display. If not specified, a fallback url will be
* shown.
* @property {String} url The url to be displayed. If not present or invalid,
* the context link will not be clickable
*/
var ContextUrlLink = React.createClass({
displayName: "ContextUrlLink",
mixins: [React.addons.PureRenderMixin],
propTypes: {
allowClick: React.PropTypes.bool.isRequired,
children: React.PropTypes.node,
description: React.PropTypes.string,
handleClick: React.PropTypes.func,
url: React.PropTypes.string
},
render: function () {
var sanitizedURL = loop.shared.utils.formatSanitizedContextURL(this.props.url);
var opts = {};
opts.classNames = classNames({
"context-wrapper": true,
"clicks-allowed": this.props.allowClick
});
if (this.props.allowClick && sanitizedURL) {
opts.href = sanitizedURL.location;
}
if (this.props.handleClick) {
opts.onClick = this.props.handleClick;
}
if (this.props.description) {
opts.title = this.props.description;
}
return React.createElement(
"a",
{ className: opts.classNames,
href: opts.href,
onClick: opts.onClick,
rel: "noreferrer",
target: "_blank",
title: opts.title },
this.props.children
);
}
});
/**
* Renders a url that's part of context on the display.
*
@ -580,7 +665,7 @@ loop.shared.views = function (_, mozL10n) {
propTypes: {
allowClick: React.PropTypes.bool.isRequired,
description: React.PropTypes.string.isRequired,
description: React.PropTypes.string,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
thumbnail: React.PropTypes.string,
url: React.PropTypes.string
@ -600,49 +685,34 @@ loop.shared.views = function (_, mozL10n) {
},
render: function () {
// Bug 1196143 - formatURL sanitizes(decodes) the URL from IDN homographic attacks.
// Try catch to not produce output if invalid url
try {
var sanitizedURL = loop.shared.utils.formatURL(this.props.url, true);
} catch (ex) {
return null;
}
// Only allow specific types of URLs.
if (!sanitizedURL || sanitizedURL.protocol !== "http:" && sanitizedURL.protocol !== "https:" && sanitizedURL.protocol !== "ftp:") {
return null;
}
var description = this.props.description || null;
var thumbnail = this.props.thumbnail;
var url = this.props.url || null;
var sanitizedURL = loop.shared.utils.formatSanitizedContextURL(url) || {};
var hostname = sanitizedURL.hostname || null;
if (!thumbnail) {
thumbnail = "shared/img/icons-16x16.svg#globe";
}
var wrapperClasses = classNames({
"context-wrapper": true,
"clicks-allowed": this.props.allowClick
});
return React.createElement(
"div",
{ className: "context-content" },
React.createElement(
"a",
{ className: wrapperClasses,
href: this.props.allowClick ? this.props.url : null,
onClick: this.handleLinkClick,
rel: "noreferrer",
target: "_blank" },
ContextUrlLink,
{ allowClick: this.props.allowClick,
description: description,
handleClick: this.handleLinkClick,
url: url },
React.createElement("img", { className: "context-preview", src: thumbnail }),
React.createElement(
"span",
{ className: "context-info" },
this.props.description,
description,
React.createElement(
"span",
{ className: "context-url" },
sanitizedURL.hostname
hostname
)
)
)
@ -834,11 +904,6 @@ loop.shared.views = function (_, mozL10n) {
return React.createElement("div", { className: "no-video" });
}
var optionalProps = {};
if (this.props.posterUrl) {
optionalProps.poster = this.props.posterUrl;
}
// For now, always mute media. For local media, we should be muted anyway,
// as we don't want to hear ourselves speaking.
//
@ -853,9 +918,9 @@ loop.shared.views = function (_, mozL10n) {
{ className: "remote-video-box" },
this.state.videoElementSize && this.props.shareCursor ? React.createElement(RemoteCursorView, {
videoElementSize: this.state.videoElementSize }) : null,
React.createElement("video", _extends({}, optionalProps, {
className: this.props.mediaType + "-video",
muted: true }))
React.createElement("video", { className: this.props.mediaType + "-video",
muted: true,
poster: this.props.posterUrl })
);
}
});
@ -1232,6 +1297,7 @@ loop.shared.views = function (_, mozL10n) {
Button: Button,
ButtonGroup: ButtonGroup,
Checkbox: Checkbox,
ContextUrlLink: ContextUrlLink,
ContextUrlView: ContextUrlView,
ConversationToolbar: ConversationToolbar,
HangUpControlButton: HangUpControlButton,

View File

@ -1352,6 +1352,7 @@ describe("loop.store.ActiveRoomStore", function() {
expect(store.getStoreState()).to.not.have.property("localSrcMediaElement");
store.mediaStreamCreated(new sharedActions.MediaStreamCreated({
hasAudio: false,
hasVideo: false,
isLocal: true,
srcMediaElement: fakeStreamElement
@ -1363,17 +1364,20 @@ describe("loop.store.ActiveRoomStore", function() {
it("should set the local video enabled", function() {
store.setStoreState({
localAudioEnabled: false,
localVideoEnabled: false,
remoteVideoEnabled: false
});
store.mediaStreamCreated(new sharedActions.MediaStreamCreated({
hasAudio: true,
hasVideo: true,
isLocal: true,
srcMediaElement: fakeStreamElement
}));
expect(store.getStoreState().localVideoEnabled).eql(true);
expect(store.getStoreState().localAudioEnabled).eql(true);
expect(store.getStoreState().remoteVideoEnabled).eql(false);
});
@ -1381,6 +1385,7 @@ describe("loop.store.ActiveRoomStore", function() {
expect(store.getStoreState()).to.not.have.property("remoteSrcMediaElement");
store.mediaStreamCreated(new sharedActions.MediaStreamCreated({
hasAudio: false,
hasVideo: false,
isLocal: false,
srcMediaElement: fakeStreamElement
@ -1397,6 +1402,7 @@ describe("loop.store.ActiveRoomStore", function() {
});
store.mediaStreamCreated(new sharedActions.MediaStreamCreated({
hasAudio: true,
hasVideo: true,
isLocal: false,
srcMediaElement: fakeStreamElement
@ -1404,6 +1410,7 @@ describe("loop.store.ActiveRoomStore", function() {
expect(store.getStoreState().localVideoEnabled).eql(false);
expect(store.getStoreState().remoteVideoEnabled).eql(true);
expect(store.getStoreState().remoteAudioEnabled).eql(true);
});
});

View File

@ -34,6 +34,7 @@ REDIRECTIONS = {
"/shared/vendor": "/chrome/content/shared/vendor",
"/add-on/panels/vendor": "/chrome/content/panels/vendor",
"/add-on/panels/js": "/chrome/content/panels/js",
"/add-on/panels/test": "/chrome/content/panels/test",
"/add-on/shared/js": "/chrome/content/shared/js",
"/add-on/shared/vendor": "/chrome/content/shared/vendor",
}

View File

@ -76,7 +76,7 @@ describe("loop.shared.views.LinkifiedTextView", function() {
var markup = renderToMarkup("http://example.com", {});
expect(markup).to.equal(
'<p><a href="http://example.com/" target="_blank" rel="noreferrer">http://example.com/</a></p>');
'<p><a href="http://example.com/" rel="noreferrer" target="_blank">http://example.com/</a></p>');
});
});
@ -94,7 +94,7 @@ describe("loop.shared.views.LinkifiedTextView", function() {
var markup = renderToMarkup("http://example.com", {});
expect(markup).to.equal(
'<p><a href="http://example.com/" target="_blank" rel="noreferrer">http://example.com/</a></p>');
'<p><a href="http://example.com/" rel="noreferrer" target="_blank">http://example.com/</a></p>');
});
});

View File

@ -154,6 +154,7 @@ var LoopMochaUtils = (function(global, _) {
}
invokeListenerCallbacks({ data: [seq, result] });
}
return undefined;
}
/**

View File

@ -223,25 +223,12 @@ describe("loop.OTSdkDriver", function() {
it("should dispatch ConnectionFailure if an error occurred", function() {
sdk.initPublisher.callArgWith(2, { message: "FAKE" });
sinon.assert.calledTwice(dispatcher.dispatch);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionFailure({
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
}));
});
it("should notify metrics if an error occurred", function() {
sdk.initPublisher.callArgWith(2, { code: 123, message: "FAKE" });
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "sdk.exception.123.FAKE",
state: "starting",
connections: 0,
sendStreams: 0,
recvStreams: 0
}));
});
});
describe("#setMute", function() {
@ -315,25 +302,12 @@ describe("loop.OTSdkDriver", function() {
it("should dispatch ConnectionFailure if an error occurred", function() {
sdk.initPublisher.callArgWith(2, { code: 123, message: "FAKE" });
sinon.assert.calledTwice(dispatcher.dispatch);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ScreenSharingState({
state: SCREEN_SHARE_STATES.INACTIVE
}));
});
it("should notify metrics if an error occurred", function() {
sdk.initPublisher.callArgWith(2, { code: 123, message: "FAKE" });
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "sdk.exception.screen.123.FAKE",
state: "starting",
connections: 0,
sendStreams: 0,
recvStreams: 0
}));
});
});
describe("Screenshare Access Denied", function() {
@ -879,6 +853,7 @@ describe("loop.OTSdkDriver", function() {
beforeEach(function() {
fakeConnection = "fakeConnection";
fakeStream = {
hasAudio: true,
hasVideo: true,
videoType: "camera",
videoDimensions: { width: 1, height: 2 }
@ -1037,6 +1012,7 @@ describe("loop.OTSdkDriver", function() {
driver._mockPublisherEl.appendChild(fakeMockVideo);
stream = {
hasAudio: true,
hasVideo: true,
videoType: "camera",
videoDimensions: { width: 1, height: 2 }
@ -1061,6 +1037,7 @@ describe("loop.OTSdkDriver", function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.MediaStreamCreated({
hasAudio: true,
hasVideo: true,
isLocal: true,
srcMediaElement: fakeMockVideo
@ -1074,6 +1051,7 @@ describe("loop.OTSdkDriver", function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.MediaStreamCreated({
hasAudio: true,
hasVideo: false,
isLocal: true,
srcMediaElement: fakeMockVideo
@ -1147,12 +1125,14 @@ describe("loop.OTSdkDriver", function() {
driver.session = session;
fakeStream.connection = fakeConnection;
fakeStream.hasVideo = true;
fakeStream.hasAudio = true;
session.trigger("streamCreated", { stream: fakeStream });
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.MediaStreamCreated({
hasAudio: true,
hasVideo: true,
isLocal: false,
srcMediaElement: videoElement
@ -1168,6 +1148,7 @@ describe("loop.OTSdkDriver", function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.MediaStreamCreated({
hasAudio: true,
hasVideo: false,
isLocal: false,
srcMediaElement: videoElement
@ -1893,6 +1874,56 @@ describe("loop.OTSdkDriver", function() {
}));
});
});
describe("Unable to Publish", function() {
it("should not do anything if the message is 'GetUserMedia'", function() {
sdk.trigger("exception", {
code: OT.ExceptionCodes.UNABLE_TO_PUBLISH,
message: "GetUserMedia"
});
sinon.assert.notCalled(dispatcher.dispatch);
});
it("should notify metrics", function() {
sdk.trigger("exception", {
code: OT.ExceptionCodes.UNABLE_TO_PUBLISH,
message: "General Media Fail"
});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "sdk.exception." + OT.ExceptionCodes.UNABLE_TO_PUBLISH +
".General Media Fail",
state: "starting",
connections: 0,
sendStreams: 0,
recvStreams: 0
}));
});
it("should notify metrics with a special screen indication for screen shares", function() {
driver.screenshare = { fake: true };
sdk.trigger("exception", {
code: OT.ExceptionCodes.UNABLE_TO_PUBLISH,
message: "General Media Fail 2",
target: driver.screenshare
});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "sdk.exception.screen." + OT.ExceptionCodes.UNABLE_TO_PUBLISH +
".General Media Fail 2",
state: "starting",
connections: 0,
sendStreams: 0,
recvStreams: 0
}));
});
});
});
});

View File

@ -349,6 +349,8 @@ describe("loop.shared.utils", function() {
return "body_context";
case "share_email_footer2":
return "footer";
default:
return "unknown";
}
});
LoopMochaUtils.stubLoopRequest(requestStubs = {

View File

@ -96,6 +96,34 @@ describe("loop.shared.views", function() {
expect(comp.getDOMNode().classList.contains("muted")).eql(true);
});
it("should render a muted and disabled local video button", function() {
var comp = TestUtils.renderIntoDocument(
React.createElement(sharedViews.MediaControlButton, {
scope: "local",
type: "video",
action: function() {},
disabled: true,
muted: true
}));
expect(comp.getDOMNode().classList.contains("muted")).eql(true);
expect(comp.getDOMNode().classList.contains("disabled")).eql(true);
});
it("should render a muted local audio button", function() {
var comp = TestUtils.renderIntoDocument(
React.createElement(sharedViews.MediaControlButton, {
scope: "local",
type: "audio",
action: function() {},
disabled: true,
muted: true
}));
expect(comp.getDOMNode().classList.contains("muted")).eql(true);
expect(comp.getDOMNode().classList.contains("disabled")).eql(true);
});
});
describe("AudioMuteButton", function() {
@ -453,6 +481,30 @@ describe("loop.shared.views", function() {
});
});
});
describe("ContextUrlLink", function() {
var view;
function mountTestComponent(extraProps) {
var props = _.extend({
allowClick: true,
description: "test",
url: "http://example.com"
}, extraProps);
return TestUtils.renderIntoDocument(
React.createElement(sharedViews.ContextUrlLink, props));
}
it("should not have any children if none are passed", function() {
view = mountTestComponent({
allowClick: true,
url: "http://wonderful.invalid"
});
var contextHasChildren = view.getDOMNode().childNodes.length;
expect(contextHasChildren).eql(0);
});
});
describe("ContextUrlView", function() {
var view;
@ -475,7 +527,7 @@ describe("loop.shared.views", function() {
});
var wrapper = view.getDOMNode().querySelector(".context-wrapper");
console.log(view.getDOMNode().querySelector(".context-wrapper").childNodes);
expect(wrapper.classList.contains("clicks-allowed")).eql(true);
});
@ -490,29 +542,45 @@ describe("loop.shared.views", function() {
expect(wrapper.classList.contains("clicks-allowed")).eql(false);
});
it("should allow context to be clickable if the url is valid", function() {
view = mountTestComponent({
allowClick: true,
url: "http://example.com/"
});
expect(view.getDOMNode().querySelector(".context-wrapper").getAttribute("href"))
.eql("http://example.com/");
});
it("should display nothing if the url is invalid", function() {
view = mountTestComponent({
allowClick: true,
url: "fjrTykyw"
});
expect(view.getDOMNode()).eql(null);
expect(view.getDOMNode().querySelector(".context-wrapper").getAttribute("href"))
.eql(null);
});
it("should display nothing if it is an about url", function() {
view = mountTestComponent({
allowClick: true,
url: "about:config"
});
expect(view.getDOMNode()).eql(null);
expect(view.getDOMNode().querySelector(".context-wrapper").getAttribute("href"))
.eql(null);
});
it("should display nothing if it is a javascript url", function() {
/* eslint-disable no-script-url */
view = mountTestComponent({
allowClick: true,
url: "javascript:alert('hello')"
});
expect(view.getDOMNode()).eql(null);
expect(view.getDOMNode().querySelector(".context-wrapper").getAttribute("href"))
.eql(null);
/* eslint-enable no-script-url */
});

View File

@ -1,4 +1,4 @@
// Backbone.js 1.3.2
// Backbone.js 1.3.3
// (c) 2010-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Backbone may be freely distributed under the MIT license.
@ -44,7 +44,7 @@
var slice = Array.prototype.slice;
// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '1.3.2';
Backbone.VERSION = '1.3.3';
// For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
// the `$` variable.

File diff suppressed because one or more lines are too long

View File

@ -193,16 +193,16 @@ rooms_signout_alert=Açıq söhbətlər qapatılacaq
room_name_untitled_page=Adsız Səhifə
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=Hələlik! Bu paylaşılmış sessiyaya istədiyiniz vaxt Hello panelindən qayıda bilərsiz.
door_hanger_prompt_name=Rahat yadda qalan ad vermək istərdiniz? Hazırki adı:
door_hanger_button=Tamam
door_hanger_bye=Hələlik!
door_hanger_return2=Bu paylaşılmış sessiyaya istədiyiniz vaxt Hello panelindən qayıda bilərsiz. Buna asan yadda qalan ad vermək istərdiniz?
door_hanger_current=Hazırki ad:
door_hanger_button2=Tamam!
# Infobar strings
infobar_screenshare_no_guest_message=Yoldaşınız daxil olduqda üzərinə kliklədiyiniz hər bir vərəqi görəcəklər.
infobar_screenshare_browser_message2=Vərəqlərinizi paylaşırsınız. Üzərinə kliklədiyiniz hər vərəq yoldaşlarınız tərəfindən görünəcək
infobar_screenshare_browser_message3=Artıq vərəqlərinizi paylaşırsınız. Yoldaşınız üzərinə kliklədiyiniz hər bir vərəqi görə biləcək.
infobar_screenshare_stop_sharing_message=Artıq vərəqlərinizi paylaşmırsınız
infobar_button_restart_label2=Paylaşmanı yenidən başlat
infobar_button_restart_accesskey=e
infobar_button_stop_label2=Paylaşmanı dayandır
@ -210,6 +210,13 @@ infobar_button_stop_accesskey=S
infobar_button_disconnect_label=Ayrıl
infobar_button_disconnect_accesskey=D
# Copy panel strings
copy_panel_message=Bu Web səhifəni paylaşmaq istəyirsiz? Səyyah vərəqinizi yoldaşınızla paylaşın.
copy_panel_dont_show_again_label=Bunu təkrar göstərmə
copy_panel_cancel_button_label=İndi deyil
copy_panel_accept_button_label=Bəli, necə edəcəyimi göstər
# E10s not supported strings
e10s_not_supported_button_label=Yeni Pəncərə Aç

View File

@ -4,8 +4,6 @@
# Panel Strings
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
## use "..." if \u2026 doesn't suit traditions in your locale.
@ -31,6 +29,8 @@ panel_disconnect_button=Изключване
## First Time Experience Slides
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
@ -112,6 +112,9 @@ hangup_button_caption2=Изход
infobar_button_disconnect_label=Изключване
infobar_button_disconnect_accesskey=И
# Copy panel strings
# E10s not supported strings
# This Source Code Form is subject to the terms of the Mozilla Public

View File

@ -4,8 +4,6 @@
# Panel Strings
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
## use "..." if \u2026 doesn't suit traditions in your locale.
@ -44,8 +42,10 @@ fte_slide_1_title=বন্ধুর সাথে পাতাটি ব্র
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_1_copy=যখনই আপনি কোন উপহারের জন্য কেনাকাটা বা কোথায় বেড়ানোর পরিকল্পনা করছেন, {{clientShortname2}} আপনাকে দ্রুত সময়ের মধ্যে তাৎক্ষণিক সিদ্ধান্ত নিতে সাহায্য করবে।
fte_slide_2_title=একই পাতায় পেতে
fte_slide_2_copy=কোন আইডিয়া শেয়ার করতে, বিকল্পগুলো তুলনা করছে এবং একটি সিধান্তে আসতে বিল্ট-ইন টেক্সট অথবা ভিডিও চ্যাট ব্যবহার করুন।
fte_slide_2_title2=ওয়েবে শেয়ার করার জন্যই তৈরি হয়েছে
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_2_copy2=এখন আপনি যখনই একজন বন্ধুকে একটি সেশনে আমন্ত্রন জানাবেন, {{clientShortname2}} স্বয়ংক্রিয়াভাবে সকল ওয়েবসাইটও শেয়ার করবে, যা আপনি ওই মূহুর্তে ব্রাউজ করছেন। পরিকল্পনা করুন, কেনাকাটা করুন, সিদ্ধান্ত নিন, সবাই একসাথে।
fte_slide_3_title=লিঙ্ক শেয়ার করে বন্ধুদের আলাপে আমন্ত্রণ জানান
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
@ -57,9 +57,7 @@ fte_slide_4_title=শুরু করতে {{clientSuperShortname}} আইক
## will be replaced by the brand short name.
fte_slide_4_copy=যখনই আপনি কোন পাতা পাবেন আলোচনা করার জন্যে, লিঙ্ক তৈরি করতে {{brandShortname}} এ ক্লিক করুন। তারপর সেই লিঙ্ক যেকোন ভাবে আপনার বন্ধুর কাছে পাঠিয়ে দিন।
invite_header_text_bold=আপনার সাথে এই পাতা ব্রাউজে যোগদানে কাউকে আমন্ত্রণ জানান!
invite_header_text_bold2=কাউকে আমন্ত্রণ জানান আপনার সাথে যোগ দিতে ।
invite_header_text3=ফায়ারফক্স হ্যালো ব্যবহার করতে দুজনের প্রয়োজন হয়, আপনার সাথে ওয়েব ব্রাউজ করতে আপনার বন্ধুকে লিঙ্ক পাঠান।
invite_header_text4=এই লিঙ্কটি শেয়ার করুন যেন আপনারা একসাথে ওয়েব ব্রাউজ করতে পারেন।
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
## invite_email_link_button, invite_facebook_button2): These labels appear under
@ -107,7 +105,8 @@ share_add_service_button=একটি সার্ভিস যুক্ত ক
## These menu items are displayed from a panel's context menu for a conversation.
copy_link_menuitem=লিঙ্ক কপি করুন
email_link_menuitem=ইমেইল লিঙ্ক
delete_conversation_menuitem2=অপসারণ
edit_name_menuitem=নাম সম্পাদনা করুন
delete_conversation_menuitem2=মুছে ফেলুন
panel_footer_signin_or_signup_link=সাইন ইন / সাইন আপ
@ -194,16 +193,18 @@ rooms_signout_alert=উন্মুক্ত কথোপকথন বন্ধ
room_name_untitled_page=শিরোনামহীন পাতা
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=আবার দেখা হবে! Hello প্যানেলের মাধ্যমে আপনি এই শেয়ারকৃত সেশনে যেকোন সময় ফিরে আসতে পারবেন।
door_hanger_prompt_name=সহজভাবে মনে রাখার জন্য কোনো নাম দিতে চান? বর্তমান নাম:
door_hanger_button=ঠিক আছে
door_hanger_bye=আবার দেখা হবে!
door_hanger_return2=Hello প্যানেলের মাধ্যমে আপনি শেয়ারকৃত এই সেশনে যেকোন সময় ফিরে আসতে পারবেন। সহজে মনে রাখতে এর কোন নাম দিতে চান?
door_hanger_current=সঠিক নাম:
door_hanger_button2=ঠিক আছে!
# Infobar strings
infobar_screenshare_no_guest_message=যখনই আপনার বন্ধু যোগ দেবে, তারা আপনি কি ট্যাব ক্লিক করছেন তা দেখতে পাবে।
infobar_screenshare_browser_message2=আপনি আপনার ট্যাব শেয়ার করছেন। যেকোনো ট্যাব ক্লিক করলে তা আপনার বন্ধু দেখতে পারবে।
infobar_screenshare_browser_message3=আপনি এখন আপনার ট্যাব শেয়ার করছেন। আপনে যেই ট্যাবেই ক্লিক করুন তা আপনার বন্ধু দেখতে পাবে।
infobar_screenshare_stop_sharing_message=আপনি আপনার ট্যাব আর শেয়ার করছেন না
infobar_screenshare_stop_sharing_message2=আপনি আর ট্যাব শেয়ার করছেন না।
infobar_screenshare_stop_no_guest_message=আপনি ট্যাব শেয়ার করা বন্ধ করেছেন। আপনার বন্ধুরা যখন যুক্ত হবে, আপনি যতক্ষণ না কোন কিছু আবার শেয়ার করছেন তারা কিছু দেখতে পাবে না।
infobar_button_restart_label2=শেয়ার করা পুনরায় শুরু করুন
infobar_button_restart_accesskey=R
infobar_button_stop_label2=শেয়ার করা বন্ধ করুন
@ -211,6 +212,13 @@ infobar_button_stop_accesskey=S
infobar_button_disconnect_label=বিচ্ছিন্ন
infobar_button_disconnect_accesskey=D
# Copy panel strings
copy_panel_message=ওয়েব পেজ শেয়ার করা প্রয়োজন? আপনার ব্রাউজারের ট্যাব বন্ধুর সাথে শেয়ার করুন।
copy_panel_dont_show_again_label=এটি পুনরায় দেখাবে না
copy_panel_cancel_button_label=এখন নয়
copy_panel_accept_button_label=হ্যাঁ, আমাকে দেখাও কিভাবে হল
# E10s not supported strings
e10s_not_supported_button_label=নতুন উইন্ডো খুলুন
@ -257,6 +265,9 @@ rooms_room_joined_owner_not_connected_label=আপনার বন্ধু আ
self_view_hidden_message=সেলফ-ভিউ লুকানো কিন্তু এখনো ছবি পাঠাবে; দেখাতে উইন্ডোর আকার পরিবর্তন করুন
peer_left_session=আপনার বন্ধু চলে গেছেন।
peer_unexpected_quit=আপনার বন্ধু অপ্রত্যাশিতভাবে বিচ্ছিন্ন হয়েছেন।
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.
tos_failure_message={{clientShortname}} আপনার দেশের বিদ্যমান নয়।

View File

@ -4,7 +4,6 @@
# Panel Strings
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
## use "..." if \u2026 doesn't suit traditions in your locale.
@ -19,18 +18,28 @@
## will be replaced by the super short brandname.
## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
## LOCALIZATION_NOTE(first_time_experience_content): Message describing
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
## ways to use Hello project.
## First Time Experience Slides
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
## will be replaced by the brand short name.
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
## invite_email_link_button, invite_facebook_button2): These labels appear under
## an iconic button for the invite view.
# Status text
# Error bars
## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
## These may be displayed at the top of the panel.
@ -98,12 +107,8 @@ hangup_button_caption2=প্রস্থান করুন
# Infobar strings
# Context in conversation strings
# Copy panel strings
## LOCALIZATION NOTE(no_conversations_message_heading2): Title shown when user
## has no conversations available.
## LOCALIZATION NOTE(no_conversations_start_message2): Subheading inviting the
## user to start a new conversation.
# E10s not supported strings

View File

@ -4,7 +4,6 @@
# Panel Strings
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
## use "..." if \u2026 doesn't suit traditions in your locale.
@ -19,18 +18,28 @@
## will be replaced by the super short brandname.
## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
## LOCALIZATION_NOTE(first_time_experience_content): Message describing
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
## ways to use Hello project.
## First Time Experience Slides
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
## will be replaced by the brand short name.
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
## invite_email_link_button, invite_facebook_button2): These labels appear under
## an iconic button for the invite view.
# Status text
# Error bars
## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
## These may be displayed at the top of the panel.
@ -112,12 +121,8 @@ tour_label=Obilazak
# Infobar strings
# Context in conversation strings
# Copy panel strings
## LOCALIZATION NOTE(no_conversations_message_heading2): Title shown when user
## has no conversations available.
## LOCALIZATION NOTE(no_conversations_start_message2): Subheading inviting the
## user to start a new conversation.
# E10s not supported strings

View File

@ -4,7 +4,6 @@
# Panel Strings
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
## use "..." if \u2026 doesn't suit traditions in your locale.
@ -19,18 +18,28 @@
## will be replaced by the super short brandname.
## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
## LOCALIZATION_NOTE(first_time_experience_content): Message describing
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
## ways to use Hello project.
## First Time Experience Slides
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
## will be replaced by the brand short name.
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
## invite_email_link_button, invite_facebook_button2): These labels appear under
## an iconic button for the invite view.
# Status text
# Error bars
## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
## These may be displayed at the top of the panel.
@ -98,12 +107,8 @@ hangup_button_caption2=Surt
# Infobar strings
# Context in conversation strings
# Copy panel strings
## LOCALIZATION NOTE(no_conversations_message_heading2): Title shown when user
## has no conversations available.
## LOCALIZATION NOTE(no_conversations_start_message2): Subheading inviting the
## user to start a new conversation.
# E10s not supported strings

View File

@ -194,16 +194,18 @@ rooms_signout_alert=Otevřené konverzace budou uzavřeny
room_name_untitled_page=Stránka bez názvu
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=Na viděnou! K této sdílené relaci se můžete kdykoliv vrátit pomocí panelu Hello.
door_hanger_prompt_name=Chcete ji pro snazší zapamatování pojmenovat? Aktuální název:
door_hanger_button=OK
door_hanger_bye=Uvidíme se později!
door_hanger_return2=K této sdílené relaci se můžete kdykoli vrátit přes panel Hello. Chcete jí dát název, který si lépe zapamatujete?
door_hanger_current=Současný název:
door_hanger_button2=OK!
# Infobar strings
infobar_screenshare_no_guest_message=Jakmile se vaší přátelé připojí, budou moci vidět všechny panely, na které klepnete.
infobar_screenshare_browser_message2=Sdílíte své panely. Jakýkoliv panel, na který klepnete, může být viděn vašimi přáteli
infobar_screenshare_browser_message3=Nyní spolu sdílíte své panely. Váš přítel uvidí každý panel, na který klepnete.
infobar_screenshare_stop_sharing_message=Už nadále nesdílíte své panely
infobar_screenshare_stop_sharing_message2=Již své panely nesdílíte.
infobar_screenshare_stop_no_guest_message=Sdílení svých panelů jste ukončili. Když se vaši přátelé připojí, nic neuvidí, dokud své panely opět nezačnete sdílet.
infobar_button_restart_label2=Restartovat sdílení
infobar_button_restart_accesskey=e
infobar_button_stop_label2=Zastavit sdílení
@ -211,6 +213,13 @@ infobar_button_stop_accesskey=s
infobar_button_disconnect_label=Odpojit
infobar_button_disconnect_accesskey=O
# Copy panel strings
copy_panel_message=Potřebujete sdílet tuto webovou stránku? Sdílejte tento panel s přáteli.
copy_panel_dont_show_again_label=Znovu nezobrazovat
copy_panel_cancel_button_label=Nyní ne
copy_panel_accept_button_label=Ano, ukažte mi, jak na to
# E10s not supported strings
e10s_not_supported_button_label=Otevřít nové okno

View File

@ -193,16 +193,12 @@ rooms_signout_alert=Bydd sgyrsiau agored yn cael eu cau
room_name_untitled_page=Tudalen Ddideitl
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=Hwyl am y tro! Gallwch ddychwelyd i'r sesiwn yma ar unrhyw adeg drwy banel Hello.
door_hanger_prompt_name=Hoffech chi roi enw iddo fel i fod yn haws ei gofio? Enw cyfredol:
door_hanger_button=Iawn
# Infobar strings
infobar_screenshare_no_guest_message=Cyn gynted a bo'ch ffrind yn ymuno, bydd modd iddyn nhw weld unrhyw dab rydych yn clicio arno.
infobar_screenshare_browser_message2=Rydych yn rhannu eich tabiau. Mae modd i'ch ffrindiau weld unrhyw dab rydych yn clicio arno
infobar_screenshare_browser_message3=Rydych nawr yn rhannu eich tabiau. Bydd eich ffrind yn gweld unrhyw dab fyddwch chi'n clicio arno.
infobar_screenshare_stop_sharing_message=Nid ydych bellach yn rhannu eich tabiau
infobar_button_restart_label2=Ail gychwyn rhannu
infobar_button_restart_accesskey=A
infobar_button_stop_label2=Peidio rhannu
@ -210,6 +206,9 @@ infobar_button_stop_accesskey=P
infobar_button_disconnect_label=Datgysylltu
infobar_button_disconnect_accesskey=D
# Copy panel strings
# E10s not supported strings
e10s_not_supported_button_label=Agor Ffenestr Newydd

View File

@ -193,16 +193,16 @@ rooms_signout_alert=Åbne samtaler vil blive slettet
room_name_untitled_page=Unavngiven side
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=Vi ses senere! Du kan til enhver tid vende tilbage til denne delte session ved at klikke på Hello-panelet.
door_hanger_prompt_name=Vil du give den et navn, der er nemmere at huske? Nuværende navn:
door_hanger_button=OK
door_hanger_bye=På gensyn!
door_hanger_return2=Du kan vendte tilbage til denne delte session når som helst via Hello-panelet. Vil du give den et navn, der er nemmere at huske?
door_hanger_current=Nuværende navn:
door_hanger_button2=OK!
# Infobar strings
infobar_screenshare_no_guest_message=Lige så snart din ven deltager, vil hun kunne se de faneblade, du klikker på.
infobar_screenshare_browser_message2=Du deler dine faneblade. Når du klikker på et faneblad kan dine venner se det
infobar_screenshare_browser_message3=Du deler nu dine faneblade. Din ven vil kunne se de faneblade, du klikker på.
infobar_screenshare_stop_sharing_message=Du deler ikke dine faneblade længere
infobar_button_restart_label2=Genstart deling
infobar_button_restart_accesskey=e
infobar_button_stop_label2=Stop med at dele
@ -210,6 +210,13 @@ infobar_button_stop_accesskey=S
infobar_button_disconnect_label=Afbryd
infobar_button_disconnect_accesskey=A
# Copy panel strings
copy_panel_message=Har du brug for at dele dette websted? Del fanebladet i din browser med en ven.
copy_panel_dont_show_again_label=Vis ikke dette igen
copy_panel_cancel_button_label=Ikke nu
copy_panel_accept_button_label=Ja, vis mig hvordan
# E10s not supported strings
e10s_not_supported_button_label=Åbn nyt vindue

View File

@ -194,16 +194,18 @@ rooms_signout_alert=Geöffnete Gespräche werden beendet.
room_name_untitled_page=Seite ohne Namen
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=Bis später! Sie können jederzeit über die Hello-Ansicht in diese geteilte Sitzung zurückkehren.
door_hanger_prompt_name=Wollen Sie Ihr einen einfacher zu merkenden Namen geben? Derzeitiger Name:
door_hanger_button=OK
door_hanger_bye=Bis später!
door_hanger_return2=Sie können diese geteilte Sitzung jederzeit über den Hello-Bereich wieder aufrufen. Möchten Sie ihr einen Namen geben, den Sie sich besser merken können?
door_hanger_current=Aktueller Name:
door_hanger_button2=Ok!
# Infobar strings
infobar_screenshare_no_guest_message=Sobald Ihr Freund dabei ist, kann er jeden Tab sehen, den Sie anklicken.
infobar_screenshare_browser_message2=Sie geben Ihre Tabs weiter. Jeder von Ihnen angeklickte Tab wird von Ihren Freunden gesehen.
infobar_screenshare_browser_message3=Sie geben jetzt Ihre Tabs weiter. Ihr Freund kann alle Tabs sehen, die Sie anklicken.
infobar_screenshare_stop_sharing_message=Sie geben Ihre Tabs nicht mehr weiter.
infobar_screenshare_stop_sharing_message2=Sie geben Ihre Tabs nicht mehr weiter.
infobar_screenshare_stop_no_guest_message=Sie geben Ihre Tabs nicht mehr weiter. Wenn Ihr Freund das Gespräch betritt, kann dieser Ihre Tabs nicht sehen, bis Sie diese wieder weitergeben.
infobar_button_restart_label2=Wieder weitergeben
infobar_button_restart_accesskey=W
infobar_button_stop_label2=Nicht mehr weitergeben
@ -211,6 +213,13 @@ infobar_button_stop_accesskey=N
infobar_button_disconnect_label=Verbindung trennen
infobar_button_disconnect_accesskey=t
# Copy panel strings
copy_panel_message=Möchten Sie diese Webseite teilen? Teilen Sie Ihren Browser-Tab mit einem Freund.
copy_panel_dont_show_again_label=Nicht mehr anzeigen
copy_panel_cancel_button_label=Jetzt nicht.
copy_panel_accept_button_label=Ja, zeig mir, wie es geht.
# E10s not supported strings
e10s_not_supported_button_label=Neues Fenster öffnen

View File

@ -193,16 +193,18 @@ rooms_signout_alert=Wócynjone rozgrona se zacyniju
room_name_untitled_page=Bok bźez titela
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=Až do chyle! Móžośo se kuždy cas pśez wokno Hello k toś tomu źělonemu pósejźenjeju wrośiś.
door_hanger_prompt_name=By wy pšosym mě pódał, kótarež dajo se sj lažčej spomnjeś? Aktualne mě:
door_hanger_button=W pórěźe
door_hanger_bye=Až do chyle!
door_hanger_return2=Móžośo se k toś tomu źělonemu pósejźenjeju kuždy cas pśez wobłuk Hello wrośiś. Cośo jomu mě daś, kótarež móžośo se lažcej spomnjeś?
door_hanger_current=Aktualne mě:
door_hanger_button2=W pórěźe!
# Infobar strings
infobar_screenshare_no_guest_message=Gaž se waš pśijaśel pśizamkujo, móžotej rejtark wiźeś, na kótaryž kliknjośo.
infobar_screenshare_browser_message2=Źěliśo swóje rejtarki. Waše pśijaśele mógu kuždy rejtark wiźeś, na kótaryž kliknjośo
infobar_screenshare_browser_message3=Źělitej něnto swóje rejtarki. Waš pśijaśel buźo rejtark wiźeś, na kótaryž kliknjośo.
infobar_screenshare_stop_sharing_message=Njeźěliśo wěcej swóje rejtarki
infobar_screenshare_stop_sharing_message2=Njeźěliśo wěcej swóje rejtarki.
infobar_screenshare_stop_no_guest_message=Njeźěliśo wěcej swóje rejtarki. Gaž se waš pśijaśel pśizamkujo, njamóžo nic wiźeś, daniž zasej njeźěliśo.
infobar_button_restart_label2=Źělenje znowego startowaś
infobar_button_restart_accesskey=n
infobar_button_stop_label2=Źělenje zastajiś
@ -210,6 +212,13 @@ infobar_button_stop_accesskey=S
infobar_button_disconnect_label=Zwisk źěliś
infobar_button_disconnect_accesskey=Z
# Copy panel strings
copy_panel_message=Cośo toś ten webbok źěliś? Źělśo swój wobglědowakowy rejtark z pśijaśelom.
copy_panel_dont_show_again_label=Wěcej se njepokazaś
copy_panel_cancel_button_label=Nic něnto
copy_panel_accept_button_label=Jo, pokažćo mě kak
# E10s not supported strings
e10s_not_supported_button_label=Nowe wokno wócyniś

View File

@ -4,7 +4,6 @@
# Panel Strings
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
## use "..." if \u2026 doesn't suit traditions in your locale.
@ -19,18 +18,28 @@
## will be replaced by the super short brandname.
## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
## LOCALIZATION_NOTE(first_time_experience_content): Message describing
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
## ways to use Hello project.
## First Time Experience Slides
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
## will be replaced by the brand short name.
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
## invite_email_link_button, invite_facebook_button2): These labels appear under
## an iconic button for the invite view.
# Status text
# Error bars
## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
## These may be displayed at the top of the panel.
@ -112,12 +121,8 @@ tour_label=Περιήγηση
# Infobar strings
# Context in conversation strings
# Copy panel strings
## LOCALIZATION NOTE(no_conversations_message_heading2): Title shown when user
## has no conversations available.
## LOCALIZATION NOTE(no_conversations_start_message2): Subheading inviting the
## user to start a new conversation.
# E10s not supported strings

View File

@ -4,8 +4,6 @@
# Panel Strings
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
## use "..." if \u2026 doesn't suit traditions in your locale.
@ -44,8 +42,10 @@ fte_slide_1_title=Browse Web pages with a friend
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_1_copy=Whether youre planning a trip or shopping for a gift, {{clientShortname2}} lets you make faster decisions in real time.
fte_slide_2_title=Get on the same page
fte_slide_2_copy=Use the built-in text or video chat to share ideas, compare options and come to an agreement.
fte_slide_2_title2=Made for sharing the Web
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_2_copy2=Now when you invite a friend to a session, {{clientShortname2}} will automatically share any Web page youre viewing. Plan. Shop. Decide. Together.
fte_slide_3_title=Invite a friend by sending a link
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
@ -57,9 +57,7 @@ fte_slide_4_title=Find the {{clientSuperShortname}} icon to get started
## will be replaced by the brand short name.
fte_slide_4_copy=Once youve found a page you want to discuss, click the icon in {{brandShortname}} to create a link. Then send it to your friend however you like!
invite_header_text_bold=Invite someone to browse this page with you!
invite_header_text_bold2=Invite a friend to join you!
invite_header_text3=It takes two to use Firefox Hello, so send a friend a link to browse the Web with you!
invite_header_text4=Share this link so you can start browsing the Web together.
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
## invite_email_link_button, invite_facebook_button2): These labels appear under
@ -107,6 +105,7 @@ share_add_service_button=Add a Service
## These menu items are displayed from a panel's context menu for a conversation.
copy_link_menuitem=Copy Link
email_link_menuitem=Email Link
edit_name_menuitem=Edit name
delete_conversation_menuitem2=Delete
panel_footer_signin_or_signup_link=Sign In or Sign Up
@ -194,16 +193,16 @@ rooms_signout_alert=Open conversations will be closed
room_name_untitled_page=Untitled Page
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=See you later! You can return to this shared session at any time through the Hello panel.
door_hanger_prompt_name=Would you like to give it a name that's easier to remember? Current name:
door_hanger_button=OK
door_hanger_bye=See you later!
door_hanger_return2=You can return to this shared session at any time through the Hello panel. Would you like to give it a name thats easier to remember?
door_hanger_current=Current name:
door_hanger_button2=OK!
# Infobar strings
infobar_screenshare_no_guest_message=As soon as your friend joins, they will be able to see any tab you click on.
infobar_screenshare_browser_message2=You are sharing your tabs. Any tab you click on can be seen by your friends
infobar_screenshare_browser_message3=You are now sharing your tabs. Your friend will see any tab you click on.
infobar_screenshare_stop_sharing_message=You are no longer sharing your tabs
infobar_button_restart_label2=Restart sharing
infobar_button_restart_accesskey=e
infobar_button_stop_label2=Stop sharing
@ -211,6 +210,13 @@ infobar_button_stop_accesskey=S
infobar_button_disconnect_label=Disconnect
infobar_button_disconnect_accesskey=D
# Copy panel strings
copy_panel_message=Need to share this Web page? Share your browser tab with a friend.
copy_panel_dont_show_again_label=Dont show this again
copy_panel_cancel_button_label=Not now
copy_panel_accept_button_label=Yes, show me how
# E10s not supported strings
e10s_not_supported_button_label=Launch New Window

View File

@ -193,16 +193,18 @@ rooms_signout_alert=Open conversations will be closed
room_name_untitled_page=Untitled Page
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=See you later! You can return to this shared session at any time through the Hello panel.
door_hanger_prompt_name=Would you like to give it a name thats easier to remember? Current name:
door_hanger_button=OK
door_hanger_bye=See you later!
door_hanger_return2=You can return to this shared session at any time through the Hello panel. Would you like to give it a name thats easier to remember?
door_hanger_current=Current name:
door_hanger_button2=OK!
# Infobar strings
infobar_screenshare_no_guest_message=As soon as your friend joins, they will be able to see any tab you click on.
infobar_screenshare_browser_message2=You are sharing your tabs. Any tab you click on can be seen by your friends
infobar_screenshare_browser_message3=You are now sharing your tabs. Your friend will see any tab you click on.
infobar_screenshare_stop_sharing_message=You are no longer sharing your tabs
infobar_screenshare_stop_sharing_message2=You are no longer sharing your tabs.
infobar_screenshare_stop_no_guest_message=You have stopped sharing your tabs. When your friend joins, they wont be able to see anything until you restart sharing.
infobar_button_restart_label2=Restart sharing
infobar_button_restart_accesskey=R
infobar_button_stop_label2=Stop sharing
@ -210,6 +212,13 @@ infobar_button_stop_accesskey=S
infobar_button_disconnect_label=Disconnect
infobar_button_disconnect_accesskey=D
# Copy panel strings
copy_panel_message=Need to share this Web page? Share your browser tab with a friend.
copy_panel_dont_show_again_label=Dont show this again
copy_panel_cancel_button_label=Not now
copy_panel_accept_button_label=Yes, show me how
# E10s not supported strings
e10s_not_supported_button_label=Launch New Window

View File

@ -4,7 +4,6 @@
# Panel Strings
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
## use "..." if \u2026 doesn't suit traditions in your locale.
@ -19,18 +18,28 @@
## will be replaced by the super short brandname.
## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
## LOCALIZATION_NOTE(first_time_experience_content): Message describing
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
## ways to use Hello project.
## First Time Experience Slides
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
## will be replaced by the brand short name.
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
## invite_email_link_button, invite_facebook_button2): These labels appear under
## an iconic button for the invite view.
# Status text
# Error bars
## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
## These may be displayed at the top of the panel.
@ -98,12 +107,8 @@ hangup_button_caption2=Eliri
# Infobar strings
# Context in conversation strings
# Copy panel strings
## LOCALIZATION NOTE(no_conversations_message_heading2): Title shown when user
## has no conversations available.
## LOCALIZATION NOTE(no_conversations_start_message2): Subheading inviting the
## user to start a new conversation.
# E10s not supported strings

View File

@ -194,16 +194,18 @@ rooms_signout_alert=Las conversaciones abiertas serán cerradas
room_name_untitled_page=Página sin título
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=¡Hasta pronto! Puedes regresar a esta sesión compartida en cualquier momento a través del panel Hello.
door_hanger_prompt_name=¿Quieres darle un nombre para que sea más fácil de recordar? Nombre actual:
door_hanger_button=Aceptar
door_hanger_bye=¡Hasta pronto!
door_hanger_return2=Puedes regresar a esta sesión compartida en cualquier momento a través del panel de Hello. ¿Te gustaría darle un nombre para que puedas encontrarla fácilmente?
door_hanger_current=Nombre actual:
door_hanger_button2=¡Aceptar!
# Infobar strings
infobar_screenshare_no_guest_message=Tan pronto como se una tu amigo, podrá ver cualquier pestaña en la que hagas clic.
infobar_screenshare_browser_message2=Estás compartiendo sus pestañas. Cualquier pestaña en la que hagas clic podrá ser vista por tus amigos
infobar_screenshare_browser_message3=Ahora estás compartiendo tus pestañas. Tu amigo verá cualquier pestaña en la que hagas clic.
infobar_screenshare_stop_sharing_message=Ya no estás compartiendo tus pestañas
infobar_screenshare_stop_sharing_message2=Ya no estás compartiendo tus pestañas.
infobar_screenshare_stop_no_guest_message=Has dejado de compartir tus pestañas. Cuando tu amigo se una, no podrá ver nada hasta que vuelvas a compartirlas.
infobar_button_restart_label2=Reanudar la compartición
infobar_button_restart_accesskey=R
infobar_button_stop_label2=Dejar de compartir
@ -211,6 +213,13 @@ infobar_button_stop_accesskey=S
infobar_button_disconnect_label=Desconectarse
infobar_button_disconnect_accesskey=D
# Copy panel strings
copy_panel_message=¿Necesitas compartir esta página Web? Comparte tu pestaña del navegador con un amigo.
copy_panel_dont_show_again_label=No volver a mostrar este mensaje
copy_panel_cancel_button_label=Ahora no
copy_panel_accept_button_label=Sí, muéstrenme cómo
# E10s not supported strings
e10s_not_supported_button_label=Lanzar nueva ventana

View File

@ -49,7 +49,7 @@ fte_slide_2_copy2=Ahora cuando invites a un amigo a una sesión, {{clientShortna
fte_slide_3_title=Invita a un amigo enviando un enlace
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
fte_slide_3_copy={{clientSuperShortnae}} funciona con la mayoría de los navegadores de escritorio. No es necesario tener cuenta y todo el mundo se conecta gratuitamente.
fte_slide_3_copy={{clientSuperShortname}} funciona con la mayoría de los navegadores de escritorio. No es necesario tener cuenta y todo el mundo se conecta gratuitamente.
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
## will be replaced by the super short brand name.
fte_slide_4_title=Busca el icono de {{clientSuperShortname}} para comenzar
@ -193,16 +193,18 @@ rooms_signout_alert=Se cerrarán las conversaciones abiertas
room_name_untitled_page=Página sin título
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=¡Hasta luego! Puedes volver a esta sesión compartida cuando quieras en el panel Hello.
door_hanger_prompt_name=¿Te gustaría darle un nombre más fácil de recordar? Nombre actual:
door_hanger_button=Aceptar
door_hanger_bye=¡Hasta luego!
door_hanger_return2=Puedes volver a esta sesión compartida cuando quieras a través del panel Hello. ¿Quieres darle un nombre para recordarla mejor?
door_hanger_current=Nombre actual:
door_hanger_button2=¡Vale!
# Infobar strings
infobar_screenshare_no_guest_message=Tan pronto se una tu amigo, podrá ver cualquier pestaña en la que pulses tú.
infobar_screenshare_browser_message2=Estás compartiendo tus pestañas. Cualquier pestaña en la que pulses puede ser vista por tus amigos
infobar_screenshare_browser_message3=Ahora estás compartiendo tus pestañas. Tu amigo verá cualquier pestaña en la que pulses.
infobar_screenshare_stop_sharing_message=Ya no compartes tus pestañas
infobar_screenshare_stop_sharing_message2=Ya no estás compartiendo tus pestañas.
infobar_screenshare_stop_no_guest_message=Has dejado de compartir tus pestañas. Cuando se unan tus amigos, no podrán ver nada hasta que vuelvas a compartirlas.
infobar_button_restart_label2=Volver a compartir
infobar_button_restart_accesskey=e
infobar_button_stop_label2=Dejar de compartir
@ -210,6 +212,13 @@ infobar_button_stop_accesskey=S
infobar_button_disconnect_label=Desconectar
infobar_button_disconnect_accesskey=D
# Copy panel strings
copy_panel_message=¿Necesitas compartir esta página? Comparte tu pestaña con un amigo.
copy_panel_dont_show_again_label=No volver a mostrar
copy_panel_cancel_button_label=Ahora no
copy_panel_accept_button_label=Si, quiero verlo
# E10s not supported strings
e10s_not_supported_button_label=Ejecutar una nueva ventana

View File

@ -193,16 +193,16 @@ rooms_signout_alert=Las conversaciones abiertas se cerrarán
room_name_untitled_page=Página sin título
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=¡Nos vemos luego! Puedes regresar a esta sesión compartida cuando quieras a través del panel de Hello.
door_hanger_prompt_name=¿Te gustaría probar un nombre más sencillo para recordar? Nombre actual:
door_hanger_button=Aceptar
door_hanger_bye=¡Nos vemos después!
door_hanger_return2=Puedes regresar a esta sesión compartida en cualquier momento a través del panel de Hello. ¿Te gustaría darle un nombre más sencillo de recordar?
door_hanger_current=Nombre actual:
door_hanger_button2=¡De acuerdo!
# Infobar strings
infobar_screenshare_no_guest_message=Tan pronto como tus amigos se vayan uniendo, serán capaces de ver cualquier pestaña en la que hagas clic.
infobar_screenshare_browser_message2=Estás compartiendo tus pestañas. Cualquier pestaña en la que des clic puede ser vista por tus amigos
infobar_screenshare_browser_message3=Estás compartiendo tus pestañas. Tu amigo podrá ver cualquier pestaña en la que hagas clic.
infobar_screenshare_stop_sharing_message=Ya no estás compartiendo tus pestañas
infobar_button_restart_label2=Volver a compartir
infobar_button_restart_accesskey=e
infobar_button_stop_label2=Dejar de compartir
@ -210,6 +210,13 @@ infobar_button_stop_accesskey=c
infobar_button_disconnect_label=Desconectar
infobar_button_disconnect_accesskey=D
# Copy panel strings
copy_panel_message=¿Necesitas compartir esta página Web? Comparte te pestaña de navegación con un amigo.
copy_panel_dont_show_again_label=No mostrar de nuevo
copy_panel_cancel_button_label=Ahora no
copy_panel_accept_button_label=Sí, muéstrame como
# E10s not supported strings
e10s_not_supported_button_label=Abrir una nueva ventana

View File

@ -194,16 +194,16 @@ rooms_signout_alert=Avatud vestlused suletakse
room_name_untitled_page=Nimeta leht
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=Näeme hiljem! Saad sellesse jagatud sessiooni naasta mis tahes ajal Hello paneeli kaudu.
door_hanger_prompt_name=Kas soovid sellele nime anda, et oleks kergem meeles pidada? Praegune nimi:
door_hanger_button=Olgu
door_hanger_bye=Nägemiseni!
door_hanger_return2=Saad sellesse seanssi mis tahes ajal Hello paneeli kaudu naasta. Kas soovid sellele kergesti meeldejääva nime anda?
door_hanger_current=Praegune nimi:
door_hanger_button2=Olgu!
# Infobar strings
infobar_screenshare_no_guest_message=Niipea kui su sõber liitub, näeb ta sinu valitud kaarti.
infobar_screenshare_browser_message2=Jagad enda kaarte. Sinu sõbrad näevad kaarti, mille oled valinud.
infobar_screenshare_browser_message3=Jagad nüüd enda kaarte. Sinu sõber näeb kaarti, mille oled valinud.
infobar_screenshare_stop_sharing_message=Sa ei jaga enam kaarte.
infobar_button_restart_label2=Alusta jagamist uuesti
infobar_button_restart_accesskey=s
infobar_button_stop_label2=Lõpeta jagamine
@ -211,6 +211,13 @@ infobar_button_stop_accesskey=L
infobar_button_disconnect_label=Lõpeta ühendus
infobar_button_disconnect_accesskey=L
# Copy panel strings
copy_panel_message=Sul on tarvis seda veebilehte jagada? Jaga veebilehitseja kaarti sõbraga.
copy_panel_dont_show_again_label=Ära seda rohkem kuva
copy_panel_cancel_button_label=Mitte praegu
copy_panel_accept_button_label=Jah, näita kuidas
# E10s not supported strings
e10s_not_supported_button_label=Ava uus aken

View File

@ -4,7 +4,6 @@
# Panel Strings
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
## use "..." if \u2026 doesn't suit traditions in your locale.
@ -19,18 +18,28 @@
## will be replaced by the super short brandname.
## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
## LOCALIZATION_NOTE(first_time_experience_content): Message describing
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
## ways to use Hello project.
## First Time Experience Slides
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
## will be replaced by the brand short name.
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
## invite_email_link_button, invite_facebook_button2): These labels appear under
## an iconic button for the invite view.
# Status text
# Error bars
## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
## These may be displayed at the top of the panel.
@ -112,12 +121,8 @@ tour_label=Itzulia
# Infobar strings
# Context in conversation strings
# Copy panel strings
## LOCALIZATION NOTE(no_conversations_message_heading2): Title shown when user
## has no conversations available.
## LOCALIZATION NOTE(no_conversations_start_message2): Subheading inviting the
## user to start a new conversation.
# E10s not supported strings

View File

@ -193,16 +193,16 @@ rooms_signout_alert=گفت‌وگوهای باز، بسته خواهند شد
room_name_untitled_page=صفحه بی‌نام
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=بعدا می‌بنیمت! شما می‌توانید از طریق قسمت Hello در هر زمانی به این نشست اشتراک گذاشته شده بازگردید.
door_hanger_prompt_name=آیا مایلید که نامی انتخاب کنید تا بعدا راحت‌تر آن را پیدا کنید؟ نام فعلی:
door_hanger_button=تایید
door_hanger_bye=تا بعد!
door_hanger_return2=شما می‌توانید در هر زمانی از طریق پنل Hello به این نشست به اشتراک گذاشته شده برگردید. آیا مایلید برای راحتی بیشتر یک نام برایش انتخاب کنید؟
door_hanger_current=نام فعلی:
door_hanger_button2=تایید!
# Infobar strings
infobar_screenshare_no_guest_message=هر موقع دوستان شما متصل شوند، آنها قادر خواهند بود هر زبانه‌ای که روی‌اش کلیک می‌کنید را ببینند.
infobar_screenshare_browser_message2=شما در حال اشتراک‌گذاری زبانه‌های خود هستید. هر زبانه‌ای که بر روی‌اش کلیک کنید، توسط دوستانتان دید میشود
infobar_screenshare_browser_message3=شما هم‌اکنون در حال اشتراک‌گذاری زبانه‌های خود هستید. دوستان شما هر زبانه‌ای که روی‌اش کلیک کنید را خواهند دید.
infobar_screenshare_stop_sharing_message=شما دیگر در حال اشتراک‌گذاری زبانه‌های خود نیستید
infobar_button_restart_label2=راه‌اندازی مجدد اشتراک‌گذاری
infobar_button_restart_accesskey=e
infobar_button_stop_label2=توقف اشتراک‌گذاری
@ -210,6 +210,13 @@ infobar_button_stop_accesskey=S
infobar_button_disconnect_label=قطع ارتباط
infobar_button_disconnect_accesskey=D
# Copy panel strings
copy_panel_message=می‌خواهید این صفحه وب را به‌اشتراک بگذارید؟ زبانه مرورگر خود را با یک دوست به‌اشتراک بگذارید.
copy_panel_dont_show_again_label=این را دوباره نمایش نده
copy_panel_cancel_button_label=الان نه
copy_panel_accept_button_label=بله، به من نشان بده
# E10s not supported strings
e10s_not_supported_button_label=اجرا یک پنجره جدید

View File

@ -4,7 +4,6 @@
# Panel Strings
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
## use "..." if \u2026 doesn't suit traditions in your locale.
@ -19,18 +18,28 @@
## will be replaced by the super short brandname.
## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
## LOCALIZATION_NOTE(first_time_experience_content): Message describing
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
## ways to use Hello project.
## First Time Experience Slides
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
## will be replaced by the brand short name.
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
## invite_email_link_button, invite_facebook_button2): These labels appear under
## an iconic button for the invite view.
# Status text
# Error bars
## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
## These may be displayed at the top of the panel.
@ -112,12 +121,8 @@ tour_label=Njillu
# Infobar strings
# Context in conversation strings
# Copy panel strings
## LOCALIZATION NOTE(no_conversations_message_heading2): Title shown when user
## has no conversations available.
## LOCALIZATION NOTE(no_conversations_start_message2): Subheading inviting the
## user to start a new conversation.
# E10s not supported strings

View File

@ -24,6 +24,7 @@ sign_in_again_button=Kirjaudu
sign_in_again_use_as_guest_button2=Käytä {{clientSuperShortname}}-palvelua vierastunnuksilla
panel_browse_with_friend_button=Selaa sivua kaverin kanssa
panel_disconnect_button=Katkaise
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
@ -37,6 +38,7 @@ first_time_experience_button_label2=Katso miten se toimii
## First Time Experience Slides
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_2_title2=Tehty verkon jakamista varten
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
@ -93,6 +95,7 @@ share_add_service_button=Lisää palvelu
## These menu items are displayed from a panel's context menu for a conversation.
copy_link_menuitem=Kopioi linkki
email_link_menuitem=Lähetä linkki
edit_name_menuitem=Muokkaa nimeä
delete_conversation_menuitem2=Poista
panel_footer_signin_or_signup_link=Kirjaudu tai rekisteröidy
@ -180,15 +183,23 @@ rooms_signout_alert=Avoimet keskustelut suljetaan
room_name_untitled_page=Nimetön sivu
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=Nähdään myöhemmin! Voit palata tähän jaettuun istuntoon milloin tahansa Hello-paneelin kautta.
door_hanger_prompt_name=Haluatko antaa sille nimen, joka on helpompi muistaa? Nykyinen nimi:
door_hanger_button=OK
door_hanger_bye=Nähdään taas!
door_hanger_current=Nykyinen nimi:
door_hanger_button2=Okei!
# Infobar strings
infobar_screenshare_browser_message2=Jaat parhaillaan välilehtiäsi. Kaverisi näkevät kaikki välilehdet, joita napsautat
infobar_button_stop_label2=Lopeta jakaminen
infobar_button_stop_accesskey=L
infobar_button_disconnect_label=Katkaise yhteys
infobar_button_disconnect_accesskey=K
# Copy panel strings
copy_panel_dont_show_again_label=Älä näytä tätä uudelleen
copy_panel_cancel_button_label=Ei nyt
copy_panel_accept_button_label=Kyllä, näytä miten
# E10s not supported strings
@ -234,6 +245,7 @@ rooms_room_join_label=Liity keskusteluun
self_view_hidden_message=Omanäkymä piilotettu, mutta lähetetään edelleen. Muuta ikkunan kokoa nähdäksesi.
peer_left_session=Kaverisi lähti.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

View File

@ -193,16 +193,18 @@ rooms_signout_alert=Les conversations ouvertes vont être fermées
room_name_untitled_page=Page sans titre
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=À bientôt ! Vous pouvez accéder à cette session partagée à nimporte quel moment depuis le panneau Hello.
door_hanger_prompt_name=Voulez-vous lui donner un nom pour la mémoriser plus facilement ? Nom actuel :
door_hanger_button=OK
door_hanger_bye=À bientôt !
door_hanger_return2=Vous pouvez rejoindre cette session partagée à nimporte moment depuis le panneau Hello. Voulez-vous lui donner un nom facile à retenir ?
door_hanger_current=Nom actuel :
door_hanger_button2=Ok
# Infobar strings
infobar_screenshare_no_guest_message=Dès que lautre personne suivra le lien, elle pourra voir tous les onglets sur lesquels vous cliquerez.
infobar_screenshare_browser_message2=Vous partagez vos onglets. Vos amis pourront voir tous les onglets sur lesquels vous cliquez.
infobar_screenshare_browser_message3=Vous partagez à présent vos onglets. Votre contact verra tous les onglets sur lesquels vous cliquerez.
infobar_screenshare_stop_sharing_message=Vous ne partagez plus vos onglets
infobar_screenshare_stop_sharing_message2=Vous ne partagez plus vos onglets.
infobar_screenshare_stop_no_guest_message=Vous avez arrêté de partager vos onglets. Lorsquune autre personne rejoindra la conversation, elle ne pourra rien voir jusquà ce que vous recommenciez à partager vos onglets.
infobar_button_restart_label2=Recommencer à partager
infobar_button_restart_accesskey=e
infobar_button_stop_label2=Arrêter le partage
@ -210,6 +212,13 @@ infobar_button_stop_accesskey=A
infobar_button_disconnect_label=Déconnexion
infobar_button_disconnect_accesskey=D
# Copy panel strings
copy_panel_message=Besoin de partager cette page web ? Partagez longlet du navigateur avec un ami.
copy_panel_dont_show_again_label=Ne plus afficher ce message
copy_panel_cancel_button_label=Plus tard
copy_panel_accept_button_label=Oui, me montrer comment faire
# E10s not supported strings
e10s_not_supported_button_label=Ouvrir une nouvelle fenêtre

View File

@ -193,16 +193,18 @@ rooms_signout_alert=Iepene petearen sille sluten wurde
room_name_untitled_page=Titelleaze side
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=Oant letter! Jo kinne hjir altyd wer nei dizze dield sesje weromkeare fia it Hello-paniel.
door_hanger_prompt_name=Wolle jo it in makliker te ûnthâlden namme jaan? Aktuele namme:
door_hanger_button=OK
door_hanger_bye=Oant sjen!
door_hanger_return2=Jo kinne op elts momint weromkeare nei dizze dielde sesje fia it Hello-paniel. Wolle jo it in namme jaan dy't makliker te ûnthâlden is?
door_hanger_current=Aktuele namme:
door_hanger_button2=OK!
# Infobar strings
infobar_screenshare_no_guest_message=Sa gau as jo freon dielnimt, kin dizze elts ljepblêd sjen wêrop jo klikke.
infobar_screenshare_browser_message2=Jo diele jo ljepblêden. Elts ljepblêd dat jo oanklikke kin sjoen wurde troch jo freonen
infobar_screenshare_browser_message3=Jo diele no jo ljepblêden. Jo freon sjocht elts ljepblêd wêrop jo klikke.
infobar_screenshare_stop_sharing_message=Jo diele net langer jo ljepblêden
infobar_screenshare_stop_sharing_message2=Jo diele net langer jo ljepblêden.
infobar_screenshare_stop_no_guest_message=Jo diele net langer jo ljepblêden. As jo freon dielnimt, sil der neat te sjen wêze oant jo opnij diele.
infobar_button_restart_label2=Dielen ferfetsje
infobar_button_restart_accesskey=D
infobar_button_stop_label2=Dielen stopje
@ -210,6 +212,13 @@ infobar_button_stop_accesskey=S
infobar_button_disconnect_label=Ferbining ferbrekke
infobar_button_disconnect_accesskey=F
# Copy panel strings
copy_panel_message=Wolle jo dizze webside diele? Diel jo browserljepblêd mei in freon.
copy_panel_dont_show_again_label=Net mear toane
copy_panel_cancel_button_label=No net
copy_panel_accept_button_label=Ja, lit it my sjen
# E10s not supported strings
e10s_not_supported_button_label=Nij finster iepenje

View File

@ -193,16 +193,18 @@ rooms_signout_alert=Iepene petearen sille sluten wurde
room_name_untitled_page=Titelleaze side
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=Oant letter! Jo kinne hjir altyd wer nei dizze dield sesje weromkeare fia it Hello-paniel.
door_hanger_prompt_name=Wolle jo it in makliker te ûnthâlden namme jaan? Aktuele namme:
door_hanger_button=OK
door_hanger_bye=Oant sjen!
door_hanger_return2=Jo kinne op elts momint weromkeare nei dizze dielde sesje fia it Hello-paniel. Wolle jo it in namme jaan dy't makliker te ûnthâlden is?
door_hanger_current=Aktuele namme:
door_hanger_button2=OK!
# Infobar strings
infobar_screenshare_no_guest_message=Sa gau as jo freon dielnimt, kin dizze elts ljepblêd sjen wêrop jo klikke.
infobar_screenshare_browser_message2=Jo diele jo ljepblêden. Elts ljepblêd dat jo oanklikke kin sjoen wurde troch jo freonen
infobar_screenshare_browser_message3=Jo diele no jo ljepblêden. Jo freon sjocht elts ljepblêd wêrop jo klikke.
infobar_screenshare_stop_sharing_message=Jo diele net langer jo ljepblêden
infobar_screenshare_stop_sharing_message2=Jo diele net langer jo ljepblêden.
infobar_screenshare_stop_no_guest_message=Jo diele net langer jo ljepblêden. As jo freon dielnimt, sil der neat te sjen wêze oant jo opnij diele.
infobar_button_restart_label2=Dielen ferfetsje
infobar_button_restart_accesskey=D
infobar_button_stop_label2=Dielen stopje
@ -210,6 +212,13 @@ infobar_button_stop_accesskey=S
infobar_button_disconnect_label=Ferbining ferbrekke
infobar_button_disconnect_accesskey=F
# Copy panel strings
copy_panel_message=Wolle jo dizze webside diele? Diel jo browserljepblêd mei in freon.
copy_panel_dont_show_again_label=Net mear toane
copy_panel_cancel_button_label=No net
copy_panel_accept_button_label=Ja, lit it my sjen
# E10s not supported strings
e10s_not_supported_button_label=Nij finster iepenje

View File

@ -4,8 +4,6 @@
# Panel Strings
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
## use "..." if \u2026 doesn't suit traditions in your locale.
@ -44,8 +42,8 @@ fte_slide_1_title=Brabhsaich duilleagan-lìn còmhla ri caraid
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_1_copy=Co-dhiù a bheil thu airson turas a chur air dòigh no prèasant a cheannach, nì thu co-dhùnaidhean nas luaithe le {{clientShortname2}}.
fte_slide_2_title=Iomair an aon ràmh
fte_slide_2_copy=Cleachd a chabadaich teacsa no bhideo na broinn gu beachdan a cho-roinneadh s coimeas a dhèanamh eadar roghainnean agus co-dhùnadh a ruigsinn.
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_3_title=Thoir cuireadh dha charaid le ceangal
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
@ -57,9 +55,7 @@ fte_slide_4_title=Lorg ìomhaigheag {{clientSuperShortname}} airson tòiseachadh
## will be replaced by the brand short name.
fte_slide_4_copy=Nuair a bhios tu air duilleag a lorg a bu toigh leat bruidhinn oirre, briog air an ìomhaigheag aig {{brandShortname}} gus ceangal a chruthachadh. Cuir e dhan charaid agad an uairsin air dòigh sam bith a thogras tu!
invite_header_text_bold=Thoir cuireadh do chuideigin a rùrachadh na duilleige seo còmhla riut!
invite_header_text_bold2=Thoir cuireadh do charaid ach an ceangail e riut!
invite_header_text3=Feumaidh tu co-dhiù dithis mus obraich Firefox Hello, nach cuir thu ceangal gu caraid a rùraicheas an lìon còmhla riut?
invite_header_text4=Co-roinn an ceangal seo ach am brabhsaich sibh an lìon còmhla.
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
## invite_email_link_button, invite_facebook_button2): These labels appear under
@ -194,16 +190,12 @@ rooms_signout_alert=Thèid còmhraidhean fosgailte a dhùnadh
room_name_untitled_page=Duilleag gun tiotal
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=Na bi fada gun tilleadh! Is urrainn dhut tilleadh gun t-seisean cho-roinnte seo uair sam bith slighe panail Hello.
door_hanger_prompt_name=An doir thu ainm air a bhios nas fhasa ri chuimhneachadh? Na tha air an-dràsta:
door_hanger_button=Bheir
# Infobar strings
infobar_screenshare_no_guest_message=Nuair a bhios do charaidean air an tighinn a-steach, chì iad gach taba a bhriogas tu air.
infobar_screenshare_browser_message2=Tha thu a co-roinneadh nan tabaichean agad. Chì do charaidean taba sam bith a bhriogas tu air
infobar_screenshare_browser_message3=Tha thu a co-roinneadh nan tabaichean agad a-nis. Chì do charaid gach taba a bhriogas tu air.
infobar_screenshare_stop_sharing_message=Chan eil thu a co-roinneadh nan tabaichean agad tuilleadh
infobar_button_restart_label2=Tòisich air co-roinneadh
infobar_button_restart_accesskey=A
infobar_button_stop_label2=Sguir a cho-roinneadh
@ -211,6 +203,9 @@ infobar_button_stop_accesskey=S
infobar_button_disconnect_label=Dì-cheangail
infobar_button_disconnect_accesskey=D
# Copy panel strings
# E10s not supported strings
e10s_not_supported_button_label=Fosgail uinneag ùr

View File

@ -4,7 +4,6 @@
# Panel Strings
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
## use "..." if \u2026 doesn't suit traditions in your locale.
@ -19,18 +18,28 @@
## will be replaced by the super short brandname.
## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
## LOCALIZATION_NOTE(first_time_experience_content): Message describing
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
## ways to use Hello project.
## First Time Experience Slides
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
## will be replaced by the brand short name.
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
## invite_email_link_button, invite_facebook_button2): These labels appear under
## an iconic button for the invite view.
# Status text
# Error bars
## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
## These may be displayed at the top of the panel.
@ -98,12 +107,8 @@ hangup_button_caption2=יציאה
# Infobar strings
# Context in conversation strings
# Copy panel strings
## LOCALIZATION NOTE(no_conversations_message_heading2): Title shown when user
## has no conversations available.
## LOCALIZATION NOTE(no_conversations_start_message2): Subheading inviting the
## user to start a new conversation.
# E10s not supported strings

View File

@ -4,7 +4,6 @@
# Panel Strings
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
## use "..." if \u2026 doesn't suit traditions in your locale.
@ -19,18 +18,28 @@
## will be replaced by the super short brandname.
## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
## LOCALIZATION_NOTE(first_time_experience_content): Message describing
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
## ways to use Hello project.
## First Time Experience Slides
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
## will be replaced by the super short brand name.
## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
## will be replaced by the brand short name.
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
## invite_email_link_button, invite_facebook_button2): These labels appear under
## an iconic button for the invite view.
# Status text
# Error bars
## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
## These may be displayed at the top of the panel.
@ -112,12 +121,8 @@ tour_label=दौरा
# Infobar strings
# Context in conversation strings
# Copy panel strings
## LOCALIZATION NOTE(no_conversations_message_heading2): Title shown when user
## has no conversations available.
## LOCALIZATION NOTE(no_conversations_start_message2): Subheading inviting the
## user to start a new conversation.
# E10s not supported strings

View File

@ -193,16 +193,18 @@ rooms_signout_alert=Wočinjene rozmołwy so začinja
room_name_untitled_page=Strona bjez titula
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=Hač potom! Móžeće so k tutomu zhromadnemu posedźenju přez wokno Hello kóždy čas wróćić.
door_hanger_prompt_name=Byšće prošu mjeno podać, kotrež da so sej lóšo spomjatkować? Aktualne mjeno:
door_hanger_button=W porjadku
door_hanger_bye=Hač potom!
door_hanger_return2=Móžeće so k tutomu dźělenemu posedźenju kóždy čas přez wobłuk Hello wróćić. Chceće jemu mjeno dać, kotrež móžeće sej lóšo spomjatkować?
door_hanger_current=Aktualne mjeno:
door_hanger_button2=W porjadku!
# Infobar strings
infobar_screenshare_no_guest_message=Tak ruče kaž so waš přećel přidruža, móžetaj rajtark widźeć, na kotryž kliknjeće.
infobar_screenshare_browser_message2=Dźěliće swoje rajtarki. Waši přećeljo móža kóždy rajtark widźeć, na kotryž kliknjeće
infobar_screenshare_browser_message3=Dźělitaj nětko swoje rajtarki. Waš přećel budźe rajtark widźeć, na kotryž kliknjeće.
infobar_screenshare_stop_sharing_message=Hižo swoje rajtarki njedźěliće
infobar_screenshare_stop_sharing_message2=Swoje rajtarki hižo njedźěliće.
infobar_screenshare_stop_no_guest_message=Swoje rajtarki hižo njedźěliće. Hdyž so waš přećel přidruža, njemóže ničo widźeć, doniž zaso njedźěliće.
infobar_button_restart_label2=Dźělenje znowa startować
infobar_button_restart_accesskey=n
infobar_button_stop_label2=Dźělenje zastajić
@ -210,6 +212,13 @@ infobar_button_stop_accesskey=S
infobar_button_disconnect_label=Zwisk dźělić
infobar_button_disconnect_accesskey=Z
# Copy panel strings
copy_panel_message=Chceće tutu webstronu dźělić? Dźělće swój wobhladowakowy rajtark z přećelom.
copy_panel_dont_show_again_label=Hižo so njepokazać
copy_panel_cancel_button_label=Nic nětko
copy_panel_accept_button_label=Haj, pokazajće mi kak
# E10s not supported strings
e10s_not_supported_button_label=Nowe wokno wočinić

View File

@ -193,16 +193,18 @@ rooms_signout_alert=A nyitott beszélgetések bezáródnak
room_name_untitled_page=Névtelen oldal
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=Viszontlátásra! Bármikor visszatérhet ehhez a megosztott munkamenethez a Hello panelen.
door_hanger_prompt_name=Szeretne egy könnyebben megjegyezhető nevet adni neki? A jelenlegi név:
door_hanger_button=OK
door_hanger_bye=Később találkozunk!
door_hanger_return2=A Hello panel segítségével bármikor visszatérhet a megosztott munkamenethez. Szeretne egy könnyebben megjegyezhető nevet megadni?
door_hanger_current=Jelenlegi név:
door_hanger_button2=OK!
# Infobar strings
infobar_screenshare_no_guest_message=Amint ismerőse bejelentkezik, máris láthatja azokat a lapjait, amelyekre kattint.
infobar_screenshare_browser_message2=Megosztja a lapjait. Ismerősei látni fogják bármely lap tartalmát, amelyre rákattint.
infobar_screenshare_browser_message3=Mostmár megosztja a lapjait. Ismerőse láthatja bármely lap tartalmát, amelyre rákattint.
infobar_screenshare_stop_sharing_message=Már nem osztja meg böngészőlapjait
infobar_screenshare_stop_sharing_message2=Már nem osztja meg böngészőlapjait.
infobar_screenshare_stop_no_guest_message=Leállította a böngészőlapjai megosztását. Amikor egy ismerőse csatlakozik, akkor nem fog semmit sem látni addig, amíg újra nem indítja a megosztást.
infobar_button_restart_label2=Megosztás újrakezdése
infobar_button_restart_accesskey=r
infobar_button_stop_label2=Megosztás leállítása
@ -210,6 +212,13 @@ infobar_button_stop_accesskey=L
infobar_button_disconnect_label=Bontás
infobar_button_disconnect_accesskey=o
# Copy panel strings
copy_panel_message=Meg kell osztani ezt a weboldalt? Ossza meg a böngészőlapját egy ismerősével.
copy_panel_dont_show_again_label=Ne mutassa ezt újra
copy_panel_cancel_button_label=Most nem
copy_panel_accept_button_label=Igen, mutassa meg hogyan
# E10s not supported strings
e10s_not_supported_button_label=Új ablak indítása

View File

@ -193,16 +193,18 @@ rooms_signout_alert=Զրույցները կփակվեն
room_name_untitled_page=Անանուն էջ
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_return=Առայժմ: Կարող եք ցանկացած ժամանակ վերադառնալ համաօգտագործվող այս աշխատաշրջան՝ Hello վահանակի միջոցով:
door_hanger_prompt_name=Ցանկանու՞մ եք տալ անուն, որ հեշտ հիշվի: Ընթացիկ անունը՝
door_hanger_button=Լավ
door_hanger_bye=Կտեսնվենք ավելի ուշ
door_hanger_return2=Կարող եք վերադառնալ համաօգտագործվող այս աշխատաշրջանին ցանկացած ժամանակ՝ Hello-ի վահանակի միջոցով: Ցանկանու՞մ եք տալ անուն՝ հեշտ հիշելու համար:
door_hanger_current=Ընթացիկ անուն.
door_hanger_button2=Լավ
# Infobar strings
infobar_screenshare_no_guest_message=Երբ ձեր ընկերը միանա՝ կկարողանա տեսնել բոլոր այն ներդիրները, որոնց դուք կսեղմեք:
infobar_screenshare_browser_message2=Դուք համաօգտագործում եք ձեր ներդիրները: Ցանկացած ներդիր, որ սեղմում եք կտեսնեն ձեր ընկերները:
infobar_screenshare_browser_message3=Այժմ դուք համաօգտագործում եք ձեր ներդիրները: Ձեր ընկերը կտեսնի բոլոր այն ներդիրները, որոնց դուք կսեղմեք:
infobar_screenshare_stop_sharing_message=Դուք այլևս չեք համաօգտագործում ձեր ներդիրները
infobar_screenshare_stop_sharing_message2=Դուք այլևս չեք համաօգտագործում ձեր ներդիրները:
infobar_screenshare_stop_no_guest_message=Դուք այլևս չեք համաօգտագործում ձեր ներդիրները: Երբ ձեր ընկերը միանա՝ դրանք չի տեսնի, մինչև որ չվերսկսեք համաօգտագործումը:
infobar_button_restart_label2=Վերսկսել համաօգտագործումը
infobar_button_restart_accesskey=e
infobar_button_stop_label2=Կանգնեցնել համաօգտագործումը
@ -210,6 +212,13 @@ infobar_button_stop_accesskey=S
infobar_button_disconnect_label=Կապախզել
infobar_button_disconnect_accesskey=D
# Copy panel strings
copy_panel_message=Ցանկանու՞մ եք համաօգտագործել այս վեբ էջը: Համաօգտագործեք դիտարկիչի ներդիրը ընկերոջ հետ:
copy_panel_dont_show_again_label=Այլևս չցուցադրել
copy_panel_cancel_button_label=Ոչ հիմա
copy_panel_accept_button_label=Այո, ցույց տալ, թե ինչպես
# E10s not supported strings
e10s_not_supported_button_label=Բացել նոր պատուհան

Some files were not shown because too many files have changed in this diff Show More