merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2016-06-24 11:12:37 +02:00
Родитель 7c1fa57b1d e5a7c8e40c
Коммит d06bb83b2e
98 изменённых файлов: 8102 добавлений и 6942 удалений

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

@ -44,6 +44,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
"resource://gre/modules/UpdateUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Color",
"resource://gre/modules/Color.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "Favicons",
"@mozilla.org/browser/favicon-service;1",
"mozIAsyncFavicons");
@ -1018,23 +1020,10 @@ var gBrowserInit = {
if (window.matchMedia("(-moz-os-version: windows-win8)").matches &&
window.matchMedia("(-moz-windows-default-theme)").matches) {
let windowFrameColor = Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {})
.Windows8WindowFrameColor.get();
// Formula from W3C's WCAG 2.0 spec's color ratio and relative luminance,
// section 1.3.4, http://www.w3.org/TR/WCAG20/ .
windowFrameColor = windowFrameColor.map((color) => {
if (color <= 10) {
return color / 255 / 12.92;
}
return Math.pow(((color / 255) + 0.055) / 1.055, 2.4);
});
let backgroundLuminance = windowFrameColor[0] * 0.2126 +
windowFrameColor[1] * 0.7152 +
windowFrameColor[2] * 0.0722;
let foregroundLuminance = 0; // Default to black for foreground text.
let contrastRatio = (backgroundLuminance + 0.05) / (foregroundLuminance + 0.05);
if (contrastRatio < 3) {
let windowFrameColor = new Color(...Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {})
.Windows8WindowFrameColor.get());
// Default to black for foreground text.
if (!windowFrameColor.isContrastRatioAcceptable(new Color(0, 0, 0))) {
document.documentElement.setAttribute("darkwindowframe", "true");
}
}

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

@ -729,7 +729,7 @@
aria-label="&urlbar.passwordNotificationAnchor.label;"/>
<image id="plugins-notification-icon" class="notification-anchor-icon plugin-icon" role="button"
aria-label="&urlbar.pluginsNotificationAnchor.label;"/>
<image id="web-notifications-notification-icon" class="notification-anchor-icon web-notifications-icon" role="button"
<image id="web-notifications-notification-icon" class="notification-anchor-icon desktop-notification-icon" role="button"
aria-label="&urlbar.webNotsNotificationAnchor3.label;"/>
<image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon camera-icon" role="button"
aria-label="&urlbar.webRTCShareDevicesNotificationAnchor.label;"/>
@ -743,7 +743,7 @@
aria-label="&urlbar.webRTCShareScreenNotificationAnchor.label;"/>
<image id="webRTC-sharingScreen-notification-icon" class="notification-anchor-icon screen-icon in-use" role="button"
aria-label="&urlbar.webRTCSharingScreenNotificationAnchor.label;"/>
<image id="pointerLock-notification-icon" class="notification-anchor-icon pointer-icon" role="button"
<image id="pointerLock-notification-icon" class="notification-anchor-icon pointerLock-icon" role="button"
aria-label="&urlbar.pointerLockNotificationAnchor.label;"/>
<image id="servicesInstall-notification-icon" class="notification-anchor-icon service-icon" role="button"
aria-label="&urlbar.servicesNotificationAnchor.label;"/>

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

@ -1944,56 +1944,6 @@ BrowserGlue.prototype = {
let xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
if (currentUIVersion < 2) {
// This code adds the customizable bookmarks button.
let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
// Need to migrate only if toolbar is customized and the element is not found.
if (currentset &&
currentset.indexOf("bookmarks-menu-button-container") == -1) {
currentset += ",bookmarks-menu-button-container";
xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
}
}
if (currentUIVersion < 4) {
// This code moves the home button to the immediate left of the bookmarks menu button.
let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
// Need to migrate only if toolbar is customized and the elements are found.
if (currentset &&
currentset.indexOf("home-button") != -1 &&
currentset.indexOf("bookmarks-menu-button-container") != -1) {
currentset = currentset.replace(/(^|,)home-button($|,)/, "$1$2")
.replace(/(^|,)bookmarks-menu-button-container($|,)/,
"$1home-button,bookmarks-menu-button-container$2");
xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
}
}
if (currentUIVersion < 5) {
// This code uncollapses PersonalToolbar if its collapsed status is not
// persisted, and user customized it or changed default bookmarks.
//
// If the user does not have a persisted value for the toolbar's
// "collapsed" attribute, try to determine whether it's customized.
if (!xulStore.hasValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed")) {
// We consider the toolbar customized if it has more than
// 3 children, or if it has a persisted currentset value.
let toolbarIsCustomized = xulStore.hasValue(BROWSER_DOCURL,
"PersonalToolbar", "currentset");
let getToolbarFolderCount = function () {
let toolbarFolder =
PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId).root;
let toolbarChildCount = toolbarFolder.childCount;
toolbarFolder.containerOpen = false;
return toolbarChildCount;
};
if (toolbarIsCustomized || getToolbarFolderCount() > 3) {
xulStore.setValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed", "false");
}
}
}
if (currentUIVersion < 9) {
// This code adds the customizable downloads buttons.
let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");

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

@ -51,7 +51,6 @@ support-files =
[browser_sidebarpanels_click.js]
skip-if = true # temporarily disabled for breaking the treeview - bug 658744
[browser_sort_in_library.js]
[browser_toolbar_migration.js]
[browser_toolbarbutton_menu_context.js]
[browser_views_liveupdate.js]
[browser_bookmark_all_tabs.js]

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

@ -1,104 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/**
* Tests PersonalToolbar migration path.
*/
var bg = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver);
var gOriginalMigrationVersion;
const BROWSER_URL = getBrowserURL();
var localStore = {
get xulStore() {
return Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
},
getValue: function getValue(aProperty)
{
return this.xulStore.getValue(BROWSER_URL, "PersonalToolbar", aProperty);
},
setValue: function setValue(aProperty, aValue)
{
if (aValue) {
this.xulStore.setValue(BROWSER_URL, "PersonalToolbar", aProperty, aValue);
} else {
this.xulStore.removeValue(BROWSER_URL, "PersonalToolbar", aProperty);
}
}
};
var gTests = [
function test_explicitly_collapsed_toolbar()
{
info("An explicitly collapsed toolbar should not be uncollapsed.");
localStore.setValue("collapsed", "true");
bg.observe(null, "browser-glue-test", "force-ui-migration");
is(localStore.getValue("collapsed"), "true", "Toolbar is collapsed");
},
function test_customized_toolbar()
{
info("A customized toolbar should be uncollapsed.");
localStore.setValue("currentset", "splitter");
bg.observe(null, "browser-glue-test", "force-ui-migration");
is(localStore.getValue("collapsed"), "false", "Toolbar has been uncollapsed");
},
function test_many_bookmarks_toolbar()
{
info("A toolbar with added bookmarks should be uncollapsed.");
let ids = [];
ids.push(
PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
PlacesUtils.bookmarks.DEFAULT_INDEX)
);
ids.push(
PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
PlacesUtils.bookmarks.DEFAULT_INDEX)
);
ids.push(
PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
PlacesUtils.bookmarks.DEFAULT_INDEX)
);
ids.push(
PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
PlacesUtils.bookmarks.DEFAULT_INDEX)
);
bg.observe(null, "browser-glue-test", "force-ui-migration");
is(localStore.getValue("collapsed"), "false", "Toolbar has been uncollapsed");
},
];
function test()
{
gOriginalMigrationVersion = Services.prefs.getIntPref("browser.migration.version");
registerCleanupFunction(clean);
if (localStore.getValue("currentset") !== null) {
info("Toolbar currentset was persisted by a previous test, fixing it.");
localStore.setValue("currentset", null);
}
if (localStore.getValue("collapsed") !== null) {
info("Toolbar collapsed status was persisted by a previous test, fixing it.");
localStore.setValue("collapsed", null);
}
while (gTests.length) {
clean();
Services.prefs.setIntPref("browser.migration.version", 4);
gTests.shift().call();
}
}
function clean()
{
Services.prefs.setIntPref("browser.migration.version", gOriginalMigrationVersion);
localStore.setValue("currentset", null);
localStore.setValue("collapsed", null);
}

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

@ -538,7 +538,8 @@ var WindowListener = {
state = "error";
mozL10nId += "-error";} else
if (this.isSlideshowOpen) {
state = "slideshow";} else
state = "slideshow";
suffix = ".label";} else
if (this.MozLoopService.screenShareActive) {
state = "action";
mozL10nId += "-screensharing";} else
@ -663,7 +664,9 @@ var WindowListener = {
this.activeSound.load();
this.activeSound.play();
this.activeSound.addEventListener("ended", function () {_this11.activeSound = undefined;}, false);},
this.activeSound.addEventListener("ended", function () {
_this11.activeSound = undefined;},
false);},
/**
@ -744,41 +747,7 @@ var WindowListener = {
this._listeningToTabSelect = false;
this._browserSharePaused = false;
this._currentRoomToken = null;
this._sendTelemetryEventsIfNeeded();},
/**
* Sends telemetry events for pause/ resume buttons if needed.
*/
_sendTelemetryEventsIfNeeded: function _sendTelemetryEventsIfNeeded() {
// The user can't click Resume button without clicking Pause button first.
if (!this._pauseButtonClicked) {
return;}
var buckets = this.constants.SHARING_SCREEN;
this.LoopAPI.sendMessageToHandler({
name: "TelemetryAddValue",
data: [
"LOOP_INFOBAR_ACTION_BUTTONS",
buckets.PAUSED] });
if (this._resumeButtonClicked) {
this.LoopAPI.sendMessageToHandler({
name: "TelemetryAddValue",
data: [
"LOOP_INFOBAR_ACTION_BUTTONS",
buckets.RESUMED] });}
this._pauseButtonClicked = false;
this._resumeButtonClicked = false;},
this._currentRoomToken = null;},
/**
@ -942,11 +911,8 @@ var WindowListener = {
buttonNode.accessKey = stringObj.accesskey;
LoopUI.MozLoopService.toggleBrowserSharing(_this13._browserSharePaused);
if (_this13._browserSharePaused) {
_this13._pauseButtonClicked = true;
// if paused we stop sharing remote cursors
_this13.removeRemoteCursor();} else
{
_this13._resumeButtonClicked = true;}
_this13.removeRemoteCursor();}
return true;},
@ -1418,10 +1384,10 @@ function startup(data) {
// Load our stylesheets.
var styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"].
getService(Components.interfaces.nsIStyleSheetService);
var sheets = ["chrome://loop-shared/skin/loop.css"];
var sheets = [
"chrome://loop-shared/skin/loop.css",
"chrome://loop/skin/platform.css"];var _iteratorNormalCompletion2 = true;var _didIteratorError2 = false;var _iteratorError2 = undefined;try {
if (AppConstants.platform != "linux") {
sheets.push("chrome://loop/skin/platform.css");}var _iteratorNormalCompletion2 = true;var _didIteratorError2 = false;var _iteratorError2 = undefined;try {
for (var _iterator2 = sheets[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {var sheet = _step2.value;

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

@ -83,47 +83,6 @@ var getObjectAPIFunctionName = function getObjectAPIFunctionName(action) {
return funcName.charAt(0).toLowerCase() + funcName.substr(1);};
/**
* Retrieves a list of Social Providers from the Social API that are explicitly
* capable of sharing URLs.
* It also adds a listener that is fired whenever a new Provider is added or
* removed.
*
* @return {Array} Sorted list of share-capable Social Providers.
*/
var updateSocialProvidersCache = function updateSocialProvidersCache() {
var providers = [];var _iteratorNormalCompletion2 = true;var _didIteratorError2 = false;var _iteratorError2 = undefined;try {
for (var _iterator2 = Social.providers[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {var provider = _step2.value;
if (!provider.shareURL) {
continue;}
// Only pass the relevant data on to content.
providers.push({
iconURL: provider.iconURL,
name: provider.name,
origin: provider.origin });}} catch (err) {_didIteratorError2 = true;_iteratorError2 = err;} finally {try {if (!_iteratorNormalCompletion2 && _iterator2.return) {_iterator2.return();}} finally {if (_didIteratorError2) {throw _iteratorError2;}}}
var providersWasSet = !!gSocialProviders;
// Replace old with new.
gSocialProviders = providers.sort(function (a, b) {return (
a.name.toLowerCase().localeCompare(b.name.toLowerCase()));});
// Start listening for changes in the social provider list, if we're not
// doing that yet.
if (!providersWasSet) {
Services.obs.addObserver(updateSocialProvidersCache, "social:providers-changed", false);} else
{
// Dispatch an event to content to let stores freshen-up.
LoopAPIInternal.broadcastPushMessage("SocialProvidersChanged");}
return gSocialProviders;};
/**
* Checks that [browser.js]'s global variable `gMultiProcessBrowser` is active,
* instead of checking on first available browser element.
@ -139,7 +98,6 @@ var gBrowserSharingListeners = new Set();
var gBrowserSharingWindows = new Set();
var gPageListeners = null;
var gOriginalPageListeners = null;
var gSocialProviders = null;
var gStringBundle = null;
var gStubbedMessageHandlers = null;
var kBatchMessage = "Batch";
@ -277,27 +235,6 @@ var kMessageHandlers = {
reply();},
/**
* Activates the Social Share panel with the Social Provider panel opened
* when the popup open.
*
* @param {Object} message Message meant for the handler function, containing
* the following parameters in its `data` property:
* [ ]
* @param {Function} reply Callback function, invoked with the result of this
* message handler. The result will be sent back to
* the senders' channel.
*/
AddSocialShareProvider: function AddSocialShareProvider(message, reply) {
var win = Services.wm.getMostRecentWindow("navigator:browser");
if (!win || !win.SocialShare) {
reply();
return;}
win.SocialShare.showDirectory(win.LoopUI.toolbarButton.anchor);
reply();},
/**
* Composes an email via the external protocol service.
*
@ -415,9 +352,9 @@ var kMessageHandlers = {
// Get the map of strings.
var strings = MozLoopService.getStrings();
// Convert it to an object.
gStringBundle = {};var _iteratorNormalCompletion3 = true;var _didIteratorError3 = false;var _iteratorError3 = undefined;try {
for (var _iterator3 = strings.entries()[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {var _ref = _step3.value;var _ref2 = _slicedToArray(_ref, 2);var key = _ref2[0];var value = _ref2[1];
gStringBundle[key] = value;}} catch (err) {_didIteratorError3 = true;_iteratorError3 = err;} finally {try {if (!_iteratorNormalCompletion3 && _iterator3.return) {_iterator3.return();}} finally {if (_didIteratorError3) {throw _iteratorError3;}}}
gStringBundle = {};var _iteratorNormalCompletion2 = true;var _didIteratorError2 = false;var _iteratorError2 = undefined;try {
for (var _iterator2 = strings.entries()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {var _ref = _step2.value;var _ref2 = _slicedToArray(_ref, 2);var key = _ref2[0];var value = _ref2[1];
gStringBundle[key] = value;}} catch (err) {_didIteratorError2 = true;_iteratorError2 = err;} finally {try {if (!_iteratorNormalCompletion2 && _iterator2.return) {_iterator2.return();}} finally {if (_didIteratorError2) {throw _iteratorError2;}}}
reply(gStringBundle);},
@ -438,10 +375,7 @@ var kMessageHandlers = {
LOOP_SESSION_TYPE: LOOP_SESSION_TYPE,
LOOP_MAU_TYPE: LOOP_MAU_TYPE,
ROOM_CREATE: ROOM_CREATE,
ROOM_DELETE: ROOM_DELETE,
SHARING_ROOM_URL: SHARING_ROOM_URL,
SHARING_SCREEN: SHARING_SCREEN,
TWO_WAY_MEDIA_CONN_LENGTH: TWO_WAY_MEDIA_CONN_LENGTH });},
SHARING_ROOM_URL: SHARING_ROOM_URL });},
@ -555,8 +489,8 @@ var kMessageHandlers = {
* the senders' channel.
*/
GetErrors: function GetErrors(message, reply) {
var errors = {};var _iteratorNormalCompletion4 = true;var _didIteratorError4 = false;var _iteratorError4 = undefined;try {
for (var _iterator4 = MozLoopService.errors[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {var _ref3 = _step4.value;var _ref4 = _slicedToArray(_ref3, 2);var type = _ref4[0];var error = _ref4[1];
var errors = {};var _iteratorNormalCompletion3 = true;var _didIteratorError3 = false;var _iteratorError3 = undefined;try {
for (var _iterator3 = MozLoopService.errors[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {var _ref3 = _step3.value;var _ref4 = _slicedToArray(_ref3, 2);var type = _ref4[0];var error = _ref4[1];
// if error.error is an nsIException, just delete it since it's hard
// to clone across the boundary.
if (error.error instanceof Ci.nsIException) {
@ -566,7 +500,7 @@ var kMessageHandlers = {
delete error.error;}
errors[type] = cloneableError(error);}} catch (err) {_didIteratorError4 = true;_iteratorError4 = err;} finally {try {if (!_iteratorNormalCompletion4 && _iterator4.return) {_iterator4.return();}} finally {if (_didIteratorError4) {throw _iteratorError4;}}}
errors[type] = cloneableError(error);}} catch (err) {_didIteratorError3 = true;_iteratorError3 = err;} finally {try {if (!_iteratorNormalCompletion3 && _iterator3.return) {_iterator3.return();}} finally {if (_didIteratorError3) {throw _iteratorError3;}}}
return reply(errors);},
@ -699,25 +633,6 @@ var kMessageHandlers = {
win.gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetPageData");},
/**
* Returns a sorted list of Social Providers that can share URLs. See
* `updateSocialProvidersCache()` for more information.
*
* @param {Object} message Message meant for the handler function, containing
* the following parameters in its `data` property:
* [ ]
* @param {Function} reply Callback function, invoked with the result of this
* message handler. The result will be sent back to
* the senders' channel.
* @return {Array} Sorted list of share-capable Social Providers.
*/
GetSocialShareProviders: function GetSocialShareProviders(message, reply) {
if (!gSocialProviders) {
updateSocialProvidersCache();}
reply(gSocialProviders);},
/**
* Gets an object with data that represents the currently
* authenticated user's identity.
@ -990,15 +905,15 @@ var kMessageHandlers = {
if (gBrowserSharingListeners.size > 0) {
// There are still clients listening in, so keep on listening...
reply();
return;}var _iteratorNormalCompletion5 = true;var _didIteratorError5 = false;var _iteratorError5 = undefined;try {
return;}var _iteratorNormalCompletion4 = true;var _didIteratorError4 = false;var _iteratorError4 = undefined;try {
for (var _iterator5 = gBrowserSharingWindows[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {var win = _step5.value;
for (var _iterator4 = gBrowserSharingWindows[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {var win = _step4.value;
win = win.get();
if (!win) {
continue;}
win.LoopUI.stopBrowserSharing();}} catch (err) {_didIteratorError5 = true;_iteratorError5 = err;} finally {try {if (!_iteratorNormalCompletion5 && _iterator5.return) {_iterator5.return();}} finally {if (_didIteratorError5) {throw _iteratorError5;}}}
win.LoopUI.stopBrowserSharing();}} catch (err) {_didIteratorError4 = true;_iteratorError4 = err;} finally {try {if (!_iteratorNormalCompletion4 && _iterator4.return) {_iterator4.return();}} finally {if (_didIteratorError4) {throw _iteratorError4;}}}
NewTabURL.reset();
@ -1090,42 +1005,6 @@ var kMessageHandlers = {
reply();},
/**
* Share a room URL with the Social API.
*
* @param {Object} message Message meant for the handler function, containing
* the following parameters in its `data` property:
* [
* {String} providerOrigin URL fragment that identifies
* a social provider
* {String} roomURL URL of a room
* {String} title Title of the sharing message
* {String} body Body of the sharing message
* ]
* @param {Function} reply Callback function, invoked with the result of this
* message handler. The result will be sent back to
* the senders' channel.
*/
SocialShareRoom: function SocialShareRoom(message, reply) {
var win = Services.wm.getMostRecentWindow("navigator:browser");
if (!win || !win.SocialShare) {
reply();
return;}var _message$data10 = _slicedToArray(
message.data, 4);var providerOrigin = _message$data10[0];var roomURL = _message$data10[1];var title = _message$data10[2];var body = _message$data10[3];
var graphData = {
url: roomURL,
title: title };
if (body) {
graphData.body = body;}
win.SocialShare.sharePage(providerOrigin, graphData, null,
win.LoopUI.toolbarButton.anchor);
reply();},
/**
* Adds a value to a telemetry histogram.
*
@ -1141,8 +1020,8 @@ var kMessageHandlers = {
* message handler. The result will be sent back to
* the senders' channel.
*/
TelemetryAddValue: function TelemetryAddValue(message, reply) {var _message$data11 = _slicedToArray(
message.data, 2);var histogramId = _message$data11[0];var value = _message$data11[1];
TelemetryAddValue: function TelemetryAddValue(message, reply) {var _message$data10 = _slicedToArray(
message.data, 2);var histogramId = _message$data10[0];var value = _message$data10[1];
if (histogramId === "LOOP_ACTIVITY_COUNTER") {
var pref = "mau." + kMauPrefMap.get(value);
@ -1189,13 +1068,13 @@ var LoopAPIInternal = {
new RemotePages("about:loopconversation"),
// Slideshow added here to expose the loop api to make L10n work.
// XXX Can remove once slideshow is made remote.
new RemotePages("chrome://loop/content/panels/slideshow.html")];var _iteratorNormalCompletion6 = true;var _didIteratorError6 = false;var _iteratorError6 = undefined;try {
for (var _iterator6 = gPageListeners[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {var page = _step6.value;
new RemotePages("chrome://loop/content/panels/slideshow.html")];var _iteratorNormalCompletion5 = true;var _didIteratorError5 = false;var _iteratorError5 = undefined;try {
for (var _iterator5 = gPageListeners[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {var page = _step5.value;
page.addMessageListener(kMessageName, this.handleMessage.bind(this));}
// Subscribe to global events:
} catch (err) {_didIteratorError6 = true;_iteratorError6 = err;} finally {try {if (!_iteratorNormalCompletion6 && _iterator6.return) {_iterator6.return();}} finally {if (_didIteratorError6) {throw _iteratorError6;}}}Services.obs.addObserver(this.handleStatusChanged, "loop-status-changed", false);},
} catch (err) {_didIteratorError5 = true;_iteratorError5 = err;} finally {try {if (!_iteratorNormalCompletion5 && _iterator5.return) {_iterator5.return();}} finally {if (_didIteratorError5) {throw _iteratorError5;}}}Services.obs.addObserver(this.handleStatusChanged, "loop-status-changed", false);},
/**
@ -1351,14 +1230,14 @@ var LoopAPIInternal = {
MozLoopService.log.debug("Unable to send event through to target: " +
ex.message);
// Unregister event handlers when the message port is unreachable.
var _iteratorNormalCompletion7 = true;var _didIteratorError7 = false;var _iteratorError7 = undefined;try {for (var _iterator7 = events[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {var eventName = _step7.value;
api.off(eventName, handlerFunc);}} catch (err) {_didIteratorError7 = true;_iteratorError7 = err;} finally {try {if (!_iteratorNormalCompletion7 && _iterator7.return) {_iterator7.return();}} finally {if (_didIteratorError7) {throw _iteratorError7;}}}}};var _iteratorNormalCompletion8 = true;var _didIteratorError8 = false;var _iteratorError8 = undefined;try {
var _iteratorNormalCompletion6 = true;var _didIteratorError6 = false;var _iteratorError6 = undefined;try {for (var _iterator6 = events[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {var eventName = _step6.value;
api.off(eventName, handlerFunc);}} catch (err) {_didIteratorError6 = true;_iteratorError6 = err;} finally {try {if (!_iteratorNormalCompletion6 && _iterator6.return) {_iterator6.return();}} finally {if (_didIteratorError6) {throw _iteratorError6;}}}}};var _iteratorNormalCompletion7 = true;var _didIteratorError7 = false;var _iteratorError7 = undefined;try {
for (var _iterator8 = events[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) {var eventName = _step8.value;
api.on(eventName, handlerFunc);}} catch (err) {_didIteratorError8 = true;_iteratorError8 = err;} finally {try {if (!_iteratorNormalCompletion8 && _iterator8.return) {_iterator8.return();}} finally {if (_didIteratorError8) {throw _iteratorError8;}}}
for (var _iterator7 = events[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {var eventName = _step7.value;
api.on(eventName, handlerFunc);}} catch (err) {_didIteratorError7 = true;_iteratorError7 = err;} finally {try {if (!_iteratorNormalCompletion7 && _iterator7.return) {_iterator7.return();}} finally {if (_didIteratorError7) {throw _iteratorError7;}}}
reply();
return { v: void 0 };}();if ((typeof _ret === "undefined" ? "undefined" : _typeof(_ret)) === "object") return _ret.v;}
@ -1388,16 +1267,16 @@ var LoopAPIInternal = {
*/
broadcastPushMessage: function broadcastPushMessage(name, data) {
if (!gPageListeners) {
return;}var _iteratorNormalCompletion9 = true;var _didIteratorError9 = false;var _iteratorError9 = undefined;try {
return;}var _iteratorNormalCompletion8 = true;var _didIteratorError8 = false;var _iteratorError8 = undefined;try {
for (var _iterator9 = gPageListeners[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) {var page = _step9.value;
for (var _iterator8 = gPageListeners[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) {var page = _step8.value;
try {
page.sendAsyncMessage(kPushMessageName, [name, data]);}
catch (ex) {
// Only make noise when the Remote Page Manager needs more time to
// initialize.
if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED) {
throw ex;}}}} catch (err) {_didIteratorError9 = true;_iteratorError9 = err;} finally {try {if (!_iteratorNormalCompletion9 && _iterator9.return) {_iterator9.return();}} finally {if (_didIteratorError9) {throw _iteratorError9;}}}},
throw ex;}}}} catch (err) {_didIteratorError8 = true;_iteratorError8 = err;} finally {try {if (!_iteratorNormalCompletion8 && _iterator8.return) {_iterator8.return();}} finally {if (_didIteratorError8) {throw _iteratorError8;}}}},
@ -1408,19 +1287,15 @@ var LoopAPIInternal = {
*/
destroy: function destroy() {
if (!gPageListeners) {
return;}var _iteratorNormalCompletion10 = true;var _didIteratorError10 = false;var _iteratorError10 = undefined;try {
return;}var _iteratorNormalCompletion9 = true;var _didIteratorError9 = false;var _iteratorError9 = undefined;try {
for (var _iterator10 = gPageListeners[Symbol.iterator](), _step10; !(_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done); _iteratorNormalCompletion10 = true) {var listener = _step10.value;
listener.destroy();}} catch (err) {_didIteratorError10 = true;_iteratorError10 = err;} finally {try {if (!_iteratorNormalCompletion10 && _iterator10.return) {_iterator10.return();}} finally {if (_didIteratorError10) {throw _iteratorError10;}}}
for (var _iterator9 = gPageListeners[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) {var listener = _step9.value;
listener.destroy();}} catch (err) {_didIteratorError9 = true;_iteratorError9 = err;} finally {try {if (!_iteratorNormalCompletion9 && _iterator9.return) {_iterator9.return();}} finally {if (_didIteratorError9) {throw _iteratorError9;}}}
gPageListeners = null;
// Unsubscribe from global events.
Services.obs.removeObserver(this.handleStatusChanged, "loop-status-changed");
// Stop listening for changes in the social provider list, if necessary.
if (gSocialProviders) {
Services.obs.removeObserver(updateSocialProvidersCache, "social:providers-changed");}} };
Services.obs.removeObserver(this.handleStatusChanged, "loop-status-changed");} };

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

@ -11,20 +11,6 @@ var LOOP_SESSION_TYPE = {
FXA: 2 };
/**
* Values that we segment 2-way media connection length telemetry probes
* into.
*
* @type {{SHORTER_THAN_10S: Number, BETWEEN_10S_AND_30S: Number,
* BETWEEN_30S_AND_5M: Number, MORE_THAN_5M: Number}}
*/
var TWO_WAY_MEDIA_CONN_LENGTH = {
SHORTER_THAN_10S: 0,
BETWEEN_10S_AND_30S: 1,
BETWEEN_30S_AND_5M: 2,
MORE_THAN_5M: 3 };
/**
* Values that we segment sharing a room URL action telemetry probes into.
*
@ -51,26 +37,6 @@ var ROOM_CREATE = {
CREATE_FAIL: 1 };
/**
* Values that we segment room delete action telemetry probes into.
*
* @type {{DELETE_SUCCESS: Number, DELETE_FAIL: Number}}
*/
var ROOM_DELETE = {
DELETE_SUCCESS: 0,
DELETE_FAIL: 1 };
/**
* Values that we segment sharing screen pause/ resume action telemetry probes into.
*
* @type {{PAUSED: Number, RESUMED: Number}}
*/
var SHARING_SCREEN = {
PAUSED: 0,
RESUMED: 1 };
/**
* Values that we segment copy panel action telemetry probes into.
*
@ -126,16 +92,12 @@ 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", "COPY_PANEL",
"ROOM_CREATE", "ROOM_DELETE"];
"SHARING_ROOM_URL", "COPY_PANEL", "ROOM_CREATE"];
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);
XPCOMUtils.defineLazyModuleGetter(this, "LoopAPI",
@ -829,7 +791,9 @@ var MozLoopServiceInternal = {
var report = convertToRTCStatsReport(internalFormat);
var logStr = "";
logs.forEach(function (s) {logStr += s + "\n";});
logs.forEach(function (s) {
logStr += s + "\n";});
// We have stats and logs.

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

@ -151,43 +151,6 @@ html[dir="rtl"] .share-action-group > .invite-button:last-child {
color: #4a4a4a;
}
.share-service-dropdown {
color: #000;
text-align: start;
bottom: auto;
top: 0;
overflow: hidden;
overflow-y: auto;
}
/* When the dropdown is showing a vertical scrollbar, compensate for its width. */
body[platform="other"] .share-service-dropdown.overflow > .dropdown-menu-item,
body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
padding-inline-end: 20px;
}
.share-service-dropdown > .dropdown-menu-item > .icon {
width: 14px;
height: 14px;
margin-right: 4px;
}
.dropdown-menu-item > .icon-add-share-service {
background-image: url("../img/icons-16x16.svg#add");
background-repeat: no-repeat;
background-size: 12px 12px;
width: 12px;
height: 12px;
}
.dropdown-menu-item:hover > .icon-add-share-service {
background-image: url("../img/icons-16x16.svg#add-hover");
}
.dropdown-menu-item:hover:active > .icon-add-share-service {
background-image: url("../img/icons-16x16.svg#add-active");
}
.share-panel-container {
position: absolute;
top: 0;

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

@ -179,9 +179,6 @@ loop.conversation = function (mozL10n) {
sdk: OT });
// expose for functional tests
loop.conversation._sdkDriver = sdkDriver;
// Create the stores.
var activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, {
isDesktop: true,
@ -236,15 +233,7 @@ loop.conversation = function (mozL10n) {
return {
AppControllerView: AppControllerView,
init: init,
/**
* Exposed for the use of functional tests to be able to check
* metric-related execution as the call sequence progresses.
*
* @type loop.OTSdkDriver
*/
_sdkDriver: null };}(
init: init };}(
document.mozL10n);

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

@ -157,8 +157,7 @@ loop.shared.desktopViews = function (mozL10n) {
locationForMetrics: React.PropTypes.string.isRequired,
// This data is supplied by the activeRoomStore.
roomData: React.PropTypes.object.isRequired,
show: React.PropTypes.bool.isRequired,
socialShareProviders: React.PropTypes.array },
show: React.PropTypes.bool.isRequired },
render: function render() {var _this = this;
@ -204,84 +203,9 @@ loop.shared.desktopViews = function (mozL10n) {
locationForMetrics: _this.props.locationForMetrics,
roomData: _this.props.roomData });}
return null;}()),
return null;}())));} });
React.createElement(SocialShareDropdown, {
dispatcher: this.props.dispatcher,
ref: "menu",
roomUrl: this.props.roomData.roomUrl,
show: this.state.showMenu,
socialShareProviders: this.props.socialShareProviders })));} });
var SocialShareDropdown = React.createClass({ displayName: "SocialShareDropdown",
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
roomUrl: React.PropTypes.string,
show: React.PropTypes.bool.isRequired,
socialShareProviders: React.PropTypes.array },
handleAddServiceClick: function handleAddServiceClick(event) {
event.preventDefault();
this.props.dispatcher.dispatch(new sharedActions.AddSocialShareProvider());},
handleProviderClick: function handleProviderClick(event) {
event.preventDefault();
var origin = event.currentTarget.dataset.provider;
var provider = this.props.socialShareProviders.
filter(function (socialProvider) {
return socialProvider.origin === origin;})[
0];
this.props.dispatcher.dispatch(new sharedActions.ShareRoomUrl({
provider: provider,
roomUrl: this.props.roomUrl,
previews: [] }));},
render: function render() {
// Don't render a thing when no data has been fetched yet.
if (!this.props.socialShareProviders) {
return null;}
var cx = classNames;
var shareDropdown = cx({
"share-service-dropdown": true,
"dropdown-menu": true,
"visually-hidden": true,
"hide": !this.props.show });
return (
React.createElement("ul", { className: shareDropdown },
React.createElement("li", { className: "dropdown-menu-item", onClick: this.handleAddServiceClick },
React.createElement("i", { className: "icon icon-add-share-service" }),
React.createElement("span", null, mozL10n.get("share_add_service_button"))),
this.props.socialShareProviders.length ? React.createElement("li", { className: "dropdown-menu-separator" }) : null,
this.props.socialShareProviders.map(function (provider, idx) {
return (
React.createElement("li", { className: "dropdown-menu-item",
"data-provider": provider.origin,
key: "provider-" + idx,
onClick: this.handleProviderClick },
React.createElement("img", { className: "icon", src: provider.iconURL }),
React.createElement("span", null, provider.name)));}.
bind(this))));} });
@ -291,7 +215,6 @@ loop.shared.desktopViews = function (mozL10n) {
CopyLinkButton: CopyLinkButton,
EmailLinkButton: EmailLinkButton,
FacebookShareButton: FacebookShareButton,
SharePanelView: SharePanelView,
SocialShareDropdown: SocialShareDropdown };}(
SharePanelView: SharePanelView };}(
navigator.mozL10n || document.mozL10n);

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

@ -687,7 +687,9 @@ loop.panel = _.extend(loop.panel || {}, function (_, mozL10n) {
topPos = clickYPos - listTop + clickOffset;}
// Ensure menu is not cut off at top
if (topPos < 0) {topPos = 0;}
if (topPos < 0) {
topPos = 0;}
return topPos;}
@ -1128,8 +1130,7 @@ loop.panel = _.extend(loop.panel || {}, function (_, mozL10n) {
facebookEnabled: this.state.facebookEnabled,
locationForMetrics: "panel",
roomData: roomData,
show: true,
socialShareProviders: this.state.socialShareProviders })));} });
show: true })));} });

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

@ -77,7 +77,6 @@ loop.store = loop.store || {};
* @type {Array}
*/
actions: [
"addSocialShareProvider",
"createRoom",
"createdRoom",
"createRoomError",
@ -89,7 +88,6 @@ loop.store = loop.store || {};
"getAllRooms",
"getAllRoomsError",
"openRoom",
"shareRoomUrl",
"updateRoomContext",
"updateRoomContextDone",
"updateRoomContextError",
@ -348,10 +346,7 @@ loop.store = loop.store || {};
console.error("No URL sharing type bucket found for '" + from + "'");
return;}
loop.requestMulti(
["TelemetryAddValue", "LOOP_SHARING_ROOM_URL", bucket],
["TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_SHARE]);},
loop.request("TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_SHARE);},
/**
@ -373,7 +368,6 @@ loop.store = loop.store || {};
loop.requestMulti(
["NotifyUITour", "Loop:RoomURLEmailed"],
["TelemetryAddValue", "LOOP_SHARING_ROOM_URL", bucket],
["TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_SHARE]);},
@ -411,49 +405,7 @@ loop.store = loop.store || {};
console.error("No URL sharing type bucket found for '" + from + "'");
return;}
loop.requestMulti(
["TelemetryAddValue", "LOOP_SHARING_ROOM_URL", bucket],
["TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_SHARE]);},
/**
* Share a room url.
*
* @param {sharedActions.ShareRoomUrl} actionData The action data.
*/
shareRoomUrl: function shareRoomUrl(actionData) {
var providerOrigin = new URL(actionData.provider.origin).hostname;
var shareTitle = "";
var shareBody = null;
switch (providerOrigin) {
case "mail.google.com":
shareTitle = mozL10n.get("share_email_subject7");
shareBody = mozL10n.get("share_email_body7", {
callUrl: actionData.roomUrl });
shareBody += mozL10n.get("share_email_footer2");
break;
case "twitter.com":
default:
shareTitle = mozL10n.get("share_tweet", {
clientShortname2: mozL10n.get("clientShortname2") });
break;}
loop.requestMulti(
["SocialShareRoom", actionData.provider.origin, actionData.roomUrl,
shareTitle, shareBody],
["NotifyUITour", "Loop:RoomURLShared"]);},
/**
* Open the share panel to add a Social share provider.
*/
addSocialShareProvider: function addSocialShareProvider() {
loop.request("AddSocialShareProvider");},
loop.request("TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_SHARE);},
/**
@ -467,12 +419,7 @@ loop.store = loop.store || {};
if (isError) {
this.dispatchAction(new sharedActions.DeleteRoomError({ error: result }));}
var buckets = this._constants.ROOM_DELETE;
loop.requestMulti(
["TelemetryAddValue", "LOOP_ROOM_DELETE", buckets[isError ?
"DELETE_FAIL" : "DELETE_SUCCESS"]],
["TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_DELETE]);}.
loop.request("TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_DELETE);}.
bind(this));},

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

@ -372,8 +372,7 @@ loop.roomViews = function (mozL10n) {
facebookEnabled: this.props.facebookEnabled,
locationForMetrics: "conversation",
roomData: roomData,
show: shouldRenderInvitationOverlay,
socialShareProviders: this.state.socialShareProviders }))));}}} });
show: shouldRenderInvitationOverlay }))));}}} });

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

@ -267,96 +267,4 @@ describe("loop.shared.desktopViews", function () {
roomData: {} });
expect(ReactDOM.findDOMNode(view)).eql(null);});});
describe("SocialShareDropdown", function () {
var fakeProvider, view;
beforeEach(function () {
fakeProvider = {
name: "foo",
origin: "https://foo",
iconURL: "http://example.com/foo.png" };});
afterEach(function () {
fakeProvider = null;});
function mountTestComponent(props) {
props = _.extend({
dispatcher: dispatcher,
show: true },
props);
return TestUtils.renderIntoDocument(
React.createElement(sharedDesktopViews.SocialShareDropdown, props));}
describe("#render", function () {
it("should show no contents when the Social Providers have not been fetched yet", function () {
view = mountTestComponent();
expect(ReactDOM.findDOMNode(view)).to.eql(null);});
it("should show an empty list when no Social Providers are available", function () {
view = mountTestComponent({
socialShareProviders: [] });
var node = ReactDOM.findDOMNode(view);
expect(node.querySelector(".icon-add-share-service")).to.not.eql(null);
expect(node.querySelectorAll(".dropdown-menu-item").length).to.eql(1);});
it("should show a list of available Social Providers", function () {
view = mountTestComponent({
socialShareProviders: [fakeProvider] });
var node = ReactDOM.findDOMNode(view);
expect(node.querySelector(".icon-add-share-service")).to.not.eql(null);
expect(node.querySelector(".dropdown-menu-separator")).to.not.eql(null);
var dropdownNodes = node.querySelectorAll(".dropdown-menu-item");
expect(dropdownNodes.length).to.eql(2);
expect(dropdownNodes[1].querySelector("img").src).to.eql(fakeProvider.iconURL);
expect(dropdownNodes[1].querySelector("span").textContent).
to.eql(fakeProvider.name);});});
describe("#handleAddServiceClick", function () {
it("should dispatch an action when the 'add provider' item is clicked", function () {
view = mountTestComponent({
socialShareProviders: [] });
var addItem = ReactDOM.findDOMNode(view).querySelector(".dropdown-menu-item:first-child");
React.addons.TestUtils.Simulate.click(addItem);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.AddSocialShareProvider());});});
describe("#handleProviderClick", function () {
it("should dispatch an action when a provider item is clicked", function () {
view = mountTestComponent({
roomUrl: "http://example.com",
socialShareProviders: [fakeProvider] });
var providerItem = ReactDOM.findDOMNode(view).querySelector(".dropdown-menu-item:last-child");
React.addons.TestUtils.Simulate.click(providerItem);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ShareRoomUrl({
provider: fakeProvider,
roomUrl: "http://example.com",
previews: [] }));});});});});
expect(ReactDOM.findDOMNode(view)).eql(null);});});});

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

@ -41,10 +41,6 @@ describe("loop.store.RoomStore", function () {
CREATE_SUCCESS: 0,
CREATE_FAIL: 1 },
ROOM_DELETE: {
DELETE_SUCCESS: 0,
DELETE_FAIL: 1 },
LOOP_MAU_TYPE: {
OPEN_PANEL: 0,
OPEN_CONVERSATION: 1,
@ -104,7 +100,9 @@ describe("loop.store.RoomStore", function () {
ctime: 1405518241 }];
document.mozL10n.get = function (str) {return str;};});
document.mozL10n.get = function (str) {
return str;};});
afterEach(function () {
@ -505,34 +503,10 @@ describe("loop.store.RoomStore", function () {
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.DeleteRoomError({
error: err }));});
error: err }));});});
it("should log a telemetry event when the operation is successful", function () {
store.deleteRoom(new sharedActions.DeleteRoom({
roomToken: fakeRoomToken }));
sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0),
"LOOP_ROOM_DELETE", 0);});
it("should log a telemetry event when the operation fails", function () {
var err = new Error("fake");
err.isError = true;
requestStubs["Rooms:Delete"].returns(err);
store.deleteRoom(new sharedActions.DeleteRoom({
roomToken: fakeRoomToken }));
sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0),
"LOOP_ROOM_DELETE", 1);});});
describe("#copyRoomUrl", function () {
it("should copy the room URL", function () {
@ -542,29 +516,7 @@ describe("loop.store.RoomStore", function () {
sinon.assert.calledOnce(requestStubs.CopyString);
sinon.assert.calledWithExactly(requestStubs.CopyString, "http://invalid");});
it("should send a telemetry event for copy from panel", function () {
store.copyRoomUrl(new sharedActions.CopyRoomUrl({
roomUrl: "http://invalid",
from: "panel" }));
sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0),
"LOOP_SHARING_ROOM_URL", 0);});
it("should send a telemetry event for copy from conversation", function () {
store.copyRoomUrl(new sharedActions.CopyRoomUrl({
roomUrl: "http://invalid",
from: "conversation" }));
sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0),
"LOOP_SHARING_ROOM_URL", 1);});});
sinon.assert.calledWithExactly(requestStubs.CopyString, "http://invalid");});});
@ -647,76 +599,7 @@ describe("loop.store.RoomStore", function () {
sinon.assert.calledOnce(requestStubs.OpenURL);
sinon.assert.calledWithMatch(requestStubs.OpenURL, sharingSite);
sinon.assert.calledWithMatch(requestStubs.OpenURL, room);
sinon.assert.calledWithMatch(requestStubs.OpenURL, fallback);});
it("should send a telemetry event for facebook share from conversation", function () {
store.facebookShareRoomUrl(new sharedActions.FacebookShareRoomUrl({
from: "conversation",
roomUrl: "http://invalid" }));
sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0),
"LOOP_SHARING_ROOM_URL", 4);});});
describe("#shareRoomUrl", function () {
var socialShareRoomStub;
beforeEach(function () {
socialShareRoomStub = sinon.stub();
LoopMochaUtils.stubLoopRequest({
SocialShareRoom: socialShareRoomStub });});
it("should pass the correct data for GMail sharing", function () {
var roomUrl = "http://invalid";
var origin = "https://mail.google.com/v1";
store.shareRoomUrl(new sharedActions.ShareRoomUrl({
roomUrl: roomUrl,
provider: {
origin: origin } }));
sinon.assert.calledOnce(socialShareRoomStub);
sinon.assert.calledWithExactly(socialShareRoomStub,
origin,
roomUrl,
"share_email_subject7",
"share_email_body7" +
"share_email_footer2");});
it("should pass the correct data for all other Social Providers", function () {
var roomUrl = "http://invalid2";
var origin = "https://twitter.com/share";
store.shareRoomUrl(new sharedActions.ShareRoomUrl({
roomUrl: roomUrl,
provider: {
origin: origin } }));
sinon.assert.calledOnce(socialShareRoomStub);
sinon.assert.calledWithExactly(socialShareRoomStub, origin,
roomUrl, "share_tweet", null);});});
describe("#addSocialShareProvider", function () {
it("should invoke to the correct mozLoop function", function () {
var stub = sinon.stub();
LoopMochaUtils.stubLoopRequest({
AddSocialShareProvider: stub });
store.addSocialShareProvider(new sharedActions.AddSocialShareProvider());
sinon.assert.calledOnce(stub);});});
sinon.assert.calledWithMatch(requestStubs.OpenURL, fallback);});});
@ -1038,8 +921,8 @@ describe("loop.store.RoomStore", function () {
from: "conversation" }));
sinon.assert.calledTwice(requestStubs["TelemetryAddValue"]);
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"].getCall(1),
sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"],
"LOOP_ACTIVITY_COUNTER", store._constants.LOOP_MAU_TYPE.ROOM_SHARE);});
@ -1049,8 +932,8 @@ describe("loop.store.RoomStore", function () {
from: "conversation" }));
sinon.assert.calledTwice(requestStubs["TelemetryAddValue"]);
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"].getCall(1),
sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"],
"LOOP_ACTIVITY_COUNTER", store._constants.LOOP_MAU_TYPE.ROOM_SHARE);});
@ -1060,8 +943,8 @@ describe("loop.store.RoomStore", function () {
from: "conversation" }));
sinon.assert.calledTwice(requestStubs["TelemetryAddValue"]);
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"].getCall(1),
sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"],
"LOOP_ACTIVITY_COUNTER", store._constants.LOOP_MAU_TYPE.ROOM_SHARE);});
@ -1070,6 +953,6 @@ describe("loop.store.RoomStore", function () {
roomToken: "42abc" }));
sinon.assert.calledTwice(requestStubs["TelemetryAddValue"]);
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"].getCall(1),
sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"],
"LOOP_ACTIVITY_COUNTER", store._constants.LOOP_MAU_TYPE.ROOM_DELETE);});});});

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

@ -1,7 +1,7 @@
pref("loop.enabled", true);
pref("loop.remote.autostart", true);
#ifdef LOOP_DEV_XPI
pref("loop.server", "https://loop-dev.stage.mozaws.net/v0");
pref("loop.server", "https://loop.dev.mozaws.net/v0");
pref("loop.linkClicker.url", "https://loop-webapp-dev.stage.mozaws.net/");
#else
pref("loop.server", "https://loop.services.mozilla.com/v0");
@ -24,7 +24,6 @@ pref("loop.copy.ticket", -1);
pref("loop.debug.loglevel", "Error");
pref("loop.debug.dispatcher", false);
pref("loop.debug.sdk", false);
pref("loop.debug.twoWayMediaTelemetry", false);
pref("loop.feedback.dateLastSeenSec", 0);
pref("loop.feedback.periodSec", 15770000); // 6 months.
pref("loop.feedback.formURL", "https://www.surveygizmo.com/s3/2651383/Firefox-Hello-Product-Survey-II?version=%APP_VERSION%");

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

@ -63,10 +63,6 @@ p {
display: none !important;
}
.visually-hidden {
visibility: hidden;
}
.tc {
text-align: center;
}
@ -466,13 +462,6 @@ html[dir="rtl"] .dropdown-menu {
background-color: #dbf7ff;
}
.dropdown-menu-separator {
height: 1px;
margin: 2px -2px 1px -2px;
border-top: 1px solid #dedede;
background-color: #fff;
}
/* Custom checkbox */
.checkbox-wrapper {

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

@ -417,24 +417,6 @@ loop.shared.actions = function () {
// roomOrigin: String
}),
/**
* Share a room url via the Social API.
* XXX: should move to some roomActions module - refs bug 1079284
* @provider: one of the share-capable Social Providers included
* @roomUrl: the URL that is shared
*/
ShareRoomUrl: Action.define("shareRoomUrl", {
provider: Object,
roomUrl: String }),
/**
* Open the share panel to add a Social share provider.
* XXX: should move to some roomActions module - refs bug 1079284
*/
AddSocialShareProvider: Action.define("addSocialShareProvider", {}),
/**
* XXX: should move to some roomActions module - refs bug 1079284
*/
@ -458,9 +440,8 @@ loop.shared.actions = function () {
// roomInfoFailure: String - Optional.
// roomName: String - Optional.
// roomState: String - Optional.
roomUrl: String
// socialShareProviders: Array - Optional.
}),
roomUrl: String }),
/**
* Notifies if the user agent will handle the room or not.
@ -469,14 +450,6 @@ loop.shared.actions = function () {
handlesRoom: Boolean }),
/**
* Updates the Social API information when it is received.
* XXX: should move to some roomActions module - refs bug 1079284
*/
UpdateSocialShareInfo: Action.define("updateSocialShareInfo", {
socialShareProviders: Array }),
/**
* Starts the process for the user to join the room.
* XXX: should move to some roomActions module - refs bug 1079284

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

@ -55,8 +55,7 @@ loop.store.ActiveRoomStore = function (mozL10n) {
roomDescription: "roomDescription",
roomInfoFailure: "roomInfoFailure",
roomName: "roomName",
roomState: "roomState",
socialShareProviders: "socialShareProviders" };
roomState: "roomState" };
var updateContextTimer = null;
@ -158,8 +157,6 @@ loop.store.ActiveRoomStore = function (mozL10n) {
roomName: null,
// True when sharing screen has been paused.
streamPaused: false,
// Social API state.
socialShareProviders: null,
// True if media has been connected both-ways.
mediaConnected: false,
// True if a chat message was sent or received during a session.
@ -273,7 +270,6 @@ loop.store.ActiveRoomStore = function (mozL10n) {
"startBrowserShare",
"endScreenShare",
"toggleBrowserSharing",
"updateSocialShareInfo",
"connectionStatus",
"mediaConnected",
"videoScreenStreamChanged"];
@ -288,13 +284,11 @@ loop.store.ActiveRoomStore = function (mozL10n) {
this._onUpdateListener = this._handleRoomUpdate.bind(this);
this._onDeleteListener = this._handleRoomDelete.bind(this);
this._onSocialShareUpdate = this._handleSocialShareUpdate.bind(this);
var roomToken = this._storeState.roomToken;
loop.request("Rooms:PushSubscription", ["delete:" + roomToken, "update:" + roomToken]);
loop.subscribe("Rooms:Delete:" + roomToken, this._handleRoomDelete.bind(this));
loop.subscribe("Rooms:Update:" + roomToken, this._handleRoomUpdate.bind(this));
loop.subscribe("SocialProvidersChanged", this._onSocialShareUpdate);},
loop.subscribe("Rooms:Update:" + roomToken, this._handleRoomUpdate.bind(this));},
/**
@ -320,16 +314,12 @@ loop.store.ActiveRoomStore = function (mozL10n) {
this._registerPostSetupActions();
// Get the window data from the Loop API.
return loop.requestMulti(
["Rooms:Get", actionData.roomToken],
["GetSocialShareProviders"]).
then(function (results) {
var room = results[0];
var socialShareProviders = results[1];
return loop.request("Rooms:Get", actionData.roomToken).then(function (result) {
var room = result;
if (room.isError) {
if (result.isError) {
this.dispatchAction(new sharedActions.RoomFailure({
error: room,
error: result,
failedJoinRequest: false }));
return;}
@ -341,8 +331,7 @@ loop.store.ActiveRoomStore = function (mozL10n) {
roomDescription: room.decryptedContext.description,
roomName: room.decryptedContext.roomName,
roomState: ROOM_STATES.READY,
roomUrl: room.roomUrl,
socialShareProviders: socialShareProviders }));
roomUrl: room.roomUrl }));
// For the conversation window, we need to automatically join the room.
@ -548,18 +537,6 @@ loop.store.ActiveRoomStore = function (mozL10n) {
/**
* Handles the updateSocialShareInfo action. Updates the room data with new
* Social API info.
*
* @param {sharedActions.UpdateSocialShareInfo} actionData
*/
updateSocialShareInfo: function updateSocialShareInfo(actionData) {
this.setStoreState({
socialShareProviders: actionData.socialShareProviders });},
/**
* Handles room updates notified by the Loop rooms API.
*
@ -585,18 +562,6 @@ loop.store.ActiveRoomStore = function (mozL10n) {
/**
* Handles an update of the position of the Share widget and changes to list
* of Social API providers, notified by the Loop API.
*/
_handleSocialShareUpdate: function _handleSocialShareUpdate() {
loop.request("GetSocialShareProviders").then(function (result) {
this.dispatchAction(new sharedActions.UpdateSocialShareInfo({
socialShareProviders: result }));}.
bind(this));},
/**
* Checks that there are audio and video devices available, and joins the
* room if there are. If there aren't then it will dispatch a ConnectionFailure
@ -751,9 +716,6 @@ loop.store.ActiveRoomStore = function (mozL10n) {
this._setRefreshTimeout(actionData.expires);
// Only send media telemetry on one side of the call: the desktop side.
actionData.sendTwoWayMediaTelemetry = this._isDesktop;
this._sdkDriver.connectSession(actionData);
loop.request("AddConversationContext", this._storeState.windowId,
@ -1161,10 +1123,6 @@ loop.store.ActiveRoomStore = function (mozL10n) {
return;}
if (loop.standaloneMedia) {
loop.standaloneMedia.multiplexGum.reset();}
if (this._browserSharingListener) {
// Remove the browser sharing listener as we don't need it now.
loop.unsubscribe("BrowserSwitch", this._browserSharingListener);
@ -1263,10 +1221,8 @@ loop.store.ActiveRoomStore = function (mozL10n) {
// There's no need to listen to these actions anymore.
this.dispatcher.unregister(this, [
"receivedTextChatMessage",
"sendTextChatMessage"]);
"sendTextChatMessage"]);},
// Ping telemetry of this session with successful message(s) exchange.
loop.request("TelemetryAddValue", "LOOP_ROOM_SESSION_WITHCHAT", 1);},
/**

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

@ -78,7 +78,9 @@ var loop = loop || {};
// These functions should only be used in unit tests.
loop.request.inspect = function () {return _.extend({}, gListenersMap);};
loop.request.inspect = function () {
return _.extend({}, gListenersMap);};
loop.request.reset = function () {
gListeningForMessages = false;
gListenersMap = {};};
@ -187,7 +189,9 @@ var loop = loop || {};
// These functions should only be used in unit tests.
loop.subscribe.inspect = function () {return _.extend({}, gSubscriptionsMap);};
loop.subscribe.inspect = function () {
return _.extend({}, gSubscriptionsMap);};
loop.subscribe.reset = function () {
gListeningForPushMessages = false;
gSubscriptionsMap = {};};

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

@ -45,15 +45,6 @@ loop.OTSdkDriver = function () {
"toggleBrowserSharing"]);
// Set loop.debug.twoWayMediaTelemetry to true in the browser
// by changing the hidden pref loop.debug.twoWayMediaTelemetry using
// about:config, or use
//
// localStorage.setItem("debug.twoWayMediaTelemetry", true);
loop.shared.utils.getBoolPreference("debug.twoWayMediaTelemetry", function (enabled) {
this._debugTwoWayMediaTelemetry = enabled;}.
bind(this));
// Set loop.debug.sdk to true in the browser, or in standalone:
// localStorage.setItem("debug.sdk", true);
loop.shared.utils.getBoolPreference("debug.sdk", function (enabled) {
@ -260,20 +251,12 @@ loop.OTSdkDriver = function () {
* - sessionId: The OT session ID
* - apiKey: The OT API key
* - sessionToken: The token for the OT session
* - sendTwoWayMediaTelemetry: boolean should we send telemetry on length
* of media sessions. Callers should ensure
* that this is only set for one side of the
* session so that things don't get
* double-counted.
*
* @param {Object} sessionData The session data for setting up the OT session.
*/
connectSession: function connectSession(sessionData) {
this.session = this.sdk.initSession(sessionData.sessionId);
this._sendTwoWayMediaTelemetry = !!sessionData.sendTwoWayMediaTelemetry;
this._setTwoWayMediaStartTime(this.CONNECTION_START_TIME_UNINITIALIZED);
this.session.on("sessionDisconnected",
this._onSessionDisconnected.bind(this));
this.session.on("connectionCreated", this._onConnectionCreated.bind(this));
@ -324,8 +307,6 @@ loop.OTSdkDriver = function () {
// Now reset the metrics as well.
this._resetMetrics();
this._noteConnectionLengthIfNeeded(this._getTwoWayMediaStartTime(), performance.now());
// Also, tidy these variables ready for next time.
delete this._sessionConnected;
delete this._publisherReady;
@ -334,8 +315,7 @@ loop.OTSdkDriver = function () {
delete this._mockPublisherEl;
delete this._publisherChannel;
delete this._subscriberChannel;
this.connections = {};
this._setTwoWayMediaStartTime(this.CONNECTION_START_TIME_UNINITIALIZED);},
this.connections = {};},
/**
@ -407,8 +387,6 @@ loop.OTSdkDriver = function () {
this._notifyMetricsEvent("Session.connectionDestroyed", "peer");
this._noteConnectionLengthIfNeeded(this._getTwoWayMediaStartTime(), performance.now());
this.dispatcher.dispatch(new sharedActions.RemotePeerDisconnected({
peerHungup: event.reason === "clientDisconnected" }));},
@ -435,8 +413,6 @@ loop.OTSdkDriver = function () {
return;}
this._noteConnectionLengthIfNeeded(this._getTwoWayMediaStartTime(),
performance.now());
this._notifyMetricsEvent("Session." + event.reason);
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
reason: reason }));},
@ -526,6 +502,8 @@ loop.OTSdkDriver = function () {
break;
case "Session.networkDisconnected":
case "Session.forceDisconnected":
case "Session.subscribeCompleted":
case "Session.screen.subscribeCompleted":
break;
default:
// We don't want unexpected events being sent to the server, so
@ -646,9 +624,9 @@ loop.OTSdkDriver = function () {
srcMediaElement: sdkSubscriberVideo }));
this._notifyMetricsEvent("Session.subscribeCompleted");
this._subscribedRemoteStream = true;
if (this._checkAllStreamsConnected()) {
this._setTwoWayMediaStartTime(performance.now());
this.dispatcher.dispatch(new sharedActions.MediaConnected());}
@ -677,9 +655,10 @@ loop.OTSdkDriver = function () {
// _handleRemoteScreenShareCreated. Maybe these should be separate
// actions. But even so, this shouldn't be necessary....
this.dispatcher.dispatch(new sharedActions.ReceivingScreenShare({
receiving: true, srcMediaElement: sdkSubscriberVideo }));},
receiving: true, srcMediaElement: sdkSubscriberVideo }));
this._notifyMetricsEvent("Session.screen.subscribeCompleted");},
/**
@ -873,57 +852,6 @@ loop.OTSdkDriver = function () {
/**
* Implementation detail, may be set to one of the CONNECTION_START_TIME
* constants, or a positive integer in milliseconds.
*
* @private
*/
__twoWayMediaStartTime: undefined,
/**
* Used as a guard to make sure we don't inadvertently use an
* uninitialized value.
*/
CONNECTION_START_TIME_UNINITIALIZED: -1,
/**
* Use as a guard to ensure that we don't note any bidirectional sessions
* twice.
*/
CONNECTION_START_TIME_ALREADY_NOTED: -2,
/**
* Set and get the start time of the two-way media connection. These
* are done as wrapper functions so that we can log sets to make manual
* verification of various telemetry scenarios possible. The get API is
* analogous in order to follow the principle of least surprise for
* people consuming this code.
*
* If this._sendTwoWayMediaTelemetry is not true, returns immediately
* without making any changes, since this data is not used, and it makes
* reading the logs confusing for manual verification of both ends of the
* call in the same browser, which is a case we care about.
*
* @param start start time in milliseconds, as returned by
* performance.now()
* @private
*/
_setTwoWayMediaStartTime: function _setTwoWayMediaStartTime(start) {
if (!this._sendTwoWayMediaTelemetry) {
return;}
this.__twoWayMediaStartTime = start;
if (this._debugTwoWayMediaTelemetry) {
console.log("Loop Telemetry: noted two-way connection start, " +
"start time in ms:", start);}},
_getTwoWayMediaStartTime: function _getTwoWayMediaStartTime() {
return this.__twoWayMediaStartTime;},
/**
* Handles the event when the remote stream is destroyed.
*
@ -1050,13 +978,18 @@ loop.OTSdkDriver = function () {
* @param {OT.Event} event
*/
_onOTException: function _onOTException(event) {
var baseException = "sdk.exception.";
if (event.target && event.target === this.screenshare) {
baseException += "screen.";}
switch (event.code) {
case OT.ExceptionCodes.PUBLISHER_ICE_WORKFLOW_FAILED:
case OT.ExceptionCodes.SUBSCRIBER_ICE_WORKFLOW_FAILED:
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
reason: FAILURE_DETAILS.ICE_FAILED }));
this._notifyMetricsEvent("sdk.exception." + event.code);
this._notifyMetricsEvent(baseException + event.code);
break;
case OT.ExceptionCodes.TERMS_OF_SERVICE_FAILURE:
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
@ -1064,21 +997,17 @@ loop.OTSdkDriver = function () {
// We still need to log the exception so that the server knows why this
// attempt failed.
this._notifyMetricsEvent("sdk.exception." + event.code);
this._notifyMetricsEvent(baseException + 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);
this._notifyMetricsEvent(baseException + event.code);
break;}},
@ -1150,7 +1079,6 @@ loop.OTSdkDriver = function () {
// Now record the fact, and check if we've got all media yet.
this._publishedLocalStream = true;
if (this._checkAllStreamsConnected()) {
this._setTwoWayMediaStartTime(performance.now());
this.dispatcher.dispatch(new sharedActions.MediaConnected());}}},
@ -1215,92 +1143,8 @@ loop.OTSdkDriver = function () {
* Called when a screenshare stream is published.
*/
_onScreenShareStreamCreated: function _onScreenShareStreamCreated() {
this._notifyMetricsEvent("Publisher.streamCreated");},
this._notifyMetricsEvent("Publisher.streamCreated");} };
/*
* XXX all of the bi-directional media connection telemetry stuff in this
* file, (much, but not all, of it is below) should be hoisted into its
* own object for maintainability and clarity, also in part because this
* stuff only wants to run one side of the connection, not both (tracked
* by bug 1145237).
*/
/**
* A hook exposed only for the use of the functional tests so that
* they can check that the bi-directional media count is being updated
* correctly.
*
* @type number
* @private
*/
_connectionLengthNotedCalls: 0,
/**
* Wrapper for adding a keyed value that also updates
* connectionLengthNoted calls and sets the twoWayMediaStartTime to
* this.CONNECTION_START_TIME_ALREADY_NOTED.
*
* @param {number} callLengthSeconds the call length in seconds
* @private
*/
_noteConnectionLength: function _noteConnectionLength(callLengthSeconds) {
var buckets = this._constants.TWO_WAY_MEDIA_CONN_LENGTH;
var bucket = buckets.SHORTER_THAN_10S;
if (callLengthSeconds >= 10 && callLengthSeconds <= 30) {
bucket = buckets.BETWEEN_10S_AND_30S;} else
if (callLengthSeconds > 30 && callLengthSeconds <= 300) {
bucket = buckets.BETWEEN_30S_AND_5M;} else
if (callLengthSeconds > 300) {
bucket = buckets.MORE_THAN_5M;}
loop.request("TelemetryAddValue", "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1", bucket);
this._setTwoWayMediaStartTime(this.CONNECTION_START_TIME_ALREADY_NOTED);
this._connectionLengthNotedCalls++;
if (this._debugTwoWayMediaTelemetry) {
console.log("Loop Telemetry: noted two-way media connection " +
"in bucket: ", bucket);}},
/**
* Note connection length if it's valid (the startTime has been initialized
* and is not later than endTime) and not yet already noted. If
* this._sendTwoWayMediaTelemetry is not true, we return immediately.
*
* @param {number} startTime in milliseconds
* @param {number} endTime in milliseconds
* @private
*/
_noteConnectionLengthIfNeeded: function _noteConnectionLengthIfNeeded(startTime, endTime) {
if (!this._sendTwoWayMediaTelemetry) {
return;}
if (startTime === this.CONNECTION_START_TIME_ALREADY_NOTED ||
startTime === this.CONNECTION_START_TIME_UNINITIALIZED ||
startTime > endTime) {
if (this._debugTwoWayMediaTelemetry) {
console.log("_noteConnectionLengthIfNeeded called with " +
" invalid params, either the calls were never" +
" connected or there is a bug; startTime:", startTime,
"endTime:", endTime);}
return;}
var callLengthSeconds = (endTime - startTime) / 1000;
this._noteConnectionLength(callLengthSeconds);},
/**
* If set to true, make it easy to test/verify 2-way media connection
* telemetry code operation by viewing the logs.
*/
_debugTwoWayMediaTelemetry: false };
return OTSdkDriver;}();

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

@ -217,7 +217,10 @@ loop.shared.views.chat = function (mozL10n) {
this.props.messageList.map(function (entry, i) {
if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
if (!this.props.showInitialContext) {return null;}
if (!this.props.showInitialContext) {
return null;}
switch (entry.contentType) {
case CHAT_CONTENT_TYPES.CONTEXT:
return (

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

@ -13,8 +13,7 @@ describe("loop.store.ActiveRoomStore", function () {
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
var ROOM_INFO_FAILURES = loop.shared.utils.ROOM_INFO_FAILURES;
var sandbox, dispatcher, store, requestStubs, fakeSdkDriver, fakeMultiplexGum;
var standaloneMediaRestore;
var sandbox, dispatcher, store, requestStubs, fakeSdkDriver;
var clock;
beforeEach(function () {
@ -38,7 +37,6 @@ describe("loop.store.ActiveRoomStore", function () {
"Rooms:PushSubscription": sinon.stub(),
SetScreenShareState: sinon.stub(),
GetActiveTabWindowId: sandbox.stub().returns(42),
GetSocialShareProviders: sinon.stub().returns([]),
TelemetryAddValue: sinon.stub() });
@ -56,15 +54,6 @@ describe("loop.store.ActiveRoomStore", function () {
endScreenShare: sinon.stub().returns(true) };
fakeMultiplexGum = {
reset: sandbox.spy() };
standaloneMediaRestore = loop.standaloneMedia;
loop.standaloneMedia = {
multiplexGum: fakeMultiplexGum };
store = new loop.store.ActiveRoomStore(dispatcher, {
sdkDriver: fakeSdkDriver });
@ -76,8 +65,7 @@ describe("loop.store.ActiveRoomStore", function () {
afterEach(function () {
sandbox.restore();
LoopMochaUtils.restore();
loop.standaloneMedia = standaloneMediaRestore;});
LoopMochaUtils.restore();});
describe("#constructor", function () {
@ -182,15 +170,6 @@ describe("loop.store.ActiveRoomStore", function () {
expect(store._storeState.failureReason).eql(FAILURE_DETAILS.EXPIRED_OR_INVALID);});
it("should reset the multiplexGum", function () {
store.roomFailure(new sharedActions.RoomFailure({
error: fakeError,
failedJoinRequest: false }));
sinon.assert.calledOnce(fakeMultiplexGum.reset);});
it("should disconnect from the servers via the sdk", function () {
store.roomFailure(new sharedActions.RoomFailure({
error: fakeError,
@ -370,8 +349,7 @@ describe("loop.store.ActiveRoomStore", function () {
participants: [],
roomName: fakeRoomData.decryptedContext.roomName,
roomState: ROOM_STATES.READY,
roomUrl: fakeRoomData.roomUrl,
socialShareProviders: [] }));});});
roomUrl: fakeRoomData.roomUrl }));});});
@ -806,28 +784,6 @@ describe("loop.store.ActiveRoomStore", function () {
describe("#updateSocialShareInfo", function () {
var fakeSocialShareInfo;
beforeEach(function () {
fakeSocialShareInfo = {
socialShareProviders: [{
name: "foo",
origin: "https://example.com",
iconURL: "icon.png" }] };});
it("should save the Social API information", function () {
store.updateSocialShareInfo(new sharedActions.UpdateSocialShareInfo(fakeSocialShareInfo));
var state = store.getStoreState();
expect(state.socialShareProviders).
eql(fakeSocialShareInfo.socialShareProviders);});});
describe("#joinRoom", function () {
var hasDevicesStub;
@ -1128,28 +1084,6 @@ describe("loop.store.ActiveRoomStore", function () {
actionData);});
it("should pass 'sendTwoWayMediaTelemetry' as true to connectSession if " +
"store._isDesktop is true", function () {
store._isDesktop = true;
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
sinon.assert.calledOnce(fakeSdkDriver.connectSession);
sinon.assert.calledWithMatch(fakeSdkDriver.connectSession,
sinon.match.has("sendTwoWayMediaTelemetry", true));});
it("should pass 'sendTwoWayTelemetry' as false to connectionSession if " +
"store._isDesktop is false", function () {
store._isDesktop = false;
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
sinon.assert.calledOnce(fakeSdkDriver.connectSession);
sinon.assert.calledWithMatch(fakeSdkDriver.connectSession,
sinon.match.has("sendTwoWayMediaTelemetry", false));});
it("should call LoopAPI.AddConversationContext", function () {
var actionData = new sharedActions.JoinedRoom(fakeJoinedData);
@ -1245,12 +1179,6 @@ describe("loop.store.ActiveRoomStore", function () {
expect(store.getStoreState().failureReason).eql("FAIL");});
it("should reset the multiplexGum", function () {
store.connectionFailure(connectionFailureAction);
sinon.assert.calledOnce(fakeMultiplexGum.reset);});
it("should disconnect from the servers via the sdk", function () {
store.connectionFailure(connectionFailureAction);
@ -1312,7 +1240,6 @@ describe("loop.store.ActiveRoomStore", function () {
store.connectionFailure(connectionFailureAction);
sinon.assert.notCalled(fakeMultiplexGum.reset);
sinon.assert.notCalled(fakeSdkDriver.disconnectSession);});});
@ -1958,12 +1885,6 @@ describe("loop.store.ActiveRoomStore", function () {
sinon.assert.calledWithExactly(requestStubs.SetScreenShareState, "1234", false);});
it("should reset the multiplexGum", function () {
store.windowUnload();
sinon.assert.calledOnce(fakeMultiplexGum.reset);});
it("should disconnect from the servers via the sdk", function () {
store.windowUnload();
@ -2027,12 +1948,6 @@ describe("loop.store.ActiveRoomStore", function () {
it("should reset the multiplexGum", function () {
store.leaveRoom();
sinon.assert.calledOnce(fakeMultiplexGum.reset);});
it("should disconnect from the servers via the sdk", function () {
store.leaveRoom();
@ -2129,24 +2044,6 @@ describe("loop.store.ActiveRoomStore", function () {
describe("#_handleSocialShareUpdate", function () {
it("should dispatch an UpdateRoomInfo action", function () {
store._handleSocialShareUpdate();
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateSocialShareInfo({
socialShareProviders: [] }));});
it("should call respective mozLoop methods", function () {
store._handleSocialShareUpdate();
sinon.assert.calledOnce(requestStubs.GetSocialShareProviders);});});
describe("#_handleTextChatMessage", function () {
beforeEach(function () {
var fakeRoomData = {
@ -2204,22 +2101,7 @@ describe("loop.store.ActiveRoomStore", function () {
sentTimestamp: "1970-01-01T00:00:00.000Z" }));
assertWeDidNothing();});
it("should ping telemetry when a chat message arrived or is to be sent", function () {
store._handleTextChatMessage(new sharedActions.ReceivedTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!",
receivedTimestamp: "1970-01-01T00:00:00.000Z" }));
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
"LOOP_ROOM_SESSION_WITHCHAT", 1);
expect(store.getStoreState().chatMessageExchanged).eql(true);
expect(dispatcher._eventData.hasOwnProperty("receivedTextChatMessage")).eql(false);
expect(dispatcher._eventData.hasOwnProperty("sendTextChatMessage")).eql(false);});});
assertWeDidNothing();});});

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

@ -13,7 +13,7 @@ describe("loop.OTSdkDriver", function () {
var CURSOR_MESSAGE_TYPES = loop.shared.utils.CURSOR_MESSAGE_TYPES;
var sandbox, constants;
var dispatcher, driver, requestStubs, publisher, screenshare, sdk, session;
var dispatcher, driver, publisher, screenshare, sdk, session;
var sessionData, subscriber, publisherConfig, fakeEvent;
beforeEach(function () {
@ -31,11 +31,6 @@ describe("loop.OTSdkDriver", function () {
sessionToken: "1357924680" };
LoopMochaUtils.stubLoopRequest(requestStubs = {
TelemetryAddValue: sinon.stub(),
GetLoopPref: sinon.stub() });
dispatcher = new loop.Dispatcher();
sandbox.stub(dispatcher, "dispatch");
@ -84,12 +79,6 @@ describe("loop.OTSdkDriver", function () {
constants = {
TWO_WAY_MEDIA_CONN_LENGTH: {
SHORTER_THAN_10S: 0,
BETWEEN_10S_AND_30S: 1,
BETWEEN_30S_AND_5M: 2,
MORE_THAN_5M: 3 },
SHARING_STATE_CHANGE: {
WINDOW_ENABLED: 0,
WINDOW_DISABLED: 1,
@ -144,23 +133,6 @@ describe("loop.OTSdkDriver", function () {
it("should enable debug for two way media telemetry if required", function () {
// Simulate the pref being enabled.
sandbox.stub(loop.shared.utils, "getBoolPreference", function (prefName, callback) {
if (prefName === "debug.twoWayMediaTelemetry") {
callback(true);}});
driver = new loop.OTSdkDriver({
constants: constants,
dispatcher: dispatcher,
sdk: sdk });
expect(driver._debugTwoWayMediaTelemetry).eql(true);});
it("should enable debug on the sdk if required", function () {
// Simulate the pref being enabled.
sandbox.stub(loop.shared.utils, "getBoolPreference", function (prefName, callback) {
@ -446,15 +418,6 @@ describe("loop.OTSdkDriver", function () {
sinon.assert.calledWith(session.connect, "1234567890", "1357924680");});
it("should set the two-way media start time to 'uninitialized' " +
"when sessionData.sendTwoWayMediaTelemetry is true'", function () {
driver.connectSession(_.extend(sessionData,
{ sendTwoWayMediaTelemetry: true }));
expect(driver._getTwoWayMediaStartTime()).to.eql(
driver.CONNECTION_START_TIME_UNINITIALIZED);});
describe("On connection complete", function () {
beforeEach(function () {
sandbox.stub(window.console, "error");});
@ -637,109 +600,7 @@ describe("loop.OTSdkDriver", function () {
driver.disconnectSession();
sinon.assert.calledOnce(publisher.destroy);});
it("should call _noteConnectionLengthIfNeeded with connection duration", function () {
driver.session = session;
var startTime = 1;
var endTime = 3;
driver._sendTwoWayMediaTelemetry = true;
driver._setTwoWayMediaStartTime(startTime);
sandbox.stub(performance, "now").returns(endTime);
sandbox.stub(driver, "_noteConnectionLengthIfNeeded");
driver.disconnectSession();
sinon.assert.calledWith(driver._noteConnectionLengthIfNeeded, startTime,
endTime);});
it("should reset the two-way media connection start time", function () {
driver.session = session;
var startTime = 1;
driver._sendTwoWayMediaTelemetry = true;
driver._setTwoWayMediaStartTime(startTime);
sandbox.stub(performance, "now");
sandbox.stub(driver, "_noteConnectionLengthIfNeeded");
driver.disconnectSession();
expect(driver._getTwoWayMediaStartTime()).to.eql(
driver.CONNECTION_START_TIME_UNINITIALIZED);});});
describe("#_noteConnectionLengthIfNeeded", function () {
var startTimeMS;
beforeEach(function () {
startTimeMS = 1;
driver._sendTwoWayMediaTelemetry = true;
driver._setTwoWayMediaStartTime(startTimeMS);});
it("should set two-way media start time to CONNECTION_START_TIME_ALREADY_NOTED", function () {
var endTimeMS = 3;
driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
expect(driver._getTwoWayMediaStartTime()).to.eql(
driver.CONNECTION_START_TIME_ALREADY_NOTED);});
it("should record telemetry with SHORTER_THAN_10S for calls less than 10s", function () {
var endTimeMS = 9000;
driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
sinon.assert.calledWith(requestStubs.TelemetryAddValue,
"LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1",
constants.TWO_WAY_MEDIA_CONN_LENGTH.SHORTER_THAN_10S);});
it("should call record telemetry with BETWEEN_10S_AND_30S for 15s calls",
function () {
var endTimeMS = 15000;
driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
sinon.assert.calledWith(requestStubs.TelemetryAddValue,
"LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1",
constants.TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_10S_AND_30S);});
it("should call record telemetry with BETWEEN_30S_AND_5M for 60s calls",
function () {
var endTimeMS = 60 * 1000;
driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
sinon.assert.calledWith(requestStubs.TelemetryAddValue,
"LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1",
constants.TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_30S_AND_5M);});
it("should call record telemetry with MORE_THAN_5M for 10m calls", function () {
var endTimeMS = 10 * 60 * 1000;
driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
sinon.assert.calledWith(requestStubs.TelemetryAddValue,
"LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1",
constants.TWO_WAY_MEDIA_CONN_LENGTH.MORE_THAN_5M);});
it("should not call record telemetry if driver._sendTwoWayMediaTelemetry is false",
function () {
var endTimeMS = 10 * 60 * 1000;
driver._sendTwoWayMediaTelemetry = false;
driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
sinon.assert.notCalled(requestStubs.TelemetryAddValue);});});
sinon.assert.calledOnce(publisher.destroy);});});
@ -919,27 +780,10 @@ describe("loop.OTSdkDriver", function () {
state: "waiting",
connections: 0,
sendStreams: 0,
recvStreams: 0 }));});
recvStreams: 0 }));});});
it("should call _noteConnectionLengthIfNeeded with connection duration", function () {
driver.session = session;
var startTime = 1;
var endTime = 3;
driver._sendTwoWayMediaTelemetry = true;
driver._setTwoWayMediaStartTime(startTime);
sandbox.stub(performance, "now").returns(endTime);
sandbox.stub(driver, "_noteConnectionLengthIfNeeded");
session.trigger("connectionDestroyed", {
reason: "clientDisconnected" });
sinon.assert.calledWith(driver._noteConnectionLengthIfNeeded, startTime,
endTime);});});
describe("sessionDisconnected", function () {
it("should notify metrics", function () {
@ -981,25 +825,7 @@ describe("loop.OTSdkDriver", function () {
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "connectionFailure"));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("reason", FAILURE_DETAILS.EXPIRED_OR_INVALID));});
it("should call _noteConnectionLengthIfNeeded with connection duration", function () {
driver.session = session;
var startTime = 1;
var endTime = 3;
driver._sendTwoWayMediaTelemetry = true;
driver._setTwoWayMediaStartTime(startTime);
sandbox.stub(performance, "now").returns(endTime);
sandbox.stub(driver, "_noteConnectionLengthIfNeeded");
session.trigger("sessionDisconnected", {
reason: "networkDisconnected" });
sinon.assert.calledWith(driver._noteConnectionLengthIfNeeded, startTime,
endTime);});});
sinon.match.hasOwn("reason", FAILURE_DETAILS.EXPIRED_OR_INVALID));});});
@ -1166,29 +992,23 @@ describe("loop.OTSdkDriver", function () {
new sharedActions.MediaConnected({}));});
it("should store the start time when both streams are up and" +
" driver._sendTwoWayMediaTelemetry is true", function () {
driver._sendTwoWayMediaTelemetry = true;
it("should dispatch a connectionStatus action if both streams are up", function () {
driver._publishedLocalStream = true;
var startTime = 1;
sandbox.stub(performance, "now").returns(startTime);
session.trigger("streamCreated", { stream: fakeStream });
expect(driver._getTwoWayMediaStartTime()).to.eql(startTime);});
// Called twice due to the VideoDimensionsChanged above.
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithMatch(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "Session.subscribeCompleted",
state: "receiving",
// Local stream connection is faked, so connections/sendStreams=0.
connections: 0,
sendStreams: 0,
recvStreams: 1 }));});
it("should not store the start time when both streams are up and" +
" driver._isDesktop is false", function () {
driver._isDesktop = false;
driver._publishedLocalStream = true;
var startTime = 73;
sandbox.stub(performance, "now").returns(startTime);
session.trigger("streamCreated", { stream: fakeStream });
expect(driver._getTwoWayMediaStartTime()).to.not.eql(startTime);});
describe("Data channel setup", function () {
var fakeChannel;
@ -1365,7 +1185,45 @@ describe("loop.OTSdkDriver", function () {
// Called twice due to the VideoDimensionsChanged above.
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ReceivingScreenShare({ receiving: true }));});});});
new sharedActions.ReceivingScreenShare({ receiving: true }));});
describe("screen share subscribe completed", function () {
beforeEach(function () {
fakeStream.videoType = "screen";
session.subscribe.yieldsOn(driver, null, fakeSubscriberObject,
videoElement).returns(this.fakeSubscriberObject);});
it("should dispatch ReceivingScreenShare on completion", function () {
fakeStream.connection = fakeConnection;
fakeStream.hasVideo = false;
session.trigger("streamCreated", { stream: fakeStream });
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ReceivingScreenShare({
receiving: true,
srcMediaElement: videoElement }));});
it("should dispatch a connectionStatus action", function () {
session.trigger("streamCreated", { stream: fakeStream });
// Called twice due to the VideoDimensionsChanged above.
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithMatch(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "Session.screen.subscribeCompleted",
state: "receiving",
connections: 0,
sendStreams: 0,
recvStreams: 1 }));});});});});

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

@ -261,6 +261,15 @@ body {
#mocha-stats .progress {
float: right;
padding-top: 0;
/**
* Set safe initial values, so mochas .progress does not inherit these
* properties from Bootstrap .progress (which causes .progress height to
* equal line height set in Bootstrap).
*/
height: auto;
box-shadow: none;
background-color: initial;
}
#mocha-stats em {

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

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

@ -1,5 +1,5 @@
/**
* ReactDOMServer v15.0.2
* ReactDOMServer v15.1.0
*
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.

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

@ -1,5 +1,5 @@
/**
* ReactDOM v15.0.2
* ReactDOM v15.1.0
*
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.

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

@ -1,5 +1,5 @@
/**
* ReactDOM v15.0.2
* ReactDOM v15.1.0
*
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

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

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

@ -262,6 +262,7 @@ rooms_room_joined_owner_not_connected_label=El to collaciu ta esperando pa resto
peer_left_session=El to collaciu coló.
peer_unexpected_quit=El to collaciu desconeutóse de mou inesperáu.
peer_join_session=Xunióse'l to collaciu.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -267,6 +267,7 @@ self_view_hidden_message=Özünü göstərmə gizlədilib amma hələ də gönd
peer_left_session=Yoldaşınız çıxdı.
peer_unexpected_quit=Yoldaşınız gözlənilmədən ayrıldı.
peer_join_session=Yoldaşınız qoşuldu.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -7,6 +7,8 @@
## 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.
loopMenuItem_label=Inicia una conversa…
loopMenuItem_accesskey=I
## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
## These are displayed together at the top of the panel when a user is needed to
@ -14,9 +16,13 @@
## and this is displayed in slightly larger font. Please arrange as necessary for
## your locale.
## {{clientShortname2}} will be replaced by the brand name for either string.
sign_in_again_title_line_one=Torneu a iniciar la sessió
sign_in_again_button=Inicia la sessió
## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
## will be replaced by the super short brandname.
panel_browse_with_friend_button=Navega per aquesta pàgina amb un amic
panel_disconnect_button=Desconnecta
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
@ -39,6 +45,10 @@
## 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.
invite_copy_link_button=Copia l'enllaç
invite_copied_link_button=S'ha copiat
invite_email_link_button=Envia l'enllaç per correu
invite_facebook_button3=Facebook
# 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):

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

@ -114,8 +114,8 @@ settings_menu_item_account=Účet
settings_menu_item_settings=Nastavení
settings_menu_item_signout=Odhlásit
settings_menu_item_signin=Přihlásit
settings_menu_item_turnnotificationson=Zapnout upozorně
settings_menu_item_turnnotificationsoff=Vypnout upozorně
settings_menu_item_turnnotificationson=Zapnout oznáme
settings_menu_item_turnnotificationsoff=Vypnout oznáme
settings_menu_item_feedback=Odeslat zpětnou vazbu
settings_menu_button_tooltip=Nastavení
@ -268,6 +268,7 @@ self_view_hidden_message=Váš obraz byl skryt, ale je nadále odesílán; pro j
peer_left_session=Váš přítel odešel.
peer_unexpected_quit=Váš přítel se nečekaně odpojil.
peer_join_session=Váš přítel se připojil.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -29,12 +29,12 @@ panel_disconnect_button=Datgysylltu
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
first_time_experience_subheading2=Cliciwch y botwm Helo i bori drwy'r tudalennau gwe gyda ffrind.
first_time_experience_subheading_button_above=Cliciwch ar y botwn uchod er mwyn pori tudalennau gwe gyda ffrind.
first_time_experience_subheading_button_above=Cliciwch ar y botwm uchod er mwyn pori tudalennau gwe gyda ffrind.
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
## ways to use Hello project.
first_time_experience_content=Ei ddefnyddio i gynllunio gyda'n gilydd, gweithio gyda'n gilydd, chwerthin gyda'n gilydd.
first_time_experience_content2=Defnyddiwch Hello i wneud pethau: cynllunio, chwerthin, gweithio gyda'ch gilydd.
first_time_experience_content2=Defnyddiwch Hello i wneud pethau: cynllunio, chwerthin, gweithio.
first_time_experience_button_label2=Gweld sut mae'n gweithio
## First Time Experience Slides
@ -141,7 +141,7 @@ initiate_audio_video_call_button2=Cychwyn
initiate_audio_video_call_tooltip2=Cychwyn sgwrs fideo
initiate_audio_call_button2=Sgwrs llais
peer_ended_conversation2=Mae'r person roeddech yn galw wedi dod a'r sgwr i ben.
peer_ended_conversation2=Mae'r person roeddech yn galw wedi dod a'r sgwrs i ben.
restart_call=Ailymuno
## LOCALIZATION NOTE (contact_offline_title): Title which is displayed when the
@ -258,7 +258,7 @@ rooms_panel_title=Dewiswch sgwrs neu gychwyn un newydd
rooms_room_full_call_to_action_label=Dysgu rhagor am {{clientShortname}} »
rooms_room_full_call_to_action_nonFx_label=Llwytho {{brandShortname}} i lawr i gychwyn eich sgwrs eich hun
rooms_room_full_label=Mae eisioes dau berson yn y sgwrs.
rooms_room_full_label=Mae eisoes dau berson yn y sgwrs.
rooms_room_join_label=Ymuno â'r sgwrs
rooms_room_joined_owner_connected_label2=Mae eich ffrind nawr wedi cysylltu a bydd yn gallu gweld eich tabiau.
rooms_room_joined_owner_not_connected_label=Mae eich ffrind yn aros i gael pori {{roomURLHostname}} gyda chi.

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

@ -218,7 +218,7 @@ infobar_button_disconnect_accesskey=t
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.
copy_panel_accept_button_label=Ja (Anleitung öffnen).
# E10s not supported strings

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

@ -14,9 +14,11 @@
## and this is displayed in slightly larger font. Please arrange as necessary for
## your locale.
## {{clientShortname2}} will be replaced by the brand name for either string.
sign_in_again_button=Είσοδος
## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
## will be replaced by the super short brandname.
panel_disconnect_button=Αποσύνδεση
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.

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

@ -267,6 +267,7 @@ self_view_hidden_message=Self-view hidden but still being sent; resize window \\
peer_left_session=Your friend has left.
peer_unexpected_quit=Your friend has unexpectedly disconnected.
peer_join_session=Your friend has joined.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -94,12 +94,6 @@ share_email_body7=A friend is waiting for you on Firefox Hello. Click the link t
share_email_body_context3=A friend is waiting for you on Firefox Hello. Click the link to connect and browse {{title}} together: {{callUrl}}
## LOCALIZATION NOTE (share_email_footer2): Common footer content for both email types
share_email_footer2=\n\n____________\nFirefox Hello lets you browse the Web with your friends. Use it when you want to get things done: plan together, work together, laugh together. Learn more at http://www.firefox.com/hello
## LOCALIZATION NOTE (share_tweeet): In this item, don't translate the part
## between {{..}}. Please keep the text below 117 characters to make sure it fits
## in a tweet.
share_tweet=Join me for a video conversation on {{clientShortname2}}!
share_add_service_button=Add a Service
## LOCALIZATION NOTE (copy_link_menuitem, email_link_menuitem, delete_conversation_menuitem):
## These menu items are displayed from a panel's context menu for a conversation.

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

@ -7,6 +7,8 @@
## 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.
loopMenuItem_label=Iniciar una conversación…
loopMenuItem_accesskey=n
## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
## These are displayed together at the top of the panel when a user is needed to
@ -14,99 +16,264 @@
## and this is displayed in slightly larger font. Please arrange as necessary for
## your locale.
## {{clientShortname2}} will be replaced by the brand name for either string.
sign_in_again_title_line_one=Ingrese nuevamente
sign_in_again_title_line_two2=para continuar usando {{clientShortname2}}
sign_in_again_button=Ingresar
## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
## will be replaced by the super short brandname.
sign_in_again_use_as_guest_button2=Usar {{clientSuperShortname}} como invitado
panel_browse_with_friend_button=Navegá esta página con un amigo
panel_disconnect_button=Desconectar
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
first_time_experience_subheading2=Clic en el botón Hello para navegar páginas web con un amigo.
first_time_experience_subheading_button_above=Clic en el botón que está encima para navegar páginas web con un amigo.
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
## ways to use Hello project.
first_time_experience_content=Puede usarse para planificar juntos, trabajar juntos, reírse juntos.
first_time_experience_content2=Puede usarse para planificar juntos, trabajar juntos, reírse juntos.
first_time_experience_button_label2=Ver como funciona
## First Time Experience Slides
fte_slide_1_title=Navegá páginas web con un amigo
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_1_copy=Si estás planificando un viaje o comprando un regalo, {{clientShortname2}} permite tomar decisiones más rápidas en tiempo real.
fte_slide_2_title2=Hecho para compartir la web
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_2_copy2=Ahora, cuando invités a un amigo a una sesión, {{clientShortname2}} compartirá automáticamente cualquier página web que estés viendo. Planifiquen. Compren. Decidan. Juntos.
fte_slide_3_title=Invitá a un amigo enviándole un enlace
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
fte_slide_3_copy={{clientSuperShortname}} funciona con la mayor parte de los navegadores de escritorio. No se necesitan cuentas y todos se conectan gratis.
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
## will be replaced by the super short brand name.
fte_slide_4_title=Buscá el ícono de {{clientSuperShortname}} para empezar
## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
## will be replaced by the brand short name.
fte_slide_4_copy=Cuando encontraste una pagina sobre la que querés discutir, clic en el ícono en {{brandShortname}} para crear un enlace. ¡Después enviáselo a tu amigo como mejor te parezca!
invite_header_text_bold2=¡Invitá a un amigo a unirse!
invite_header_text4=Compartí este enlace para poder empezar a navegar la web juntos.
## 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.
invite_copy_link_button=Copiar enlace
invite_copied_link_button=¡Copiado!
invite_email_link_button=Enlace por correo
invite_facebook_button3=Facebook
invite_your_link=Tu enlace:
# 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.
session_expired_error_description=Sesión expirada. Toda URL creada previamente y compartida no funcionará más.
could_not_authenticate=No se pudo autenticar
password_changed_question=¿Cambiaste tu contraseña?
try_again_later=Intentá nuevamente
could_not_connect=No se pudo conectar al servidor
check_internet_connection=Verificá la conexión a internet
login_expired=El ingreso ha expirado
service_not_available=Servicio no disponible en este momento
problem_accessing_account=Hubo un problema accediendo a la cuenta
## LOCALIZATION NOTE(retry_button): Displayed when there is an error to retry
## the appropriate action.
retry_button=Reintentar
share_email_subject7=Tu invitación a navegar la web juntos
## LOCALIZATION NOTE (share_email_body7): In this item, don't translate the
## part between {{..}} and leave the \n\n part alone
share_email_body7=Un amigo te está esperando en Firefox Hello. Clic en el enlace para conectar y navegar la web juntos: {{callUrl}}
## LOCALIZATION NOTE (share_email_body_context3): In this item, don't translate
## the part between {{..}} and leave the \n\n part alone.
share_email_body_context3=Un amigo te está esperando en Firefox Hello. Clic en el enlace para conectar y navegar {{title}} juntos: {{callUrl}}
## LOCALIZATION NOTE (share_email_footer2): Common footer content for both email types
share_email_footer2=\n\n____________\nFirefox Hello permite navegar la web con tus amigos. Usalo cuando quieras que se hagan las cosas: planificar juntos, trabajar juntos, reírse juntos. Conocé más en http://www.firefox.com/hello
## LOCALIZATION NOTE (share_tweeet): In this item, don't translate the part
## between {{..}}. Please keep the text below 117 characters to make sure it fits
## in a tweet.
share_tweet=¡Unite a una conversación en video en {{clientShortname2}}!
share_add_service_button=Agregar un servicio
## LOCALIZATION NOTE (copy_link_menuitem, email_link_menuitem, delete_conversation_menuitem):
## These menu items are displayed from a panel's context menu for a conversation.
copy_link_menuitem=Copiar enlace
email_link_menuitem=Enlace por correo
edit_name_menuitem=Editar nombre
delete_conversation_menuitem2=Borrar
panel_footer_signin_or_signup_link=Ingresar o registrarse
settings_menu_item_account=Cuenta
settings_menu_item_settings=Configuración
settings_menu_item_signout=Salir
settings_menu_item_signin=Ingresar
settings_menu_item_turnnotificationson=Habilitar notificaciones
settings_menu_item_turnnotificationsoff=Deshabilitar notificaciones
settings_menu_item_feedback=Enviar opinión
settings_menu_button_tooltip=Configuración
# Conversation Window Strings
initiate_call_button_label2=¿Listo para empezar la conversación?
incoming_call_title2=Pedido de conversación
incoming_call_block_button=Bloquear
hangup_button_title=Desconectar
hangup_button_caption2=Salir
## LOCALIZATION NOTE (call_with_contact_title): The title displayed
## when calling a contact. Don't translate the part between {{..}} because
## this will be replaced by the contact's name.
call_with_contact_title=Conversación con {{contactName}}
# Outgoing conversation
outgoing_call_title=¿Comenzar conversación?
initiate_audio_video_call_button2=Comenzar
initiate_audio_video_call_tooltip2=Comenzar una conversación con video
initiate_audio_call_button2=Conversación de voz
peer_ended_conversation2=La persona que estabas llamando terminó la conversación.
restart_call=Volvé a unirte
## LOCALIZATION NOTE (contact_offline_title): Title which is displayed when the
## contact is offline.
contact_offline_title=Esta persona no está conectada
## LOCALIZATION NOTE (call_timeout_notification_text): Title which is displayed
## when the call didn't go through.
call_timeout_notification_text=La llamada no prosperó.
## LOCALIZATION NOTE (cancel_button):
## This button is displayed when a call has failed.
cancel_button=Cancelar
rejoin_button=Volvé a unirte a la conversación
cannot_start_call_session_not_ready=No se puede comenzar la conversación, la sesión no está lista.
network_disconnected=La conexión de red terminó abruptamente.
connection_error_see_console_notification=Falló la llamada; mirá la consola para más detalles.
no_media_failure_message=No se encontró cámara o micrófono.
ice_failure_message=Falló la conexión. El firewall puede estar bloqueando llamadas.
## LOCALIZATION NOTE (legal_text_and_links3): In this item, don't translate the
## parts between {{..}} because these will be replaced with links with the labels
## from legal_text_tos and legal_text_privacy. clientShortname will be replaced
## by the brand name.
legal_text_and_links3=Al usar {{clientShortname}} acepta los {{terms_of_use}} y la {{privacy_notice}}.
legal_text_tos=Términos de uso
legal_text_privacy=Nota de privacidad
## LOCALIZATION NOTE (powered_by_beforeLogo, powered_by_afterLogo):
## These 2 strings are displayed before and after a 'Telefonica'
## logo.
powered_by_beforeLogo=Con tecnología de
powered_by_afterLogo=
## LOCALIZATION_NOTE (feedback_rejoin_button): Displayed on the feedback form after
## a signed-in to signed-in user call.
feedback_rejoin_button=Volver a unirse
## LOCALIZATION NOTE (feedback_report_user_button): Used to report a user in the case of
## an abusive user.
feedback_report_user_button=Informar usuario
feedback_window_heading=¿Cómo fue la conversación?
feedback_request_button=Dejar opinión
rooms_list_recently_browsed2=Navegado recientemente
rooms_list_currently_browsing2=Navegando actualmente
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_bye=¡Nos vemos!
door_hanger_return2=Podés volver a esta sesión compartida en cualquier momento desde el panel de Hello. ¿Querés ponerle un nombre para que sea más fácil de recordar?
door_hanger_current=Nombre actual:
door_hanger_button2=Aceptar
# Infobar strings
infobar_screenshare_no_guest_message=En cuanto tu amigo se una, podrán ver cualquier pestaña en la que se haga clic.
infobar_screenshare_browser_message2=Estás compartiendo tus pestañas. Cualquier pestaña en la que hagás clic 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 hagás clic.
infobar_screenshare_stop_sharing_message2=Ya no estás compartiendo las pestañas.
infobar_screenshare_stop_no_guest_message=Dejaste de compartir tus pestañas. Cuando tu amigo se una, no podrá ver nada hasta que se reinicie la comparticion.
infobar_button_restart_label2=Reiniciar compartición
infobar_button_restart_accesskey=e
infobar_button_stop_label2=Dejar de compartir
infobar_button_stop_accesskey=a
infobar_button_disconnect_label=Desconectar
infobar_button_disconnect_accesskey=D
# Copy panel strings
copy_panel_message=¿Necesitás compartir esta página web? Compartí la pestaña del navegador con un amigo.
copy_panel_dont_show_again_label=No mostrar nuevamente
copy_panel_cancel_button_label=No ahora
copy_panel_accept_button_label=Si, mostrarme como
# E10s not supported strings
e10s_not_supported_button_label=Abrir nueva ventana
e10s_not_supported_subheading={{brandShortname}} no funciona en una ventana multiproceso.
# 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/.
## LOCALIZATION NOTE: In this file, don't translate the part between {{..}}
# Text chat strings
chat_textbox_placeholder=Esscribí aquí...
## LOCALIZATION NOTE(clientShortname2): This should not be localized and
## should remain "Firefox Hello" for all locales.
clientShortname2=Firefox Hello
conversation_has_ended=Se terminó tu conversación.
generic_failure_message=Estamos con problemas técnicos...
generic_failure_no_reason2=¿Querés probar de vuelta?
help_label=Ayuda
mute_local_audio_button_title=Deshabilitá tu audio
unmute_local_audio_button_title=Habilitá tu audio
mute_local_video_button_title2=Deshabilitar video
unmute_local_video_button_title2=Habilitar video
## LOCALIZATION NOTE (retry_call_button):
## This button is displayed when a call has failed.
retry_call_button=Probar de nuevo
rooms_leave_button_label=Salir
rooms_panel_title=Elegí una conversación o empezá una nueva
rooms_room_full_call_to_action_label=Aprendé más sobre ({clientShortname}) »
rooms_room_full_call_to_action_nonFx_label=Descargá ({brandShortname}) para empezar la tuya
rooms_room_full_label=Ya hay dos personas en esta conversación.
rooms_room_join_label=Unite a la conversación
rooms_room_joined_owner_connected_label2=Ahroa tu amigo está conectado y podrá ver tus pestañas.
rooms_room_joined_owner_not_connected_label=Tu amigo está esperando para navegar ({roomURLHostname}) con vos.
self_view_hidden_message=La vista propia está oculta pero se envía de todas maneras; cambiá el tamaño de la ventana para verlo
peer_left_session=Tu amigo se fue.
peer_unexpected_quit=Tu amigo se desconectó de manera inesperada.
peer_join_session=Tu amigo se unió.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.
tos_failure_message=({clientShortname}) no está disponible en tu país.
display_name_guest=Invitado
## LOCALIZATION NOTE(clientSuperShortname): This should not be localized and
## should remain "Hello" for all locales.
clientSuperShortname=Hello

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

@ -268,6 +268,7 @@ self_view_hidden_message=La vista local está oculta pero continúa siendo envia
peer_left_session=Tu amigo se ha ido.
peer_unexpected_quit=Tu amigo se ha desconectado inesperadamente.
peer_join_session=Tu amigo se ha unido.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -267,6 +267,7 @@ self_view_hidden_message=Auto-vista oculta pero enviándose; redimensiona ventan
peer_left_session=Tu amigo se ha ido.
peer_unexpected_quit=Tu amigo se ha desconectado inesperadamente.
peer_join_session=Tu amigo se ha unido.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -268,6 +268,7 @@ self_view_hidden_message=Sinu pilti edastatakse, kuid see on praegu peidetud. Pi
peer_left_session=Sõber on lahkunud.
peer_unexpected_quit=Ühendus sõbraga on ootamatult katkenud.
peer_join_session=Sinu sõber liitus.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -203,6 +203,8 @@ door_hanger_button2=تایید!
infobar_screenshare_no_guest_message=هر موقع دوستان شما متصل شوند، آنها قادر خواهند بود هر زبانه‌ای که روی‌اش کلیک می‌کنید را ببینند.
infobar_screenshare_browser_message2=شما در حال اشتراک‌گذاری زبانه‌های خود هستید. هر زبانه‌ای که بر روی‌اش کلیک کنید، توسط دوستانتان دید میشود
infobar_screenshare_browser_message3=شما هم‌اکنون در حال اشتراک‌گذاری زبانه‌های خود هستید. دوستان شما هر زبانه‌ای که روی‌اش کلیک کنید را خواهند دید.
infobar_screenshare_stop_sharing_message2=شما هیچ زبانه‌ای را با دیگران به اشتراک نگذاشته‌اید.
infobar_screenshare_stop_no_guest_message=شما به اشتراک گذاری زبانه‌های خود را متوقف کرده‌اید. وقتی دوستان شما وارد شوند، قادر به دیدن چیزی نخواهند بود تا زمانی که شما به اشتراک گذاری را از نو راه‌اندازی کنید.
infobar_button_restart_label2=راه‌اندازی مجدد اشتراک‌گذاری
infobar_button_restart_accesskey=e
infobar_button_stop_label2=توقف اشتراک‌گذاری
@ -265,6 +267,7 @@ self_view_hidden_message=نمای فردی پنهان شده است ولی ار
peer_left_session=دوست شما گفت‌وگو را ترک کرد.
peer_unexpected_quit=دوست شما بطور غیرمنتظره‌ای قطع شد.
peer_join_session=دوستان شما وارد شده‌اند.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -45,7 +45,7 @@ fte_slide_1_copy=Oft jo no in reis planne of in kado sykje, mei {{clientShortnam
fte_slide_2_title2=Makke om it web te dielen
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_2_copy2=As jo no in freon útnoegje foar in sesje, sil {{clientShortname2}} automatysk websiden dy't jo besjen diele. Plan. Winkelje. Beslis. Tegearre.
fte_slide_2_copy2=As jo no in freon útnûgje foar in sesje, sil {{clientShortname2}} automatysk websiden dy't jo besjen diele. Plan. Winkelje. Beslis. Tegearre.
fte_slide_3_title=Noegje in freon út troch in keppeling te dielen
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
@ -85,7 +85,7 @@ problem_accessing_account=Der wie in probleem mei tagong ta jo account
## the appropriate action.
retry_button=Nochris probearje
share_email_subject7=Jo útnoeging om tegearre op it web te sneupjen
share_email_subject7=Jo útnûging om tegearre op it web te sneupjen
## LOCALIZATION NOTE (share_email_body7): In this item, don't translate the
## part between {{..}} and leave the \n\n part alone
share_email_body7=In freon wachtet op dy op Firefox Hello. Klik op de keppeling om te ferbinen en sneup tegearre op it web: {{callUrl}}

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

@ -45,7 +45,7 @@ fte_slide_1_copy=Oft jo no in reis planne of in kado sykje, mei {{clientShortnam
fte_slide_2_title2=Makke om it web te dielen
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_2_copy2=As jo no in freon útnoegje foar in sesje, sil {{clientShortname2}} automatysk websiden dy't jo besjen diele. Plan. Winkelje. Beslis. Tegearre.
fte_slide_2_copy2=As jo no in freon útnûgje foar in sesje, sil {{clientShortname2}} automatysk websiden dy't jo besjen diele. Plan. Winkelje. Beslis. Tegearre.
fte_slide_3_title=Noegje in freon út troch in keppeling te dielen
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
@ -85,7 +85,7 @@ problem_accessing_account=Der wie in probleem mei tagong ta jo account
## the appropriate action.
retry_button=Nochris probearje
share_email_subject7=Jo útnoeging om tegearre op it web te sneupjen
share_email_subject7=Jo útnûging om tegearre op it web te sneupjen
## LOCALIZATION NOTE (share_email_body7): In this item, don't translate the
## part between {{..}} and leave the \n\n part alone
share_email_body7=In freon wachtet op dy op Firefox Hello. Klik op de keppeling om te ferbinen en sneup tegearre op it web: {{callUrl}}

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

@ -7,6 +7,7 @@
## 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.
loopMenuItem_label=התחלת שיחה…
## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
## These are displayed together at the top of the panel when a user is needed to
@ -14,35 +15,61 @@
## and this is displayed in slightly larger font. Please arrange as necessary for
## your locale.
## {{clientShortname2}} will be replaced by the brand name for either string.
sign_in_again_title_line_one=נא להתחבר שוב
sign_in_again_title_line_two2=כדי להמשיך להשתמש ב־{{clientShortname2}}
## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
## will be replaced by the super short brandname.
sign_in_again_use_as_guest_button2=השתמש ב־{{clientSuperShortname}} כאורח
panel_browse_with_friend_button=גלישה בדף זה עם חבר
panel_disconnect_button=התנתקות
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
first_time_experience_subheading2=לחץ על כפתור ה־Hello כדי לגלוש באתרים עם חבר.
first_time_experience_subheading_button_above=לחץ על הכפתור שלמעלה כדי לגלוש באתרים עם חבר.
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
## ways to use Hello project.
first_time_experience_content=השתמש בזה כדי לתכנן ביחד, לעבוד ביחד, לצחוק ביחד.
first_time_experience_button_label2=ראו כיצד זה עובד
## First Time Experience Slides
fte_slide_1_title=גלישה באתרים עם חבר
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_2_title2=נועד לשיתוף הרשת
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_3_title=הזמנת חבר באמצעות שליחת קישור
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
fte_slide_3_copy={{clientSuperShortname}} עובד עם רוב הדפדפנים השולחניים. אין צורך בחשבונות וכולם יכולים להתחבר בחינם.
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
## will be replaced by the super short brand name.
fte_slide_4_title=אתר את סמל ה־{{clientSuperShortname}} כדי להתחיל
## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
## will be replaced by the brand short name.
invite_header_text_bold2=הזמן חבר שיצטרף אליך!
invite_header_text4=שתף קישור זה כך שתוכלו לגלוש ברשת ביחד.
## 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.
invite_copy_link_button=העתקת קישור
invite_copied_link_button=הועתק!
invite_email_link_button=שליחת קישור בדואר אלקטרוני
invite_facebook_button3=Facebook
invite_your_link=הקישור שלך:
# 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.
could_not_authenticate=לא ניתן לאמת
password_changed_question=האם שינית את ססמתך?
try_again_later=נא לנסות שוב מאוחר יותר
could_not_connect=לא ניתן להתחבר לשרת
service_not_available=השירות אינו זמין כעת
## LOCALIZATION NOTE(retry_button): Displayed when there is an error to retry
## the appropriate action.
@ -56,15 +83,29 @@
## between {{..}}. Please keep the text below 117 characters to make sure it fits
## in a tweet.
share_add_service_button=הוספת שירות
## LOCALIZATION NOTE (copy_link_menuitem, email_link_menuitem, delete_conversation_menuitem):
## These menu items are displayed from a panel's context menu for a conversation.
copy_link_menuitem=העתקת קישור
email_link_menuitem=שליחת קישור בדוא״ל
edit_name_menuitem=עריכת שם
delete_conversation_menuitem2=מחיקה
panel_footer_signin_or_signup_link=הרשמה או התחברות
settings_menu_item_account=חשבון
settings_menu_item_settings=הגדרות
settings_menu_item_signout=התנתקות
settings_menu_item_turnnotificationson=הפעלת התראות
settings_menu_item_turnnotificationsoff=נטרול התראות
settings_menu_item_feedback=שליחת משוב
settings_menu_button_tooltip=הגדרות
# Conversation Window Strings
incoming_call_block_button=חסימה
hangup_button_title=ניתוק
hangup_button_caption2=יציאה
@ -75,6 +116,8 @@ hangup_button_caption2=יציאה
# Outgoing conversation
initiate_audio_video_call_tooltip2=התחלת שיחת וידאו
initiate_audio_call_button2=שיחת וידאו
## LOCALIZATION NOTE (contact_offline_title): Title which is displayed when the
@ -84,12 +127,15 @@ hangup_button_caption2=יציאה
## LOCALIZATION NOTE (cancel_button):
## This button is displayed when a call has failed.
cancel_button=ביטול
## LOCALIZATION NOTE (legal_text_and_links3): In this item, don't translate the
## parts between {{..}} because these will be replaced with links with the labels
## from legal_text_tos and legal_text_privacy. clientShortname will be replaced
## by the brand name.
legal_text_tos=תנאי שימוש
legal_text_privacy=הצהרת פרטיות
## LOCALIZATION NOTE (powered_by_beforeLogo, powered_by_afterLogo):
## These 2 strings are displayed before and after a 'Telefonica'
@ -99,19 +145,30 @@ hangup_button_caption2=יציאה
## a signed-in to signed-in user call.
## LOCALIZATION NOTE (feedback_report_user_button): Used to report a user in the case of
## an abusive user.
feedback_report_user_button=דיווח על משתמש
feedback_request_button=יציאה מהמשוב
tour_label=סיור
room_name_untitled_page=דף ללא כותרת
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_bye=להתראות!
door_hanger_current=שם נוכחי:
door_hanger_button2=קיבלתי!
# Infobar strings
infobar_button_disconnect_label=התנתקות
# Copy panel strings
copy_panel_accept_button_label=כן, הראו לי כיצד
# E10s not supported strings
e10s_not_supported_button_label=פתיחת חלון חדש
e10s_not_supported_subheading={{brandShortname}} לא עובד בחלון מרובה תהליכים.
# 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/.
@ -119,11 +176,14 @@ hangup_button_caption2=יציאה
## LOCALIZATION NOTE: In this file, don't translate the part between {{..}}
# Text chat strings
chat_textbox_placeholder=הקלד כאן…
## LOCALIZATION NOTE(clientShortname2): This should not be localized and
## should remain "Firefox Hello" for all locales.
clientShortname2=Firefox Hello
conversation_has_ended=הדיון הסתיים.
conversation_has_ended=השיחה הסתיימה.
generic_failure_message=יש לנו קשיים טכניים…
generic_failure_no_reason2=לנסות שוב?
@ -131,6 +191,8 @@ help_label=עזרה
mute_local_audio_button_title=השתקת השמע שלך
unmute_local_audio_button_title=ביטול השתקת השמע שלך
mute_local_video_button_title2=נטרול וידאו
unmute_local_video_button_title2=אפשר וידאו
## LOCALIZATION NOTE (retry_call_button):
## This button is displayed when a call has failed.
@ -138,12 +200,24 @@ retry_call_button=ניסיון חוזר
rooms_leave_button_label=עזיבה
rooms_panel_title=בחר בשיחה או התחל שיחה חדשה
rooms_room_join_label=הצטרפות לדיון
rooms_room_full_call_to_action_label=מידע נוסף אודות {{clientShortname}} »
rooms_room_full_label=יש כבר שני אנשים בשיחה זו.
rooms_room_join_label=הצטרפות לשיחה
rooms_room_joined_owner_connected_label2=חברך כרגע מחובר ויוכל לצפות בלשוניות שלך.
rooms_room_joined_owner_not_connected_label=חברך ממתין לגלוש ב־{{roomURLHostname}} יחד אתך.
peer_left_session=חברך עזב.
peer_unexpected_quit=חברך התנתק באופן לא צפוי.
peer_join_session=חברך הצטרף.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.
tos_failure_message={{clientShortname}} אינו זמין במדינה שלך.
display_name_guest=אורח
## LOCALIZATION NOTE(clientSuperShortname): This should not be localized and
## should remain "Hello" for all locales.

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

@ -265,8 +265,9 @@ rooms_room_joined_owner_not_connected_label=Teman Anda sedang menunggu untuk men
self_view_hidden_message=Tampilan diri sedang tersembunyi tetapi tetap dikirim, ubah ukuran jendela untuk menampilkannya
peer_left_session=Teman anda telah pergi.
peer_unexpected_quit=Teman anda tiba-tiba terputus.
peer_left_session=Teman Anda telah pergi.
peer_unexpected_quit=Teman Anda tiba-tiba tak tersambung.
peer_join_session=Teman Anda telah bergabung.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -267,6 +267,7 @@ self_view_hidden_message=Lanteprima della fotocamera è nascosta, ma linte
peer_left_session=L'interlocutore si è disconnesso.
peer_unexpected_quit=L'interlocutore si è inaspettatamente disconnesso.
peer_join_session=Linterlocutore si è aggiunto alla conversazione.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -24,6 +24,7 @@ sign_in_again_button=로그인
sign_in_again_use_as_guest_button2=손님으로 {{clientSuperShortname}} 쓰기
panel_browse_with_friend_button=친구와 함께 이 페이지 사용
panel_disconnect_button=연결 끊음
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.

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

@ -222,7 +222,7 @@ copy_panel_accept_button_label=Ja, toon me hoe
# E10s not supported strings
e10s_not_supported_button_label=Nieuw venster openen
e10s_not_supported_subheading={{brandShortname}} werkt niet in een multi-process-venster.
e10s_not_supported_subheading={{brandShortname}} werkt niet in een multiprocess-venster.
# 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/.

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

@ -35,7 +35,7 @@ first_time_experience_subheading_button_above=Trykk på knappen ovanfor for å s
## ways to use Hello project.
first_time_experience_content=Bruk han til å planleggja, arbeida, og ha det moro i lag.
first_time_experience_content2=Bruk han til å få gjort ting: planlegging, arbeid og for å ha det moro i lag.
first_time_experience_button_label2=Sjå korleis det fungerar
first_time_experience_button_label2=Sjå korleis det fungerer
## First Time Experience Slides
fte_slide_1_title=Surf på nettsider med ein ven
@ -45,11 +45,11 @@ fte_slide_1_copy=Om du planlegg ei reise eller leitar du etter ein present, {{cl
fte_slide_2_title2=Laga for å dela nettet
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_2_copy2=Når du inviterer ein ven til ei ykt, vil {{clientShortname2}} automatisk dela nettsidene du ser på. Planlegg. Handla. Ta avgjerder. Saman.
fte_slide_2_copy2=Når du inviterer inn ein ven til ei økt, vil {{clientShortname2}} automatisk dela alle nettsider du viser. Planlegg. Gjer innkjøp. Ta avgjerder. Ilag.
fte_slide_3_title=Inviter ein ven ved å senda ei lenke
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
## will be replaced by the super short brand name.
fte_slide_3_copy={{clientSuperShortname}} fungerar med dei fleste nettlesarar. Det treng ingen konto og elle tilkoplingar er gratis.
fte_slide_3_copy={{clientSuperShortname}} fungerer med dei fleste nettlesarar. Du treng ingen konto og alle tilkoplingar er gratis.
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
## will be replaced by the super short brand name.
fte_slide_4_title=Finn ikonet for {{clientSuperShortname}} for å koma i gang
@ -222,7 +222,7 @@ copy_panel_accept_button_label=Ja, vis meg korleis
# E10s not supported strings
e10s_not_supported_button_label=Opna nytt vindauge
e10s_not_supported_subheading={{brandShortname}} fungerar ikkje i eit multiprosessvindauge.
e10s_not_supported_subheading={{brandShortname}} fungerer ikkje i eit multiprosessvindauge.
# 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/.
@ -267,6 +267,7 @@ self_view_hidden_message=Bildet frå eige kamera er skjult, men vert framleis se
peer_left_session=Venane dine fór.
peer_unexpected_quit=Venane dine har uventa kopla seg frå.
peer_join_session=Venen din er no med.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -23,9 +23,9 @@ first_time_experience_button_label2=Przekonaj się, jak to działa
fte_slide_1_title=Przeglądaj strony WWW wspólnie ze znajomymi
fte_slide_1_copy=Niezależnie czy planujesz wycieczkę, zakup prezentu, {{clientShortname2}} pozwala podejmować decyzje szybciej.
fte_slide_2_title2=Stworzony, by dzielić się siecią
fte_slide_2_copy2=Zapraszając znajomego do czatu, {{clientShortname2}} automatycznie udostępni stronę którą oglądasz. Planuj. Rób zakupy. Decyduj. Wspólnie.
fte_slide_2_copy2=Zapraszając znajomego do czatu, {{clientShortname2}} automatycznie udostępni stronę, którą oglądasz. Planuj. Rób zakupy. Decyduj. Wspólnie.
fte_slide_3_title=Zaproś znajomego wysyłając odnośnik
fte_slide_3_copy={{clientSuperShortname}} działa w większości przeglądarek na komputery. Nie jest wymagane zakładnie kont i wszyscy łączą się bez opłat.
fte_slide_3_copy={{clientSuperShortname}} działa w większości przeglądarek na komputery. Nie jest wymagane zakładanie kont i wszyscy łączą się bez opłat.
fte_slide_4_title=Odszukaj ikonę {{clientSuperShortname}}, aby rozpocząć
fte_slide_4_copy=Po znalezieniu strony do omówienia, kliknij ikonę w {{brandShortname}}, aby utworzyć odnośnik. Wyślij go znajomemu jakkolwiek chcesz!
@ -117,9 +117,6 @@ rooms_list_currently_browsing2=Obecnie aktywne
rooms_signout_alert=Otwarte rozmowy zostaną zamknięte
room_name_untitled_page=Strona bez tytułu
door_hanger_return=Do zobaczenia! Do tej współdzielonej sesji można zawsze wrócić przez panel Hello.
door_hanger_prompt_name=Czy nadać jej nazwę ułatwiającą jej zapamiętanie? Obecna nazwa to:
door_hanger_button=OK
door_hanger_bye=Do zobaczenia!
door_hanger_return2=Zawsze można powrócić do tej udostępnianej sesji poprzez panel Firefox Hello. Czy nadać ułatwiającą jej zapamiętanie nazwę?
door_hanger_current=Obecna nazwa:
@ -128,7 +125,8 @@ door_hanger_button2=OK
infobar_screenshare_no_guest_message=Jak tylko znajomy dołączy, będzie mógł oglądać zawartość każdej karty, którą klikniesz.
infobar_screenshare_browser_message2=Udostępniasz obraz kart. Rozmówcy mogą oglądać zawartość każdej karty, którą klikniesz.
infobar_screenshare_browser_message3=Udostępniasz obraz kart. Rozmówcy mogą oglądać zawartość każdej karty, którą klikniesz.
infobar_screenshare_stop_sharing_message=Udostępnianie obrazu kart zostało wstrzymane
infobar_screenshare_stop_sharing_message2=Udostępnianie obrazu kart zostało wstrzymane.
infobar_screenshare_stop_no_guest_message=Udostępnianie obrazu kart zostało wstrzymane. Gdy ktoś dołączy do rozmowy, nic nie zobaczy dopóki nie wznowisz udostępniania.
infobar_button_restart_label2=Wznów udostępnianie
infobar_button_restart_accesskey=W
infobar_button_stop_label2=Nie udostępniaj dłużej
@ -174,10 +172,11 @@ rooms_room_join_label=Dołącz do rozmowy
rooms_room_joined_owner_connected_label2=Zestawiono połączenie, druga osoba będzie od teraz mogła widzieć Twoje karty.
rooms_room_joined_owner_not_connected_label=Ktoś czeka, aby wspólnie przeglądać {{roomURLHostname}}.
self_view_hidden_message=Obraz z kamery jest ukryty ale nadal wysyłany (powiększenie okna ukaże go)
self_view_hidden_message=Obraz z kamery jest ukryty, ale nadal wysyłany (powiększenie okna ukaże go)
peer_left_session=Rozmówca się rozłączył.
peer_unexpected_quit=Rozmówca nieoczekiwanie się rozłączył.
peer_join_session=Dołączył rozmówca.
tos_failure_message=Usługa {{clientShortname}} nie jest dostępna w tym kraju.

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

@ -267,6 +267,7 @@ self_view_hidden_message=Vista própria oculta mas ainda a ser enviada; redimens
peer_left_session=O seu amigo saiu.
peer_unexpected_quit=O seu amigo foi desligado de forma inesperada.
peer_join_session=O seu amigo entrou.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -34,6 +34,7 @@ first_time_experience_subheading_button_above=Clicca sin il buttun survart per n
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
## ways to use Hello project.
first_time_experience_content=Fa diever da la funcziun per planisar ensemen, lavurar ensemen, rir ensemen.
first_time_experience_content2=Fa diever da la funcziun per planisar ensemen, lavurar ensemen, rir ensemen.
first_time_experience_button_label2=Mussar co che quai funcziunescha
## First Time Experience Slides
@ -193,6 +194,7 @@ rooms_signout_alert=Conversaziuns avertas vegnan serradas
room_name_untitled_page=Pagina senza titel
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
door_hanger_bye=A pli tard!
# Infobar strings

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

@ -7,6 +7,8 @@
## 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.
loopMenuItem_label=Pornește o conversație…
loopMenuItem_accesskey=t
## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
## These are displayed together at the top of the panel when a user is needed to
@ -14,35 +16,56 @@
## and this is displayed in slightly larger font. Please arrange as necessary for
## your locale.
## {{clientShortname2}} will be replaced by the brand name for either string.
sign_in_again_title_line_one=Te rugăm să te autentifici din nou
sign_in_again_title_line_two2=pentru a continua să folosești {{clientShortname2}}
sign_in_again_button=Autentificare
## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
## will be replaced by the super short brandname.
sign_in_again_use_as_guest_button2=Folosește {{clientSuperShortname}} ca oaspete
panel_browse_with_friend_button=Navighează pe această pagină cu un prieten
panel_disconnect_button=Deconectare
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
## user to create his or her first conversation.
first_time_experience_subheading2=Clic pe butonul Hello pentru a naviga pe pagini web cu un prieten.
first_time_experience_subheading_button_above=Clic pe butonul de mai sus pentru a naviga pe pagini web cu un prieten.
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
## ways to use Hello project.
first_time_experience_content=Folosește-l pentru a planifica împreună, a lucra împreună, a râde împreună.
first_time_experience_content2=Folosește-l pentru realizarea unor lucruri: pentru a planifica împreună, a lucra împreună, a râde împreună.
first_time_experience_button_label2=Vezi cum funcționează
## First Time Experience Slides
fte_slide_1_title=Navighează pe pagini web cu un prieten
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
## will be replaced by the short name 2.
fte_slide_2_title2=Realizat pentru a împărtăși webul
## 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.
fte_slide_4_title=Găsește iconița {{clientSuperShortname}} pentru a începe
## 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.
invite_copied_link_button=Copiat!
invite_email_link_button=Trimite e-mail cu linkul
invite_facebook_button3=Facebook
invite_your_link=Linkul tău:
# 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.
session_expired_error_description=Sesiune expirată. Toate URL-urile pe care le-ai creat anterior și partajate nu vor mai funcționa.
could_not_authenticate=Nu se poate autentifica
try_again_later=Te rugăm să încerci din nou mai târziu
## LOCALIZATION NOTE(retry_button): Displayed when there is an error to retry
## the appropriate action.
@ -56,15 +79,23 @@
## between {{..}}. Please keep the text below 117 characters to make sure it fits
## in a tweet.
share_add_service_button=Adaugă un serviciu
## LOCALIZATION NOTE (copy_link_menuitem, email_link_menuitem, delete_conversation_menuitem):
## These menu items are displayed from a panel's context menu for a conversation.
email_link_menuitem=Trimite e-mail cu linkul
settings_menu_item_account=Cont
settings_menu_item_settings=Setări
settings_menu_button_tooltip=Setări
# Conversation Window Strings
initiate_call_button_label2=Ești gata să pornești conversația?
incoming_call_title2=Cerere de conversație
incoming_call_block_button=Blochează
hangup_button_title=Închide
hangup_button_caption2=Ieșire
@ -75,7 +106,11 @@ hangup_button_caption2=Ieșire
# Outgoing conversation
outgoing_call_title=Pornești conversația?
initiate_audio_video_call_button2=Pornește
initiate_audio_video_call_tooltip2=Pornește o conversație video
restart_call=Realătură-te
## LOCALIZATION NOTE (contact_offline_title): Title which is displayed when the
## contact is offline.
@ -85,6 +120,7 @@ hangup_button_caption2=Ieșire
## LOCALIZATION NOTE (cancel_button):
## This button is displayed when a call has failed.
cannot_start_call_session_not_ready=Nu se poate porni apelul, sesiunea nu este pregătită.
## LOCALIZATION NOTE (legal_text_and_links3): In this item, don't translate the
## parts between {{..}} because these will be replaced with links with the labels
@ -106,6 +142,9 @@ hangup_button_caption2=Ieșire
# Infobar strings
infobar_button_restart_label2=Repornește partajarea
infobar_button_restart_accesskey=R
infobar_button_stop_label2=Oprește partajarea
# Copy panel strings
@ -125,8 +164,8 @@ chat_textbox_placeholder=Tastează aici…
## should remain "Firefox Hello" for all locales.
clientShortname2=Firefox Hello
conversation_has_ended=Conversația s-a încheiat.
generic_failure_message=Momentan întâmpinăm dificultăți tehnice…
conversation_has_ended=Conversația ta s-a încheiat.
generic_failure_message=Întâmpinăm dificultăți tehnice…
generic_failure_no_reason2=Dorești să încerci din nou?
@ -134,16 +173,16 @@ help_label=Ajutor
mute_local_audio_button_title=Închide sunetul
unmute_local_audio_button_title=Pornește sunetul
mute_local_video_button_title2=Oprește video
unmute_local_video_button_title2=Pornește video
mute_local_video_button_title2=Oprește videoul
unmute_local_video_button_title2=Pornește videoul
## LOCALIZATION NOTE (retry_call_button):
## This button is displayed when a call has failed.
retry_call_button=Reîncearcă
rooms_leave_button_label=Părăsire
rooms_leave_button_label=Părăsește
rooms_panel_title=Alege o conversație sau începe una nouă
rooms_panel_title=Alege o conversație sau pornește una nouă
rooms_room_full_call_to_action_label=Află mai multe despre {{clientShortname}} »
rooms_room_full_call_to_action_nonFx_label=Descarcă {{brandShortname}} pentru a porni propria conversație

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

@ -41,7 +41,7 @@ first_time_experience_button_label2=Посмотреть, как это рабо
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_1_copy=Будь то планирование поездки или покупка подарка, {{clientShortname2}} позволяет вам принимать решения быстрее в реальном времени.
fte_slide_2_title2=Создан для совместной работы в Интернете
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
## will be replaced by the short name 2.

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

@ -268,6 +268,7 @@ self_view_hidden_message=Záber z kamery je skrytý, napriek tomu sa stále odos
peer_left_session=Váš priateľ odišiel.
peer_unexpected_quit=Váš priateľ neočakávane ukončil spojenie.
peer_join_session=Váš priateľ sa pripojil.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -268,6 +268,7 @@ self_view_hidden_message=Приказ вашег екрана је сакрив
peer_left_session=Ваш пријатељ је изашао.
peer_unexpected_quit=Ваш пријатељ се неочекивано искључио.
peer_join_session=Ваш пријатељ се придружио.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -267,6 +267,7 @@ self_view_hidden_message=Kendi görünümünüz gizlendi ama hâlâ gönderiliyo
peer_left_session=Arkadaşınız çıktı.
peer_unexpected_quit=Arkadaşınızın bağlantısı koptu.
peer_join_session=Arkadaşınız geldi.
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
## as this will be replaced by clientShortname2.

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

@ -0,0 +1,25 @@
/* 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/. */
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
/* Only apply to browser.xul documents */
@-moz-document url("chrome://browser/content/browser.xul") {
/**
* XXX Due to bug 1228542, anything in this file that overrides a browser style
* must specify !important. Otherwise the style won't get applied correctly
* due to the limitations caused by the bug.
*/
#loop-button[state="slideshow"] {
background: none;
}
#loop-button[state="slideshow"] > .toolbarbutton-badge-stack {
background: var(--toolbarbutton-hover-background) !important;
background-clip: padding-box !important;
border-color: var(--toolbarbutton-hover-bordercolor) !important;
box-shadow: var(--toolbarbutton-hover-boxshadow) !important;
}
}

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

@ -23,4 +23,15 @@
list-style-image: url(chrome://loop/skin/toolbar-lunaSilver@2x.png)
}
}
#loop-button[state="slideshow"] {
background: none;
}
#loop-button[state="slideshow"] > .toolbarbutton-badge-stack {
background: var(--toolbarbutton-hover-background) !important;
background-clip: padding-box !important;
border-color: var(--toolbarbutton-hover-bordercolor) !important;
box-shadow: var(--toolbarbutton-hover-boxshadow) !important;
}
}

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

@ -16,11 +16,10 @@ skip-if = os == "linux" # Bug 1266041
[browser_mozLoop_chat.js]
[browser_mozLoop_context.js]
[browser_mozLoop_infobar.js]
[browser_mozLoop_socialShare.js]
[browser_panel_privateBrowsing.js]
[browser_mozLoop_sharingListeners.js]
[browser_mozLoop_telemetry.js]
skip-if = os == (win && !debug) || true # Bug 1267562 zombiecheck | child process 1228 still alive after shutdown (on win7-vm specifically) | Bug 1278389 for the true
skip-if = os == win && !debug # Bug 1267562 zombiecheck | child process 1228 still alive after shutdown (on win7-vm specifically)
[browser_sharingTitleListeners.js]
[browser_throttler.js]
[browser_toolbarbutton.js]

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

@ -6,7 +6,9 @@ var [, gHandlers] = LoopAPI.inspect();
add_task(function* test_mozLoop_appVersionInfo() {
let appVersionInfo;
gHandlers.GetAppVersionInfo({}, result => { appVersionInfo = result; });
gHandlers.GetAppVersionInfo({}, result => {
appVersionInfo = result;
});
Assert.ok(appVersionInfo, "should have appVersionInfo");

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

@ -1,98 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://gre/modules/Promise.jsm");
const { SocialService } = Cu.import("resource://gre/modules/SocialService.jsm", {});
var [, gHandlers] = LoopAPI.inspect();
const kShareProvider = {
name: "provider 1",
origin: "https://example.com",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png",
shareURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar_empty.html"
};
const kShareProviderInvalid = {
name: "provider 1",
origin: "https://example2.com"
};
const kActivationPage = "https://example.com/browser/browser/base/content/test/social/share_activate.html";
registerCleanupFunction(function* () {
Services.prefs.clearUserPref("social.shareDirectory");
yield new Promise(resolve => SocialService.disableProvider(kShareProvider.origin, resolve));
yield new Promise(resolve => SocialService.disableProvider(kShareProviderInvalid.origin, resolve));
Assert.strictEqual(Social.providers.length, 0, "all providers should be removed");
SocialShare.uninit();
});
add_task(function* test_mozLoop_addSocialShareProvider() {
Services.prefs.setCharPref("social.shareDirectory", kActivationPage);
gHandlers.AddSocialShareProvider({}, () => {});
yield promiseWaitForCondition(() => SocialShare.panel.state == "open");
Assert.equal(SocialShare.iframe.getAttribute("src"), "about:providerdirectory",
"Provider directory page should be visible");
SocialShare.panel.hidePopup();
});
add_task(function* test_mozLoop_getSocialShareProviders() {
let providers;
gHandlers.GetSocialShareProviders({}, result => { providers = result; });
Assert.strictEqual(providers.length, 0, "Provider list should be empty initially");
// Add a provider.
yield new Promise(resolve => SocialService.addProvider(kShareProvider, resolve));
gHandlers.GetSocialShareProviders({}, result => { providers = result; });
Assert.strictEqual(providers.length, 1,
"The newly added provider should be part of the list");
let provider = providers[0];
Assert.strictEqual(provider.iconURL, kShareProvider.iconURL, "Icon URLs should match");
Assert.strictEqual(provider.name, kShareProvider.name, "Names should match");
Assert.strictEqual(provider.origin, kShareProvider.origin, "Origins should match");
// Add another provider that should not be picked up by Loop.
yield new Promise(resolve => SocialService.addProvider(kShareProviderInvalid, resolve));
gHandlers.GetSocialShareProviders({}, result => { providers = result; });
Assert.strictEqual(providers.length, 1,
"The newly added provider should not be part of the list");
// Let's add a valid second provider object.
let provider2 = Object.create(kShareProvider);
provider2.name = "Wildly different name";
provider2.origin = "https://example3.com";
yield new Promise(resolve => SocialService.addProvider(provider2, resolve));
gHandlers.GetSocialShareProviders({}, result => { providers = result; });
Assert.strictEqual(providers.length, 2,
"The newly added provider should be part of the list");
Assert.strictEqual(providers[1].name, provider2.name,
"Providers should be ordered alphabetically");
// Remove the second valid provider.
yield new Promise(resolve => SocialService.disableProvider(provider2.origin, resolve));
gHandlers.GetSocialShareProviders({}, result => { providers = result; });
Assert.strictEqual(providers.length, 1,
"The uninstalled provider should not be part of the list");
Assert.strictEqual(providers[0].name, kShareProvider.name, "Names should match");
});
add_task(function* test_mozLoop_socialShareRoom() {
gHandlers.SocialShareRoom({ data: [kShareProvider.origin, "https://someroom.com",
"Some Title"] }, () => {});
yield promiseWaitForCondition(() => SocialShare.panel.state == "open");
Assert.equal(SocialShare.iframe.getAttribute("origin"), kShareProvider.origin,
"Origins should match");
Assert.equal(SocialShare.iframe.getAttribute("src"), kShareProvider.shareURL,
"Provider's share page should be displayed");
SocialShare.panel.hidePopup();
});

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

@ -9,7 +9,9 @@
var [, gHandlers] = LoopAPI.inspect();
var gConstants;
gHandlers.GetAllConstants({}, constants => { gConstants = constants; });
gHandlers.GetAllConstants({}, constants => {
gConstants = constants;
});
function resetMauPrefs() {
Services.prefs.clearUserPref("loop.mau.openPanel");
@ -36,65 +38,6 @@ add_task(function* test_initialize() {
});
});
/**
* Tests that enumerated bucket histograms exist and can be updated.
*/
add_task(function* test_mozLoop_telemetryAdd_buckets() {
let histogramId = "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1";
let histogram = Services.telemetry.getHistogramById(histogramId);
let CONN_LENGTH = gConstants.TWO_WAY_MEDIA_CONN_LENGTH;
histogram.clear();
for (let value of [CONN_LENGTH.SHORTER_THAN_10S,
CONN_LENGTH.BETWEEN_10S_AND_30S,
CONN_LENGTH.BETWEEN_10S_AND_30S,
CONN_LENGTH.BETWEEN_30S_AND_5M,
CONN_LENGTH.BETWEEN_30S_AND_5M,
CONN_LENGTH.BETWEEN_30S_AND_5M,
CONN_LENGTH.MORE_THAN_5M,
CONN_LENGTH.MORE_THAN_5M,
CONN_LENGTH.MORE_THAN_5M,
CONN_LENGTH.MORE_THAN_5M]) {
gHandlers.TelemetryAddValue({ data: [histogramId, value] }, () => {});
}
let snapshot = histogram.snapshot();
is(snapshot.counts[CONN_LENGTH.SHORTER_THAN_10S], 1, "TWO_WAY_MEDIA_CONN_LENGTH.SHORTER_THAN_10S");
is(snapshot.counts[CONN_LENGTH.BETWEEN_10S_AND_30S], 2, "TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_10S_AND_30S");
is(snapshot.counts[CONN_LENGTH.BETWEEN_30S_AND_5M], 3, "TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_30S_AND_5M");
is(snapshot.counts[CONN_LENGTH.MORE_THAN_5M], 4, "TWO_WAY_MEDIA_CONN_LENGTH.MORE_THAN_5M");
});
add_task(function* test_mozLoop_telemetryAdd_sharingURL_buckets() {
let histogramId = "LOOP_SHARING_ROOM_URL";
let histogram = Services.telemetry.getHistogramById(histogramId);
const SHARING_TYPES = gConstants.SHARING_ROOM_URL;
histogram.clear();
for (let value of [SHARING_TYPES.COPY_FROM_PANEL,
SHARING_TYPES.COPY_FROM_CONVERSATION,
SHARING_TYPES.COPY_FROM_CONVERSATION,
SHARING_TYPES.EMAIL_FROM_CALLFAILED,
SHARING_TYPES.EMAIL_FROM_CALLFAILED,
SHARING_TYPES.EMAIL_FROM_CALLFAILED,
SHARING_TYPES.EMAIL_FROM_CONVERSATION,
SHARING_TYPES.EMAIL_FROM_CONVERSATION,
SHARING_TYPES.EMAIL_FROM_CONVERSATION,
SHARING_TYPES.EMAIL_FROM_CONVERSATION]) {
gHandlers.TelemetryAddValue({ data: [histogramId, value] }, () => {});
}
let snapshot = histogram.snapshot();
Assert.strictEqual(snapshot.counts[SHARING_TYPES.COPY_FROM_PANEL], 1,
"SHARING_ROOM_URL.COPY_FROM_PANEL");
Assert.strictEqual(snapshot.counts[SHARING_TYPES.COPY_FROM_CONVERSATION], 2,
"SHARING_ROOM_URL.COPY_FROM_CONVERSATION");
Assert.strictEqual(snapshot.counts[SHARING_TYPES.EMAIL_FROM_CALLFAILED], 3,
"SHARING_ROOM_URL.EMAIL_FROM_CALLFAILED");
Assert.strictEqual(snapshot.counts[SHARING_TYPES.EMAIL_FROM_CONVERSATION], 4,
"SHARING_ROOM_URL.EMAIL_FROM_CONVERSATION");
});
add_task(function* test_mozLoop_telemetryAdd_roomCreate_buckets() {
let histogramId = "LOOP_ROOM_CREATE";
let histogram = Services.telemetry.getHistogramById(histogramId);
@ -114,59 +57,6 @@ add_task(function* test_mozLoop_telemetryAdd_roomCreate_buckets() {
"SHARING_ROOM_URL.CREATE_FAIL");
});
add_task(function* test_mozLoop_telemetryAdd_roomDelete_buckets() {
let histogramId = "LOOP_ROOM_DELETE";
let histogram = Services.telemetry.getHistogramById(histogramId);
const ACTION_TYPES = gConstants.ROOM_DELETE;
histogram.clear();
for (let value of [ACTION_TYPES.DELETE_SUCCESS,
ACTION_TYPES.DELETE_FAIL,
ACTION_TYPES.DELETE_FAIL]) {
gHandlers.TelemetryAddValue({ data: [histogramId, value] }, () => {});
}
let snapshot = histogram.snapshot();
Assert.strictEqual(snapshot.counts[ACTION_TYPES.DELETE_SUCCESS], 1,
"SHARING_ROOM_URL.DELETE_SUCCESS");
Assert.strictEqual(snapshot.counts[ACTION_TYPES.DELETE_FAIL], 2,
"SHARING_ROOM_URL.DELETE_FAIL");
});
add_task(function* test_mozLoop_telemetryAdd_roomSessionWithChat() {
let histogramId = "LOOP_ROOM_SESSION_WITHCHAT";
let histogram = Services.telemetry.getHistogramById(histogramId);
histogram.clear();
let snapshot;
for (let i = 1; i < 4; ++i) {
gHandlers.TelemetryAddValue({ data: [histogramId, 1] }, () => {});
snapshot = histogram.snapshot();
Assert.strictEqual(snapshot.counts[0], i);
}
});
add_task(function* test_mozLoop_telemetryAdd_infobarActionButtons() {
let histogramId = "LOOP_INFOBAR_ACTION_BUTTONS";
let histogram = Services.telemetry.getHistogramById(histogramId);
const ACTION_TYPES = gConstants.SHARING_SCREEN;
histogram.clear();
for (let value of [ACTION_TYPES.PAUSED,
ACTION_TYPES.PAUSED,
ACTION_TYPES.RESUMED]) {
gHandlers.TelemetryAddValue({ data: [histogramId, value] }, () => {});
}
let snapshot = histogram.snapshot();
Assert.strictEqual(snapshot.counts[ACTION_TYPES.RESUMED], 1,
"SHARING_SCREEN.RESUMED");
Assert.strictEqual(snapshot.counts[ACTION_TYPES.PAUSED], 2,
"SHARING_SCREEN.PAUSED");
});
add_task(function* test_mozLoop_telemetryAdd_loopMauType_buckets() {
let histogramId = "LOOP_ACTIVITY_COUNTER";
let histogram = Services.telemetry.getHistogramById(histogramId);

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

@ -9,7 +9,7 @@
<Description about="urn:mozilla:install-manifest">
<em:id>loop@mozilla.org</em:id>
<em:bootstrap>true</em:bootstrap>
<em:version>1.4.0</em:version>
<em:version>1.4.1</em:version>
<em:type>2</em:type>
<!-- Target Application this extension can install into,

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

@ -11,7 +11,7 @@ CONTENT_SERVER_URL = environ.get("CONTENT_SERVER_URL") or \
LOOP_SERVER_URLS = {
"local": "http://localhost:" + str(LOOP_SERVER_PORT),
"dev": "https://loop-dev.stage.mozaws.net",
"dev": "https://loop.dev.mozaws.net",
"stage": "https://loop.stage.mozaws.net",
"prod": "https://loop.services.mozilla.com"
}

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

@ -18,6 +18,11 @@ class LoopTestDriver():
def setUp(self, marionette):
self.marionette = marionette
# XXX This should be unnecessary once bug 1254132 is fixed.
def set_context(self, context):
self.context = context
self.marionette.set_context(context)
def wait_for_element_displayed(self, by, locator, timeout=None):
Wait(self.marionette, timeout,
ignored_exceptions=[NoSuchElementException, StaleElementException])\
@ -42,13 +47,25 @@ class LoopTestDriver():
.until(lambda e: element.is_enabled(),
message="Timed out waiting for element to be enabled")
def wait_for_element_attribute_to_be_false(self, element, attribute, timeout=10):
def wait_for_element_property_to_be_false(self, element, property, timeout=10):
# XXX We have to switch between get_attribute and get_property here as the
# content mode now required get_property for real properties of HTMLElements.
# However, in some places (e.g. switch_to_chatbox), we're still operating in
# a chrome mode. So we have to use get_attribute for these.
# Bug 1277065 should fix this for marionette, alternately this should go
# away when the e10s bug 1254132 is fixed.
def check_property(m):
if self.context == "content":
return not element.get_property(property)
return element.get_attribute(property) == "false"
Wait(self.marionette, timeout) \
.until(lambda e: element.get_attribute(attribute) == "false",
message="Timeout out waiting for " + attribute + " to be false")
.until(check_property,
message="Timeout out waiting for " + property + " to be false")
def open_panel(self):
self.marionette.set_context("chrome")
self.set_context("chrome")
self.marionette.switch_to_frame()
button = self.marionette.find_element(By.ID, "loop-button")
@ -56,13 +73,13 @@ class LoopTestDriver():
button.click()
def switch_to_panel(self):
self.marionette.set_context("chrome")
self.set_context("chrome")
# switch to the frame
frame = self.marionette.find_element(By.ID, "loop-panel-iframe")
self.marionette.switch_to_frame(frame)
def switch_to_chatbox(self):
self.marionette.set_context("chrome")
self.set_context("chrome")
self.marionette.switch_to_frame()
contentBox = "content"
@ -94,7 +111,7 @@ class LoopTestDriver():
copyLink.click()
def switch_to_standalone(self):
self.marionette.set_context("content")
self.set_context("content")
def load_homepage(self):
self.switch_to_standalone()
@ -128,8 +145,8 @@ class LoopTestDriver():
# Assumes the standalone or the conversation window is selected first.
def check_video(self, selector):
video = self.wait_for_element_displayed(By.CSS_SELECTOR, selector, 30)
self.wait_for_element_attribute_to_be_false(video, "paused")
self.assertEqual(video.get_attribute("ended"), "false")
self.wait_for_element_property_to_be_false(video, "paused")
self.wait_for_element_property_to_be_false(video, "ended")
def local_check_room_self_video(self):
self.switch_to_chatbox()

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

@ -1,5 +1,7 @@
[DEFAULT]
run-if = buildapp == 'browser'
# app == 'Firefox' is used for running in the Loop repo.
# buildapp == 'browser' is used for mozilla-central & co.
run-if = buildapp == 'browser' || app == 'Firefox'
[test_1_browser_call.py]
[test_2_linkclicker.py]

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

@ -147,38 +147,6 @@ class Test1BrowserCall(LoopTestDriver, MarionetteTestCase):
return self.marionette.execute_script(script, [chatbox])
def local_get_media_start_time(self):
return self.local_get_chatbox_window_expr(
"loop.conversation._sdkDriver._getTwoWayMediaStartTime()")
# XXX could be memoized
def local_get_media_start_time_uninitialized(self):
return self.local_get_chatbox_window_expr(
"loop.conversation._sdkDriver.CONNECTION_START_TIME_UNINITIALIZED"
)
def local_check_media_start_time_uninitialized(self):
self.assertEqual(
self.local_get_media_start_time(),
self.local_get_media_start_time_uninitialized(),
"media start time should be uninitialized before "
"link clicker enters room")
def local_check_media_start_time_initialized(self):
self.assertNotEqual(
self.local_get_media_start_time(),
self.local_get_media_start_time_uninitialized(),
"media start time should be initialized after "
"media is bidirectionally connected")
def local_check_connection_length_noted(self):
noted_calls = self.local_get_chatbox_window_expr(
"loop.conversation._sdkDriver._connectionLengthNotedCalls")
self.assertGreater(noted_calls, 0,
"OTSdkDriver._connectionLengthNotedCalls should be "
"> 0, noted_calls = " + str(noted_calls))
def local_close_conversation(self):
self.marionette.set_context("chrome")
self.marionette.switch_to_frame()
@ -216,9 +184,6 @@ class Test1BrowserCall(LoopTestDriver, MarionetteTestCase):
# Check the self video in the conversation window
self.local_check_room_self_video()
# make sure that the media start time is not initialized
self.local_check_media_start_time_uninitialized()
room_url = self.local_get_and_verify_room_url()
# load the link clicker interface into the current content browser
@ -231,10 +196,6 @@ class Test1BrowserCall(LoopTestDriver, MarionetteTestCase):
# Check text messaging
self.check_text_messaging()
# since bi-directional media is connected, make sure we've set
# the start time
self.local_check_media_start_time_initialized()
# Check that screenshare was automatically started
self.standalone_check_remote_screenshare()
@ -245,8 +206,6 @@ class Test1BrowserCall(LoopTestDriver, MarionetteTestCase):
# drops, rather than waiting until the window closes.
self.remote_leave_room()
self.local_check_connection_length_noted()
# Hangup on local will open feedback window first
self.local_close_conversation()
self.check_feedback_form()

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

@ -1510,6 +1510,13 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button {
box-shadow: @focusRingShadow@;
}
@media (-moz-mac-yosemite-theme) {
#urlbar[focused="true"],
.searchbar-textbox[focused="true"] {
box-shadow: @yosemiteFocusRingShadow@;
}
}
#urlbar-container {
-moz-box-align: center;
}

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

@ -10,6 +10,7 @@
}
</style>
<path id="camera" d="m 2,23 a 3,3 0 0 0 3,3 l 14,0 a 3,3 0 0 0 3,-3 l 0,-4 6,5.5 c 0.5,0.5 1,0.7 2,0.5 l 0,-18 c -1,-0.2 -1.5,0 -2,0.5 l -6,5.5 0,-4 a 3,3 0 0 0 -3,-3 l -14,0 a 3,3 0 0 0 -3,3 z" />
<path id="desktop-notification" d="m 2,20 a 4,4 0 0 0 4,4 l 13,0 7,7 0,-7 a 4,4 0 0 0 4,-4 l 0,-12 a 4,4 0 0 0 -4,-4 l -20,0 a 4,4 0 0 0 -4,4 z m 5,-2 a 1,1 0 1 1 0,-2 l 10,0 a 1,1 0 1 1 0,2 z m 0,-4 a 1,1 0 1 1 0,-2 l 14,0 a 1,1 0 1 1 0,2 z m 0,-4 a 1,1 0 1 1 0,-2 l 18,0 a 1,1 0 1 1 0,2 z" />
<path id="geo-linux" d="m 2,15.9 a 14,14 0 1 1 0,0.2 z m 4,2.1 a 10,10 0 0 0 8,8 l 0,-4 4,0 0,4 a 10,10 0 0 0 8,-8 l -4,0 0,-4 4,0 a 10,10 0 0 0 -8,-8 l 0,4 -4,0 0,-4 a 10,10 0 0 0 -8,8 l 4,0 0,4 z" />
<path id="geo-linux-detailed" d="m 2,15.9 a 14,14 0 1 1 0,0.2 z m 3,2.1 a 11,11 0 0 0 9,9 l 1,-5 2,0 1,5 a 11,11 0 0 0 9,-9 l -5,-1 0,-2 5,-1 a 11,11 0 0 0 -9,-9 l -1,5 -2,0 -1,-5 a 11,11 0 0 0 -9,9 l 5,1 0,2 z" />
<path id="geo-osx" d="m 0,16 16,0 0,16 12,-28 z" />
@ -20,7 +21,6 @@
<path id="login-detailed" d="m 1,27 0,3.5 a 0.5,0.5 0 0 0 0.5,0.5 l 5,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1.5 1.5,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1.5 1,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1 1,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-2 2,0 2.5,-2.5 q 0.5,-0.5 1,0 l 1,1 c 0.5,0.5 1,0.5 1.5,-0.5 l 1,-2 a 9,9 0 1 0 -8,-8 l -2,1 c -1,0.5 -1,1 -0.5,1.5 l 1.5,1.5 q 0.5,0.5 0,1 z m 21,-19.1 a 2,2 0 1 1 0,0.2 z" />
<path id="microphone" d="m 8,14 0,4 a 8,8 0 0 0 6,7.7 l 0,2.3 -2,0 a 2,2 0 0 0 -2,2 l 12,0 a 2,2 0 0 0 -2,-2 l -2,0 0,-2.3 a 8,8 0 0 0 6,-7.7 l 0,-4 -2,0 0,4 a 6,6 0 0 1 -12,0 l 0,-4 z m 4,4 a 4,4 0 0 0 8,0 l 0,-12 a 4,4 0 0 0 -8,0 z" />
<path id="microphone-detailed" d="m 8,18 a 8,8 0 0 0 6,7.7 l 0,2.3 -1,0 a 3,2 0 0 0 -3,2 l 12,0 a 3,2 0 0 0 -3,-2 l -1,0 0,-2.3 a 8,8 0 0 0 6,-7.7 l 0,-4 a 1,1 0 0 0 -2,0 l 0,4 a 6,6 0 0 1 -12,0 l 0,-4 a 1,1 0 0 0 -2,0 z m 4,0 a 4,4 0 0 0 8,0 l 0,-12 a 4,4 0 0 0 -8,0 z" />
<path id="pointer" d="m 8,24 6,-5 5,10 4,-2 -5,-10 7,-1 -17,-14 z" />
<path id="pointerLock" d="m 8,24 6,-5 5,10 4,-2 -5,-10 7,-1 -17,-14 z" />
<path id="screen" d="m 2,18 a 2,2 0 0 0 2,2 l 2,0 0,-6 a 4,4 0 0 1 4,-4 l 14,0 0,-6 a 2,2 0 0 0 -2,-2 l -18,0 a 2,2 0 0 0 -2,2 z m 6,10 a 2,2 0 0 0 2,2 l 18,0 a 2,2 0 0 0 2,-2 l 0,-14 a 2,2 0 0 0 -2,-2 l -18,0 a 2,2 0 0 0 -2,2 z" />
<path id="web-notifications" d="m 2,20 a 4,4 0 0 0 4,4 l 13,0 7,7 0,-7 a 4,4 0 0 0 4,-4 l 0,-12 a 4,4 0 0 0 -4,-4 l -20,0 a 4,4 0 0 0 -4,4 z m 5,-2 a 1,1 0 1 1 0,-2 l 10,0 a 1,1 0 1 1 0,2 z m 0,-4 a 1,1 0 1 1 0,-2 l 14,0 a 1,1 0 1 1 0,2 z m 0,-4 a 1,1 0 1 1 0,-2 l 18,0 a 1,1 0 1 1 0,2 z" />
</svg>

До

Ширина:  |  Высота:  |  Размер: 3.4 KiB

После

Ширина:  |  Высота:  |  Размер: 3.4 KiB

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

@ -80,9 +80,9 @@
.indexedDB-icon,
.login-icon,
.microphone-icon,
.pointer-icon,
.pointerLock-icon,
.screen-icon,
.web-notifications-icon,
.desktop-notification-icon,
.popup-notification-icon[popupid="geolocation"],
.popup-notification-icon[popupid="indexedDB-permissions-prompt"],
.popup-notification-icon[popupid="password"],
@ -106,6 +106,11 @@
fill: #fea01b;
}
.popup-notification-icon[popupid="web-notifications"],
.desktop-notification-icon {
list-style-image: url(chrome://browser/skin/glyphs.svg#desktop-notification);
}
.geo-icon {
%ifdef XP_MACOSX
list-style-image: url(chrome://browser/skin/glyphs.svg#geo-osx);
@ -169,14 +174,9 @@
list-style-image: url(chrome://browser/skin/glyphs.svg#screen);
}
.popup-notification-icon[popupid="web-notifications"],
.web-notifications-icon {
list-style-image: url(chrome://browser/skin/glyphs.svg#web-notifications);
}
.popup-notification-icon[popupid="pointerLock"],
.pointer-icon {
list-style-image: url(chrome://browser/skin/glyphs.svg#pointer);
.pointerLock-icon {
list-style-image: url(chrome://browser/skin/glyphs.svg#pointerLock);
}
/* EME */

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

@ -84,20 +84,28 @@ indentLess.commandkey=[
# DO NOT translate this key without proper synchronization with toolbox.dtd.
indentMore.commandkey=]
# LOCALIZATION NOTE (moveLineUp.commandkey): This is the key to use to move
# the selected lines up.
# LOCALIZATION NOTE (moveLineUp.commandkey): This is the combination of keys
# used to move the current line up.
# Do not localize "Alt", "Up", or change the format of the string. These are key
# identifiers, not messages displayed to the user.
moveLineUp.commandkey=Alt-Up
# LOCALIZATION NOTE (moveLineDown.commandkey): This is the key to use to move
# the selected lines down.
# LOCALIZATION NOTE (moveLineDown.commandkey): This is the combination of keys
# used to move the current line up.
# Do not localize "Alt", "Down", or change the format of the string. These are
# key identifiers, not messages displayed to the user.
moveLineDown.commandkey=Alt-Down
# LOCALIZATION NOTE (autocomplete.commandkey): This is the key to use
# in conjunction with Ctrl for autocompletion.
# LOCALIZATION NOTE (autocompletion.commandkey): This is the key, used with
# Ctrl, for code autocompletion.
# Do not localize "Space", it's the key identifier, not a message displayed to
# the user.
autocompletion.commandkey=Space
# LOCALIZATION NOTE (showInformation2.commandkey): This is the key to use to
# show more information, like type inference.
# LOCALIZATION NOTE (showInformation2.commandkey): This is the combination of
# keys used to display more information, like type inference.
# Do not localize "Shift", "Ctrl", "Space", or change the format of the string.
# These are key identifiers, not messages displayed to the user.
showInformation2.commandkey=Shift-Ctrl-Space
# LOCALIZATION NOTE (find.commandkey): This is the key to use in

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

@ -24,7 +24,7 @@ const { updateTouchSimulationEnabled } = require("./actions/touch-simulation");
const DeviceModal = createFactory(require("./components/device-modal"));
const GlobalToolbar = createFactory(require("./components/global-toolbar"));
const Viewports = createFactory(require("./components/viewports"));
const { updateDeviceList } = require("./devices");
const { updatePreferredDevices } = require("./devices");
const Types = require("./types");
let App = createClass({
@ -55,7 +55,7 @@ let App = createClass({
},
onDeviceListUpdate(devices) {
updateDeviceList(devices);
updatePreferredDevices(devices);
},
onExit() {

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

@ -53,21 +53,28 @@ module.exports = createClass({
onUpdateDeviceModalOpen,
} = this.props;
let displayedDeviceList = [];
let preferredDevices = {
"added": new Set(),
"removed": new Set(),
};
for (let type of devices.types) {
for (let device of devices[type]) {
if (this.state[device.name] != device.displayed) {
onUpdateDeviceDisplayed(device, type, this.state[device.name]);
let newState = this.state[device.name];
if (device.featured && !newState) {
preferredDevices.removed.add(device.name);
} else if (!device.featured && newState) {
preferredDevices.added.add(device.name);
}
if (this.state[device.name]) {
displayedDeviceList.push(device.name);
if (this.state[device.name] != device.displayed) {
onUpdateDeviceDisplayed(device, type, this.state[device.name]);
}
}
}
onDeviceListUpdate(displayedDeviceList);
onDeviceListUpdate(preferredDevices);
onUpdateDeviceModalOpen(false);
},

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

@ -18,7 +18,7 @@ const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList";
* Action dispatch function
*/
let initDevices = Task.async(function* (dispatch) {
let deviceList = loadDeviceList();
let preferredDevices = loadPreferredDevices();
let devices = yield GetDevices();
for (let type of devices.TYPES) {
@ -29,54 +29,60 @@ let initDevices = Task.async(function* (dispatch) {
}
let newDevice = Object.assign({}, device, {
displayed: deviceList.has(device.name) ?
true :
!!device.featured,
displayed: preferredDevices.added.has(device.name) ||
(device.featured && !(preferredDevices.removed.has(device.name))),
});
if (newDevice.displayed) {
deviceList.add(newDevice.name);
}
dispatch(addDevice(newDevice, type));
}
}
updateDeviceList(deviceList);
});
/**
* Returns a set containing the user preference of displayed devices.
* Returns an object containing the user preference of displayed devices.
*
* @return {Set} containing the device names that are to be displayed in the
* device catalog.
* @return {Object} containing two Sets:
* - added: Names of the devices that were explicitly enabled by the user
* - removed: Names of the devices that were explicitly removed by the user
*/
function loadDeviceList() {
let deviceList = new Set();
function loadPreferredDevices() {
let preferredDevices = {
"added": new Set(),
"removed": new Set(),
};
if (Services.prefs.prefHasUserValue(DISPLAYED_DEVICES_PREF)) {
try {
let savedList = Services.prefs.getCharPref(DISPLAYED_DEVICES_PREF);
deviceList = new Set(JSON.parse(savedList));
let savedData = Services.prefs.getCharPref(DISPLAYED_DEVICES_PREF);
savedData = JSON.parse(savedData);
if (savedData.added && savedData.removed) {
preferredDevices.added = new Set(savedData.added);
preferredDevices.removed = new Set(savedData.removed);
}
} catch (e) {
console.error(e);
}
}
return deviceList;
return preferredDevices;
}
/**
* Update the displayed device list preference with the given device list.
*
* @param {Set} devices
* Set of device names that are displayed in the device catalog.
* @param {Object} containing two Sets:
* - added: Names of the devices that were explicitly enabled by the user
* - removed: Names of the devices that were explicitly removed by the user
*/
function updateDeviceList(devices) {
let listToSave = JSON.stringify(Array.from(devices));
Services.prefs.setCharPref(DISPLAYED_DEVICES_PREF, listToSave);
function updatePreferredDevices(devices) {
let devicesToSave = {
added: Array.from(devices.added),
removed: Array.from(devices.removed),
};
devicesToSave = JSON.stringify(devicesToSave);
Services.prefs.setCharPref(DISPLAYED_DEVICES_PREF, devicesToSave);
}
exports.initDevices = initDevices;
exports.loadDeviceList = loadDeviceList;
exports.updateDeviceList = updateDeviceList;
exports.loadPreferredDevices = loadPreferredDevices;
exports.updatePreferredDevices = updatePreferredDevices;

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

@ -17,7 +17,7 @@ addRDMTask(TEST_URL, function* ({ ui }) {
openDeviceModal(ui);
let deviceListBefore = loadDeviceList();
let preferredDevicesBefore = loadPreferredDevices();
info("Check the first unchecked device and exit the modal.");
let uncheckedCb = [...document.querySelectorAll(".device-input-checkbox")]
@ -30,9 +30,14 @@ addRDMTask(TEST_URL, function* ({ ui }) {
"The device modal is hidden on exit.");
info("Check that the device list remains unchanged after exitting.");
let deviceListAfter = loadDeviceList();
is(deviceListBefore.size, deviceListAfter.size,
"Got expected number of displayed devices.");
ok(!deviceListAfter.has(value),
value + " was not added to displayed device list.");
let preferredDevicesAfter = loadPreferredDevices();
is(preferredDevicesBefore.added.size, preferredDevicesAfter.added.size,
"Got expected number of added devices.");
is(preferredDevicesBefore.removed.size, preferredDevicesAfter.removed.size,
"Got expected number of removed devices.");
ok(!preferredDevicesAfter.removed.has(value),
value + " was not added to removed device list.");
});

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

@ -4,6 +4,19 @@ http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test submitting display device changes on the device modal
const { GetDevices, AddDevice } = require("devtools/client/shared/devices");
const addedDevice = {
"name": "Fake Phone RDM Test",
"width": 320,
"height": 570,
"pixelRatio": 1.5,
"userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
"touch": true,
"firefoxOS": false,
"os": "custom",
"featured": true,
};
const TEST_URL = "data:text/html;charset=utf-8,";
@ -21,15 +34,24 @@ addRDMTask(TEST_URL, function* ({ ui }) {
info("Checking displayed device checkboxes are checked in the device modal.");
let checkedCbs = [...document.querySelectorAll(".device-input-checkbox")]
.filter(cb => cb.checked);
let deviceList = loadDeviceList();
is(deviceList.size, checkedCbs.length,
let remoteList = yield GetDevices();
let featuredCount = remoteList.TYPES.reduce((total, type) => {
return total + remoteList[type].reduce((subtotal, device) => {
return subtotal + ((device.os != "fxos" && device.featured) ? 1 : 0);
}, 0);
}, 0);
is(featuredCount, checkedCbs.length,
"Got expected number of displayed devices.");
for (let cb of checkedCbs) {
ok(deviceList.has(cb.value), cb.value + " is correctly checked.");
ok(Object.keys(remoteList).filter(type => remoteList[type][cb.value]),
cb.value + " is correctly checked.");
}
// Tests where the user adds a non-featured device
info("Check the first unchecked device and submit new device list.");
let uncheckedCb = [...document.querySelectorAll(".device-input-checkbox")]
.filter(cb => !cb.checked)[0];
@ -40,13 +62,13 @@ addRDMTask(TEST_URL, function* ({ ui }) {
ok(modal.classList.contains("hidden"),
"The device modal is hidden on submit.");
info("Checking new device is added to the displayed device list.");
deviceList = loadDeviceList();
ok(deviceList.has(value), value + " added to displayed device list.");
info("Checking that the new device is added to the user preference list.");
let preferredDevices = loadPreferredDevices();
ok(preferredDevices.added.has(value), value + " in user added list.");
info("Checking new device is added to the device selector.");
let options = [...select.options];
is(options.length - 2, deviceList.size,
is(options.length - 2, featuredCount + 1,
"Got expected number of devices in device selector.");
ok(options.filter(o => o.value === value)[0],
value + " added to the device selector.");
@ -56,4 +78,66 @@ addRDMTask(TEST_URL, function* ({ ui }) {
ok([...document.querySelectorAll(".device-input-checkbox")]
.filter(cb => cb.checked && cb.value === value)[0],
value + " is checked in the device modal.");
// Tests where the user removes a featured device
info("Uncheck the first checked device different than the previous one");
let checkedCb = [...document.querySelectorAll(".device-input-checkbox")]
.filter(cb => cb.checked && cb.value != value)[0];
let checkedVal = checkedCb.value;
checkedCb.click();
submitButton.click();
info("Checking that the device is removed from the user preference list.");
preferredDevices = loadPreferredDevices();
ok(preferredDevices.removed.has(checkedVal), checkedVal + " in removed list");
info("Checking that the device is not in the device selector.");
options = [...select.options];
is(options.length - 2, featuredCount,
"Got expected number of devices in device selector.");
ok(!options.filter(o => o.value === checkedVal)[0],
checkedVal + " removed from the device selector.");
info("Reopen device modal and check device is correctly unchecked");
openDeviceModal(ui);
ok([...document.querySelectorAll(".device-input-checkbox")]
.filter(cb => !cb.checked && cb.value === checkedVal)[0],
checkedVal + " is unchecked in the device modal.");
// Let's add a dummy device to simulate featured flag changes for next test
AddDevice(addedDevice);
});
addRDMTask(TEST_URL, function* ({ ui }) {
let { store, document } = ui.toolWindow;
let select = document.querySelector(".viewport-device-selector");
// Wait until the viewport has been added
yield waitUntilState(store, state => state.viewports.length == 1);
openDeviceModal(ui);
let remoteList = yield GetDevices();
let featuredCount = remoteList.TYPES.reduce((total, type) => {
return total + remoteList[type].reduce((subtotal, device) => {
return subtotal + ((device.os != "fxos" && device.featured) ? 1 : 0);
}, 0);
}, 0);
let preferredDevices = loadPreferredDevices();
// Tests to prove that reloading the RDM didn't break our device list
info("Checking new featured device appears in the device selector.");
let options = [...select.options];
is(options.length - 2, featuredCount
- preferredDevices.removed.size + preferredDevices.added.size,
"Got expected number of devices in device selector.");
ok(options.filter(o => o.value === addedDevice.name)[0],
"dummy device added to the device selector.");
ok(options.filter(o => preferredDevices.added.has(o.value))[0],
"device added by user still in the device selector.");
ok(!options.filter(o => preferredDevices.removed.has(o.value))[0],
"device removed by user not in the device selector.");
});

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

@ -38,7 +38,7 @@ registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList");
});
const { ResponsiveUIManager } = require("resource://devtools/client/responsivedesign/responsivedesign.jsm");
const { loadDeviceList } = require("devtools/client/responsive.html/devices");
const { loadPreferredDevices } = require("devtools/client/responsive.html/devices");
const { getOwnerWindow } = require("sdk/tabs/utils");
const OPEN_DEVICE_MODAL_VALUE = "OPEN_DEVICE_MODAL";

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

@ -44,7 +44,7 @@
if (!evt.touches.length) {
div.style.transform = "none";
}
touchendTime = new Date().getTime();
touchendTime = performance.now();
updatePreviousEvent(evt);
}, true);
@ -69,7 +69,7 @@
div.addEventListener("mousedown", function(evt){
if (previousEvent === "touchend" && touchendTime !== 0) {
let now = new Date().getTime();
let now = performance.now();
div.dataset.isDelay = ((now - touchendTime) >= 300) ? true : false;
} else {
div.dataset.isDelay = false;

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

@ -8,6 +8,7 @@ support-files =
[test_notification_box_03.html]
[test_reps_date-time.html]
[test_reps_object-with-url.html]
[test_reps_stylesheet.html]
[test_reps_undefined.html]
[test_reps_window.html]
[test_frame_01.html]

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

@ -0,0 +1,52 @@
<!DOCTYPE HTML>
<html>
<!--
Test Stylesheet rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - Stylesheet</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { StyleSheet } = browserRequire("devtools/client/shared/components/reps/stylesheet");
let gripStub = {
"type": "object",
"class": "CSSStyleSheet",
"actor": "server1.conn2.obj1067",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "ObjectWithURL",
"url": "https://example.com/styles.css"
}
};
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, StyleSheet.rep, `Rep correctly selects ${StyleSheet.rep.displayName}`);
// Test rendering
const renderedComponent = renderComponent(StyleSheet.rep, { object: gripStub });
is(renderedComponent.textContent, "StyleSheet styles.css", "StyleSheet rep has expected text content");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

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

@ -8902,66 +8902,26 @@
"kind": "boolean",
"description": "Stores 1 every time the URL is copied or shared."
},
"LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1": {
"alert_emails": ["firefox-dev@mozilla.org", "dmose@mozilla.com"],
"expires_in_version": "50",
"kind": "enumerated",
"n_values": 8,
"releaseChannelCollection": "opt-out",
"description": "Connection length for bi-directionally connected media (0=SHORTER_THAN_10S, 1=BETWEEN_10S_AND_30S, 2=BETWEEN_30S_AND_5M, 3=MORE_THAN_5M)"
},
"LOOP_SHARING_ROOM_URL": {
"alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
"expires_in_version": "50",
"kind": "enumerated",
"n_values": 8,
"releaseChannelCollection": "opt-out",
"description": "Number of times a room URL is shared (0=COPY_FROM_PANEL, 1=COPY_FROM_CONVERSATION, 2=EMAIL_FROM_CALLFAILED, 3=EMAIL_FROM_CONVERSATION, 4=FACEBOOK_FROM_CONVERSATION, 5=EMAIL_FROM_PANEL)"
},
"LOOP_ROOM_CREATE": {
"alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
"expires_in_version": "50",
"expires_in_version": "54",
"kind": "enumerated",
"n_values": 4,
"releaseChannelCollection": "opt-out",
"description": "Number of times a room create action is performed (0=CREATE_SUCCESS, 1=CREATE_FAIL)"
},
"LOOP_ROOM_DELETE": {
"alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
"expires_in_version": "50",
"kind": "enumerated",
"n_values": 4,
"releaseChannelCollection": "opt-out",
"description": "Number of times a room delete action is performed (0=DELETE_SUCCESS, 2=DELETE_FAIL)"
},
"LOOP_ROOM_SESSION_WITHCHAT": {
"alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
"expires_in_version": "50",
"kind": "count",
"releaseChannelCollection": "opt-out",
"description": "Number of sessions where at least one chat message was exchanged"
},
"LOOP_COPY_PANEL_ACTIONS": {
"alert_emails": ["firefox-dev@mozilla.org", "edilee@mozilla.com"],
"expires_in_version": "51",
"expires_in_version": "54",
"kind": "enumerated",
"n_values": 5,
"releaseChannelCollection": "opt-out",
"bug_numbers": [1239965, 1259506],
"description": "Number of times each of the following copy panel actions are triggered: 0=SHOWN, 1=NO_AGAIN, 2=NO_NEVER, 3=YES_AGAIN, 4=YES_NEVER"
},
"LOOP_INFOBAR_ACTION_BUTTONS": {
"alert_emails": ["firefox-dev@mozilla.org", "mbanner@mozilla.com"],
"expires_in_version": "51",
"kind": "enumerated",
"n_values": 4,
"releaseChannelCollection": "opt-out",
"bug_numbers": [1245486],
"description": "Number times info bar buttons are clicked (0=PAUSED, 1=CREATED)"
},
"LOOP_ACTIVITY_COUNTER": {
"alert_emails": ["firefox-dev@mozilla.org", "mbanner@mozilla.com"],
"expires_in_version": "51",
"expires_in_version": "54",
"kind": "enumerated",
"n_values": 5,
"releaseChannelCollection": "opt-out",

85
toolkit/modules/Color.jsm Normal file
Просмотреть файл

@ -0,0 +1,85 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["Color"];
/**
* Color class, which describes a color.
* In the future, this object may be extended to allow for conversions between
* different color formats and notations, support transparency.
*
* @param {Number} r Red color component
* @param {Number} g Green color component
* @param {Number} b Blue color component
*/
function Color(r, g, b) {
this.r = r;
this.g = g;
this.b = b;
}
Color.prototype = {
/**
* Formula from W3C's WCAG 2.0 spec's relative luminance, section 1.4.1,
* http://www.w3.org/TR/WCAG20/.
*
* @return {Number} Relative luminance, represented as number between 0 and 1.
*/
get relativeLuminance() {
let colorArr = [this.r, this.b, this.g].map(color => {
color = parseInt(color, 10);
if (color <= 10)
return color / 255 / 12.92;
return Math.pow(((color / 255) + 0.055) / 1.055, 2.4);
});
return colorArr[0] * 0.2126 +
colorArr[1] * 0.7152 +
colorArr[2] * 0.0722;
},
/**
* @return {Boolean} TRUE if the color value can be considered bright.
*/
get isBright() {
// Note: this is a high enough value to be considered as 'bright', but was
// decided upon empirically.
return this.relativeLuminance > 0.7;
},
/**
* Get the contrast ratio between the current color and a second other color.
* A common use case is to express the difference between a foreground and a
* background color in numbers.
* Formula from W3C's WCAG 2.0 spec's contrast ratio, section 1.4.1,
* http://www.w3.org/TR/WCAG20/.
*
* @param {Color} otherColor Color instance to calculate the contrast with
* @return {Number} Contrast ratios can range from 1 to 21, commonly written
* as 1:1 to 21:1.
*/
contrastRatio(otherColor) {
if (!(otherColor instanceof Color))
throw new TypeError("The first argument should be an instance of Color");
let luminance = this.relativeLuminance;
let otherLuminance = otherColor.relativeLuminance;
return (Math.max(luminance, otherLuminance) + 0.05) /
(Math.min(luminance, otherLuminance) + 0.05);
},
/**
* Biased method to check if the contrast ratio between two colors is high
* enough to be discernable.
*
* @param {Color} otherColor Color instance to calculate the contrast with
* @return {Boolean}
*/
isContrastRatioAcceptable(otherColor) {
// Note: this is a high enough value to be considered as 'high contrast',
// but was decided upon empirically.
return this.contrastRatio(otherColor) > 3;
}
};

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

@ -10,17 +10,21 @@ const { interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Color", "resource://gre/modules/Color.jsm");
const kHighlightIterationSizeMax = 100;
const kModalHighlightRepaintFreqMs = 10;
const kModalHighlightPref = "findbar.modalHighlight";
const kFontPropsCSS = ["font-family", "font-kerning", "font-size", "font-size-adjust",
"font-stretch", "font-variant", "font-weight", "letter-spacing", "text-emphasis",
"text-orientation", "text-transform", "word-spacing"];
const kFontPropsCSS = ["color", "font-family", "font-kerning", "font-size",
"font-size-adjust", "font-stretch", "font-variant", "font-weight", "letter-spacing",
"text-emphasis", "text-orientation", "text-transform", "word-spacing"];
const kFontPropsCamelCase = kFontPropsCSS.map(prop => {
let parts = prop.split("-");
return parts.shift() + parts.map(part => part.charAt(0).toUpperCase() + part.slice(1)).join("");
});
const kRGBRE = /^rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*/i
// This uuid is used to prefix HTML element IDs and classNames in order to make
// them unique and hard to clash with IDs and classNames content authors come up
// with, since the stylesheet for modal highlighting is inserted as an agent-sheet
@ -70,10 +74,18 @@ const kModalStyle = `
z-index: 1;
}
.findbar-modalHighlight-outlineMask[brighttext] {
background: #fff;
}
.findbar-modalHighlight-rect {
background: #fff;
border: 1px solid #666;
position: absolute;
}
.findbar-modalHighlight-outlineMask[brighttext] > .findbar-modalHighlight-rect {
background: #000;
}`;
const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
@ -278,6 +290,7 @@ FinderHighlighter.prototype = {
window = window || this.finder._getWindow();
this._removeHighlightAllMask(window);
this._removeModalHighlightListeners(window);
delete this._brightText;
},
/**
@ -321,6 +334,12 @@ FinderHighlighter.prototype = {
let rect = foundRange.getBoundingClientRect();
let fontStyle = this._getRangeFontStyle(foundRange);
if (typeof this._brightText == "undefined") {
this._brightText = this._isColorBright(fontStyle.color);
}
// Text color in the outline is determined by our stylesheet.
delete fontStyle.color;
let anonNode = this.show(window);
@ -497,6 +516,20 @@ FinderHighlighter.prototype = {
return style.join(" ");
},
/**
* Checks whether a CSS RGB color value can be classified as being 'bright'.
*
* @param {String} cssColor RGB color value in the default format rgb[a](r,g,b)
* @return {Boolean}
*/
_isColorBright(cssColor) {
cssColor = cssColor.match(kRGBRE);
if (!cssColor || !cssColor.length)
return false;
cssColor.shift();
return new Color(...cssColor).isBright;
},
/**
* Add a range to the list of ranges to highlight on, or cut out of, the dimmed
* background.
@ -599,6 +632,8 @@ FinderHighlighter.prototype = {
maskNode.setAttribute("id", kMaskId);
maskNode.setAttribute("class", kMaskId);
maskNode.setAttribute("style", `width: ${width}px; height: ${height}px;`);
if (this._brightText)
maskNode.setAttribute("brighttext", "true");
// Create a DOM node for each rectangle representing the ranges we found.
let maskContent = [];

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

@ -310,7 +310,7 @@ RemoteFinderListener.prototype = {
break;
case "Finder:ModalHighlightChange":
this._finder.ModalHighlightChange(data.useModalHighlight);
this._finder.onModalHighlightChange(data.useModalHighlight);
break;
}
}

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

@ -32,6 +32,7 @@ EXTRA_JS_MODULES += [
'CertUtils.jsm',
'CharsetMenu.jsm',
'ClientID.jsm',
'Color.jsm',
'Console.jsm',
'debug.js',
'DeferredTask.jsm',

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

@ -27,7 +27,8 @@ support-files =
[browser_Finder.js]
[browser_Finder_hidden_textarea.js]
[browser_FinderHighlighter.js]
skip-if = debug
skip-if = debug || os = "linux"
support-files = file_FinderSample.html
[browser_Geometry.js]
[browser_InlineSpellChecker.js]
[browser_WebNavigation.js]

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

@ -7,6 +7,7 @@ Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/AppConstants.jsm");
const kPrefModalHighlight = "findbar.modalHighlight";
const kFixtureBaseURL = "https://example.com/browser/toolkit/modules/tests/browser/";
function promiseOpenFindbar(findbar) {
findbar.onFindCommand()
@ -34,7 +35,8 @@ function promiseFindResult(findbar, str = null) {
findbar.browser.finder.removeResultListener(listener);
resolve();
}
}
},
onMatchesCountResult: () => {}
};
findbar.browser.finder.addResultListener(listener);
});
@ -51,13 +53,13 @@ function promiseEnterStringIntoFindField(findbar, str) {
return promise;
}
function promiseTestHighlighterOutput(browser, word, expectedResult) {
return ContentTask.spawn(browser, { word, expectedResult }, function* ({ word, expectedResult }) {
function promiseTestHighlighterOutput(browser, word, expectedResult, extraTest = () => {}) {
return ContentTask.spawn(browser, { word, expectedResult, extraTest: extraTest.toSource() },
function* ({ word, expectedResult, extraTest }) {
let document = content.document;
return new Promise((resolve, reject) => {
let stubbed = [document.insertAnonymousContent,
document.removeAnonymousContent];
let stubbed = {};
let callCounts = {
insertCalls: [],
removeCalls: []
@ -67,18 +69,26 @@ function promiseTestHighlighterOutput(browser, word, expectedResult) {
// was called.
const kTimeoutMs = 1000;
// The initial timeout may wait for a while for results to come in.
let timeout = content.setTimeout(finish, kTimeoutMs * 4);
let timeout = content.setTimeout(() => finish(false, "Timeout"), kTimeoutMs * 5);
function finish(ok = true, message) {
function finish(ok = true, message = "finished with error") {
// Restore the functions we stubbed out.
document.insertAnonymousContent = stubbed[0];
document.removeAnonymousContent = stubbed[1];
document.insertAnonymousContent = stubbed.insert;
document.removeAnonymousContent = stubbed.remove;
stubbed = {};
content.clearTimeout(timeout);
Assert.equal(callCounts.insertCalls.length, expectedResult.insertCalls,
`Insert calls should match for '${word}'.`);
Assert.equal(callCounts.removeCalls.length, expectedResult.removeCalls,
`Remove calls should match for '${word}'.`);
if (expectedResult.rectCount !== 0)
Assert.ok(ok, message);
Assert.greaterOrEqual(callCounts.insertCalls.length, expectedResult.insertCalls[0],
`Min. insert calls should match for '${word}'.`);
Assert.lessOrEqual(callCounts.insertCalls.length, expectedResult.insertCalls[1],
`Max. insert calls should match for '${word}'.`);
Assert.greaterOrEqual(callCounts.removeCalls.length, expectedResult.removeCalls[0],
`Min. remove calls should match for '${word}'.`);
Assert.lessOrEqual(callCounts.removeCalls.length, expectedResult.removeCalls[1],
`Max. remove calls should match for '${word}'.`);
// We reached the amount of calls we expected, so now we can check
// the amount of rects.
@ -86,23 +96,29 @@ function promiseTestHighlighterOutput(browser, word, expectedResult) {
if (!lastMaskNode && expectedResult.rectCount !== 0) {
Assert.ok(false, `No mask node found, but expected ${expectedResult.rectCount} rects.`);
}
if (lastMaskNode) {
Assert.equal(lastMaskNode.getElementsByTagName("div").length,
expectedResult.rectCount, `Amount of inserted rects should match for '${word}'.`);
}
// Allow more specific assertions to be tested in `extraTest`.
extraTest = eval(extraTest);
extraTest(lastMaskNode);
resolve();
}
// Create a function that will stub the original version and collects
// the arguments so we can check the results later.
function stub(which) {
stubbed[which] = document[which + "AnonymousContent"];
let prop = which + "Calls";
return function(node) {
callCounts[prop].push(node);
content.clearTimeout(timeout);
timeout = content.setTimeout(finish, kTimeoutMs);
return node;
return stubbed[which].call(document, node);
};
}
document.insertAnonymousContent = stub("insert");
@ -121,35 +137,29 @@ add_task(function* setup() {
// Test the results of modal highlighting, which is on by default.
add_task(function* testModalResults() {
let tests = new Map([
["mo", {
rectCount: 4,
insertCalls: 2,
removeCalls: AppConstants.platform == "linux" ? 1 : 2
["Roland", {
rectCount: 2,
insertCalls: [2, 4],
removeCalls: [1, 2]
}],
["m", {
rectCount: 8,
insertCalls: 1,
removeCalls: 1
["ro", {
rectCount: 41,
insertCalls: [1, 2],
removeCalls: [1, 2]
}],
["new", {
rectCount: 1,
insertCalls: 1,
removeCalls: 1
rectCount: 2,
insertCalls: [1, 2],
removeCalls: [1, 2]
}],
["o", {
rectCount: 1217,
insertCalls: 1,
removeCalls: 1
rectCount: 492,
insertCalls: [1, 2],
removeCalls: [1, 2]
}]
]);
yield BrowserTestUtils.withNewTab("about:mozilla", function* (browser) {
// We're inserting 1200 additional o's at the end of the document.
yield ContentTask.spawn(browser, null, function* () {
let document = content.document;
document.getElementsByTagName("section")[0].innerHTML += "<p>" +
(new Array(1200).join(" o ")) + "</p>";
});
let url = kFixtureBaseURL + "file_FinderSample.html";
yield BrowserTestUtils.withNewTab(url, function* (browser) {
let findbar = gBrowser.getFindBar();
for (let [word, expectedResult] of tests) {
@ -160,7 +170,7 @@ add_task(function* testModalResults() {
yield promiseEnterStringIntoFindField(findbar, word);
yield promise;
findbar.close();
findbar.close(true);
}
});
});
@ -168,17 +178,18 @@ add_task(function* testModalResults() {
// Test if runtime switching of highlight modes between modal and non-modal works
// as expected.
add_task(function* testModalSwitching() {
yield BrowserTestUtils.withNewTab("about:mozilla", function* (browser) {
let url = kFixtureBaseURL + "file_FinderSample.html";
yield BrowserTestUtils.withNewTab(url, function* (browser) {
let findbar = gBrowser.getFindBar();
yield promiseOpenFindbar(findbar);
Assert.ok(!findbar.hidden, "Findbar should be open now.");
let word = "mo";
let word = "Roland";
let expectedResult = {
rectCount: 4,
insertCalls: 2,
removeCalls: AppConstants.platform == "linux" ? 1 : 2
rectCount: 2,
insertCalls: [2, 4],
removeCalls: [1, 2]
};
let promise = promiseTestHighlighterOutput(browser, word, expectedResult);
yield promiseEnterStringIntoFindField(findbar, word);
@ -188,12 +199,74 @@ add_task(function* testModalSwitching() {
expectedResult = {
rectCount: 0,
insertCalls: 0,
removeCalls: 0
insertCalls: [0, 0],
removeCalls: [0, 0]
};
promise = promiseTestHighlighterOutput(browser, word, expectedResult);
findbar.clear();
yield promiseEnterStringIntoFindField(findbar, word);
yield promise;
findbar.close(true);
});
yield SpecialPowers.pushPrefEnv({ "set": [[ kPrefModalHighlight, true ]] });
});
// Test if highlighting a dark page is detected properly.
add_task(function* testDarkPageDetection() {
let url = kFixtureBaseURL + "file_FinderSample.html";
yield BrowserTestUtils.withNewTab(url, function* (browser) {
let findbar = gBrowser.getFindBar();
yield promiseOpenFindbar(findbar);
let word = "Roland";
let expectedResult = {
rectCount: 2,
insertCalls: [2, 4],
removeCalls: [1, 2]
};
let promise = promiseTestHighlighterOutput(browser, word, expectedResult, function(node) {
Assert.ok(!node.hasAttribute("brighttext"), "White HTML page shouldn't have 'brighttext' set");
});
yield promiseEnterStringIntoFindField(findbar, word);
yield promise;
findbar.close(true);
});
yield BrowserTestUtils.withNewTab(url, function* (browser) {
let findbar = gBrowser.getFindBar();
yield promiseOpenFindbar(findbar);
let word = "Roland";
let expectedResult = {
rectCount: 2,
insertCalls: [2, 4],
removeCalls: [1, 2]
};
yield ContentTask.spawn(browser, null, function* () {
let dwu = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let uri = "data:text/css;charset=utf-8," + encodeURIComponent(`
body {
background: maroon radial-gradient(circle, #a01010 0%, #800000 80%) center center / cover no-repeat;
color: white;
}`);
try {
dwu.loadSheetUsingURIString(uri, dwu.USER_SHEET);
} catch (e) {}
});
let promise = promiseTestHighlighterOutput(browser, word, expectedResult, node => {
Assert.ok(node.hasAttribute("brighttext"), "Dark HTML page should have 'brighttext' set");
});
yield promiseEnterStringIntoFindField(findbar, word);
yield promise;
findbar.close(true);
});
});

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

@ -0,0 +1,824 @@
<!DOCTYPE html>
<html>
<head>
<title>Childe Roland</title>
</head>
<body>
<h1>"Childe Roland to the Dark Tower Came"</h1><h5>Robert Browning</h5>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>I.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>My first thought was, he lied in every word,
<dl>
<dd>That hoary cripple, with malicious eye</dd>
<dd>Askance to watch the working of his lie</dd>
</dl>
</dd>
<dd>On mine, and mouth scarce able to afford</dd>
<dd>Suppression of the glee that pursed and scored
<dl>
<dd>Its edge, at one more victim gained thereby.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>II.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>What else should he be set for, with his staff?
<dl>
<dd>What, save to waylay with his lies, ensnare</dd>
<dd>All travellers who might find him posted there,</dd>
</dl>
</dd>
<dd>And ask the road? I guessed what skull-like laugh</dd>
<dd>Would break, what crutch 'gin write my epitaph
<dl>
<dd>For pastime in the dusty thoroughfare,</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>III.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>If at his counsel I should turn aside
<dl>
<dd>Into that ominous tract which, all agree,</dd>
<dd>Hides the Dark Tower. Yet acquiescingly</dd>
</dl>
</dd>
<dd>I did turn as he pointed: neither pride</dd>
<dd>Nor hope rekindling at the end descried,
<dl>
<dd>So much as gladness that some end might be.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>IV.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>For, what with my whole world-wide wandering,
<dl>
<dd>What with my search drawn out thro' years, my hope</dd>
<dd>Dwindled into a ghost not fit to cope</dd>
</dl>
</dd>
<dd>With that obstreperous joy success would bring,</dd>
<dd>I hardly tried now to rebuke the spring
<dl>
<dd>My heart made, finding failure in its scope.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>V.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>As when a sick man very near to death
<dl>
<dd>Seems dead indeed, and feels begin and end</dd>
<dd>The tears and takes the farewell of each friend,</dd>
</dl>
</dd>
<dd>And hears one bid the other go, draw breath</dd>
<dd>Freelier outside ("since all is o'er," he saith,
<dl>
<dd>"And the blow fallen no grieving can amend;")</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>VI.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>While some discuss if near the other graves
<dl>
<dd>Be room enough for this, and when a day</dd>
<dd>Suits best for carrying the corpse away,</dd>
</dl>
</dd>
<dd>With care about the banners, scarves and staves:</dd>
<dd>And still the man hears all, and only craves
<dl>
<dd>He may not shame such tender love and stay.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>VII.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>Thus, I had so long suffered in this quest,
<dl>
<dd>Heard failure prophesied so oft, been writ</dd>
<dd>So many times among "The Band" - to wit,</dd>
</dl>
</dd>
<dd>The knights who to the Dark Tower's search addressed</dd>
<dd>Their steps - that just to fail as they, seemed best,
<dl>
<dd>And all the doubt was now—should I be fit?</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>VIII.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>So, quiet as despair, I turned from him,
<dl>
<dd>That hateful cripple, out of his highway</dd>
<dd>Into the path he pointed. All the day</dd>
</dl>
</dd>
<dd>Had been a dreary one at best, and dim</dd>
<dd>Was settling to its close, yet shot one grim
<dl>
<dd>Red leer to see the plain catch its estray.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>IX.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>For mark! no sooner was I fairly found
<dl>
<dd>Pledged to the plain, after a pace or two,</dd>
<dd>Than, pausing to throw backward a last view</dd>
</dl>
</dd>
<dd>O'er the safe road, 'twas gone; grey plain all round:</dd>
<dd>Nothing but plain to the horizon's bound.
<dl>
<dd>I might go on; nought else remained to do.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>X.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>So, on I went. I think I never saw
<dl>
<dd>Such starved ignoble nature; nothing throve:</dd>
<dd>For flowers - as well expect a cedar grove!</dd>
</dl>
</dd>
<dd>But cockle, spurge, according to their law</dd>
<dd>Might propagate their kind, with none to awe,
<dl>
<dd>You'd think; a burr had been a treasure trove.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XI.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>No! penury, inertness and grimace,
<dl>
<dd>In some strange sort, were the land's portion. "See</dd>
<dd>Or shut your eyes," said Nature peevishly,</dd>
</dl>
</dd>
<dd>"It nothing skills: I cannot help my case:</dd>
<dd>'Tis the Last Judgment's fire must cure this place,
<dl>
<dd>Calcine its clods and set my prisoners free."</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XII.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>If there pushed any ragged thistle-stalk
<dl>
<dd>Above its mates, the head was chopped; the bents</dd>
<dd>Were jealous else. What made those holes and rents</dd>
</dl>
</dd>
<dd>In the dock's harsh swarth leaves, bruised as to baulk</dd>
<dd>All hope of greenness? 'tis a brute must walk
<dl>
<dd>Pashing their life out, with a brute's intents.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XIII.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>As for the grass, it grew as scant as hair
<dl>
<dd>In leprosy; thin dry blades pricked the mud</dd>
<dd>Which underneath looked kneaded up with blood.</dd>
</dl>
</dd>
<dd>One stiff blind horse, his every bone a-stare,</dd>
<dd>Stood stupefied, however he came there:
<dl>
<dd>Thrust out past service from the devil's stud!</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XIV.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>Alive? he might be dead for aught I know,
<dl>
<dd>With that red gaunt and colloped neck a-strain,</dd>
<dd>And shut eyes underneath the rusty mane;</dd>
</dl>
</dd>
<dd>Seldom went such grotesqueness with such woe;</dd>
<dd>I never saw a brute I hated so;
<dl>
<dd>He must be wicked to deserve such pain.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XV.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>I shut my eyes and turned them on my heart.
<dl>
<dd>As a man calls for wine before he fights,</dd>
<dd>I asked one draught of earlier, happier sights,</dd>
</dl>
</dd>
<dd>Ere fitly I could hope to play my part.</dd>
<dd>Think first, fight afterwards - the soldier's art:
<dl>
<dd>One taste of the old time sets all to rights.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XVI.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>Not it! I fancied Cuthbert's reddening face
<dl>
<dd>Beneath its garniture of curly gold,</dd>
<dd>Dear fellow, till I almost felt him fold</dd>
</dl>
</dd>
<dd>An arm in mine to fix me to the place</dd>
<dd>That way he used. Alas, one night's disgrace!
<dl>
<dd>Out went my heart's new fire and left it cold.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XVII.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>Giles then, the soul of honour - there he stands
<dl>
<dd>Frank as ten years ago when knighted first.</dd>
<dd>What honest men should dare (he said) he durst.</dd>
</dl>
</dd>
<dd>Good - but the scene shifts - faugh! what hangman hands</dd>
<dd>Pin to his breast a parchment? His own bands
<dl>
<dd>Read it. Poor traitor, spit upon and curst!</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XVIII.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>Better this present than a past like that;
<dl>
<dd>Back therefore to my darkening path again!</dd>
<dd>No sound, no sight as far as eye could strain.</dd>
</dl>
</dd>
<dd>Will the night send a howlet or a bat?</dd>
<dd>I asked: when something on the dismal flat
<dl>
<dd>Came to arrest my thoughts and change their train.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XIX.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>A sudden little river crossed my path
<dl>
<dd>As unexpected as a serpent comes.</dd>
<dd>No sluggish tide congenial to the glooms;</dd>
</dl>
</dd>
<dd>This, as it frothed by, might have been a bath</dd>
<dd>For the fiend's glowing hoof - to see the wrath
<dl>
<dd>Of its black eddy bespate with flakes and spumes.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XX.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>So petty yet so spiteful! All along
<dl>
<dd>Low scrubby alders kneeled down over it;</dd>
<dd>Drenched willows flung them headlong in a fit</dd>
</dl>
</dd>
<dd>Of mute despair, a suicidal throng:</dd>
<dd>The river which had done them all the wrong,
<dl>
<dd>Whate'er that was, rolled by, deterred no whit.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XXI.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>Which, while I forded, - good saints, how I feared
<dl>
<dd>To set my foot upon a dead man's cheek,</dd>
<dd>Each step, or feel the spear I thrust to seek</dd>
</dl>
</dd>
<dd>For hollows, tangled in his hair or beard!</dd>
<dd>—It may have been a water-rat I speared,
<dl>
<dd>But, ugh! it sounded like a baby's shriek.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XXII.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>Glad was I when I reached the other bank.
<dl>
<dd>Now for a better country. Vain presage!</dd>
<dd>Who were the strugglers, what war did they wage,</dd>
</dl>
</dd>
<dd>Whose savage trample thus could pad the dank</dd>
<dd>Soil to a plash? Toads in a poisoned tank,
<dl>
<dd>Or wild cats in a red-hot iron cage—</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XXIII.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>The fight must so have seemed in that fell cirque.
<dl>
<dd>What penned them there, with all the plain to choose?</dd>
<dd>No foot-print leading to that horrid mews,</dd>
</dl>
</dd>
<dd>None out of it. Mad brewage set to work</dd>
<dd>Their brains, no doubt, like galley-slaves the Turk
<dl>
<dd>Pits for his pastime, Christians against Jews.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XXIV.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>And more than that - a furlong on - why, there!
<dl>
<dd>What bad use was that engine for, that wheel,</dd>
<dd>Or brake, not wheel - that harrow fit to reel</dd>
</dl>
</dd>
<dd>Men's bodies out like silk? with all the air</dd>
<dd>Of Tophet's tool, on earth left unaware,
<dl>
<dd>Or brought to sharpen its rusty teeth of steel.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XXV.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>Then came a bit of stubbed ground, once a wood,
<dl>
<dd>Next a marsh, it would seem, and now mere earth</dd>
<dd>Desperate and done with; (so a fool finds mirth,</dd>
</dl>
</dd>
<dd>Makes a thing and then mars it, till his mood</dd>
<dd>Changes and off he goes!) within a rood—
<dl>
<dd>Bog, clay and rubble, sand and stark black dearth.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XXVI.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>Now blotches rankling, coloured gay and grim,
<dl>
<dd>Now patches where some leanness of the soil's</dd>
<dd>Broke into moss or substances like boils;</dd>
</dl>
</dd>
<dd>Then came some palsied oak, a cleft in him</dd>
<dd>Like a distorted mouth that splits its rim
<dl>
<dd>Gaping at death, and dies while it recoils.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XXVII.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>And just as far as ever from the end!
<dl>
<dd>Nought in the distance but the evening, nought</dd>
<dd>To point my footstep further! At the thought,</dd>
</dl>
</dd>
<dd>A great black bird, Apollyon's bosom-friend,</dd>
<dd>Sailed past, nor beat his wide wing dragon-penned
<dl>
<dd>That brushed my cap—perchance the guide I sought.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XXVIII.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>For, looking up, aware I somehow grew,
<dl>
<dd>'Spite of the dusk, the plain had given place</dd>
<dd>All round to mountains - with such name to grace</dd>
</dl>
</dd>
<dd>Mere ugly heights and heaps now stolen in view.</dd>
<dd>How thus they had surprised me, - solve it, you!
<dl>
<dd>How to get from them was no clearer case.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XXIX.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>Yet half I seemed to recognise some trick
<dl>
<dd>Of mischief happened to me, God knows when—</dd>
<dd>In a bad dream perhaps. Here ended, then,</dd>
</dl>
</dd>
<dd>Progress this way. When, in the very nick</dd>
<dd>Of giving up, one time more, came a click
<dl>
<dd>As when a trap shuts - you're inside the den!</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XXX.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>Burningly it came on me all at once,
<dl>
<dd>This was the place! those two hills on the right,</dd>
<dd>Crouched like two bulls locked horn in horn in fight;</dd>
</dl>
</dd>
<dd>While to the left, a tall scalped mountain... Dunce,</dd>
<dd>Dotard, a-dozing at the very nonce,
<dl>
<dd>After a life spent training for the sight!</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XXXI.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>What in the midst lay but the Tower itself?
<dl>
<dd>The round squat turret, blind as the fool's heart</dd>
<dd>Built of brown stone, without a counterpart</dd>
</dl>
</dd>
<dd>In the whole world. The tempest's mocking elf</dd>
<dd>Points to the shipman thus the unseen shelf
<dl>
<dd>He strikes on, only when the timbers start.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XXXII.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>Not see? because of night perhaps? - why, day
<dl>
<dd>Came back again for that! before it left,</dd>
<dd>The dying sunset kindled through a cleft:</dd>
</dl>
</dd>
<dd>The hills, like giants at a hunting, lay</dd>
<dd>Chin upon hand, to see the game at bay,—
<dl>
<dd>"Now stab and end the creature - to the heft!"</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XXXIII.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>Not hear? when noise was everywhere! it tolled
<dl>
<dd>Increasing like a bell. Names in my ears</dd>
<dd>Of all the lost adventurers my peers,—</dd>
</dl>
</dd>
<dd>How such a one was strong, and such was bold,</dd>
<dd>And such was fortunate, yet each of old
<dl>
<dd>Lost, lost! one moment knelled the woe of years.</dd>
</dl>
</dd>
</dl>
<p><br /></p>
<dl>
<dd>
<dl>
<dd>
<dl>
<dd>XXXIV.</dd>
</dl>
</dd>
</dl>
</dd>
<dd>There they stood, ranged along the hillsides, met
<dl>
<dd>To view the last of me, a living frame</dd>
<dd>For one more picture! in a sheet of flame</dd>
</dl>
</dd>
<dd>I saw them and I knew them all. And yet</dd>
<dd>Dauntless the slug-horn to my lips I set,
<dl>
<dd>And blew "<i>Childe Roland to the Dark Tower came.</i>"</dd>
</dl>
</dd>
</dl>
</body>
</html>

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

@ -0,0 +1,53 @@
"use strict";
Components.utils.import("resource://gre/modules/Color.jsm");
function run_test() {
testRelativeLuminance();
testIsBright();
testContrastRatio();
testIsContrastRatioAcceptable();
}
function testRelativeLuminance() {
let c = new Color(0, 0, 0);
Assert.equal(c.relativeLuminance, 0, "Black is not illuminating");
c = new Color(255, 255, 255);
Assert.equal(c.relativeLuminance, 1, "White is quite the luminant one");
c = new Color(142, 42, 142);
Assert.equal(c.relativeLuminance, 0.25263952353998204,
"This purple is not that luminant");
}
function testIsBright() {
let c = new Color(0, 0, 0);
Assert.equal(c.isBright, 0, "Black is bright");
c = new Color(255, 255, 255);
Assert.equal(c.isBright, 1, "White is bright");
}
function testContrastRatio() {
let c = new Color(0, 0, 0);
let c2 = new Color(255, 255, 255);
Assert.equal(c.contrastRatio(c2), 21, "Contrast between black and white is max");
Assert.equal(c.contrastRatio(c), 1, "Contrast between equals is min");
let c3 = new Color(142, 42, 142);
Assert.equal(c.contrastRatio(c3), 6.05279047079964, "Contrast between black and purple");
Assert.equal(c2.contrastRatio(c3), 3.469474137806338, "Contrast between white and purple");
}
function testIsContrastRatioAcceptable() {
// Let's assert what browser.js is doing for window frames.
let c = new Color(...[55, 156, 152]);
let c2 = new Color(0, 0, 0);
Assert.equal(c.r, 55, "Reds should match");
Assert.equal(c.g, 156, "Greens should match");
Assert.equal(c.b, 152, "Blues should match");
Assert.ok(c.isContrastRatioAcceptable(c2), "The blue is high contrast enough");
c = new Color(...[35, 65, 100]);
Assert.ok(!c.isContrastRatioAcceptable(c2), "The blue is not high contrast enough");
}

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

@ -14,6 +14,7 @@ skip-if = toolkit == 'android'
[test_CanonicalJSON.js]
[test_client_id.js]
skip-if = toolkit == 'android'
[test_Color.js]
[test_DeferredTask.js]
skip-if = toolkit == 'android'
[test_FileUtils.js]

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

@ -2,6 +2,7 @@
%define loweredShadow 0 1px rgba(255, 255, 255, .4)
%define focusRingShadow 0 0 1px -moz-mac-focusring inset, 0 0 4px 1px -moz-mac-focusring, 0 0 1.5px 1px -moz-mac-focusring
%define yosemiteFocusRingShadow 0 0 0 0.5px -moz-mac-focusring inset, 0 0 0 2px -moz-mac-focusring;
%define roundButtonBorder 1px solid rgba(0,0,0,.35)
%define roundButtonBackground linear-gradient(#f6f6f6, #e9e9e9)

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

@ -51,6 +51,15 @@ static nscolor GetColorFromNSColor(NSColor* aColor)
(unsigned int)([deviceColor blueComponent] * 255.0));
}
static nscolor GetColorFromNSColorWithAlpha(NSColor* aColor, float alpha)
{
NSColor* deviceColor = [aColor colorUsingColorSpaceName:NSDeviceRGBColorSpace];
return NS_RGBA((unsigned int)([deviceColor redComponent] * 255.0),
(unsigned int)([deviceColor greenComponent] * 255.0),
(unsigned int)([deviceColor blueComponent] * 255.0),
(unsigned int)(alpha * 255.0));
}
nsresult
nsLookAndFeel::NativeGetColor(ColorID aID, nscolor &aColor)
{
@ -253,7 +262,7 @@ nsLookAndFeel::NativeGetColor(ColorID aID, nscolor &aColor)
}
break;
case eColorID__moz_mac_focusring:
aColor = GetColorFromNSColor([NSColor keyboardFocusIndicatorColor]);
aColor = GetColorFromNSColorWithAlpha([NSColor keyboardFocusIndicatorColor], 0.48);
break;
case eColorID__moz_mac_menushadow:
aColor = NS_RGB(0xA3,0xA3,0xA3);