Merge mozilla-central to autoland

This commit is contained in:
Carsten "Tomcat" Book 2016-07-30 16:49:41 +02:00
Родитель f08d7afd35 3c40ac6f0b
Коммит fc8e03f0d8
807 изменённых файлов: 9306 добавлений и 174218 удалений

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

@ -74,9 +74,6 @@ browser/extensions/pdfjs/**
browser/extensions/pocket/content/panels/js/vendor/**
browser/locales/**
# Ignore all of loop since it is imported from github and checked at source.
browser/extensions/loop/**
# devtools/ exclusions
devtools/client/canvasdebugger/**
devtools/client/commandline/**

3
.gitignore поставляемый
Просмотреть файл

@ -11,9 +11,6 @@ ID
*.pdb
*.egg-info
# Allow the id locale directory for loop ('ID' matches this normally)
!browser/extensions/loop/chrome/locale/id
# Vim swap files.
.*.sw[a-z]

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

@ -87,16 +87,6 @@ GRTAGS
GSYMS
GPATH
# Various items for Loop
^browser/components/loop/standalone/content/config\.js$
^browser/extensions/loop/.*/node_modules/
^browser/extensions/loop/.*\.module-cache
^browser/extensions/loop/test/coverage/desktop
^browser/extensions/loop/test/coverage/shared_standalone
^browser/extensions/loop/test/visual-regression/diff
^browser/extensions/loop/test/visual-regression/new
^browser/extensions/loop/test/visual-regression/refs
# Git clone directory for updating web-platform-tests
^testing/web-platform/sync/

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

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 1285541 - Clobber needed because this patch renames a file.
Bug 1287827 - Clobber needed because this patch removes files, second landing AND Bug 1272693 - Clobber required to rebuild NSS.

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

@ -41,10 +41,14 @@ xpcAccessibleHyperText::GetCharacterCount(int32_t* aCharacterCount)
NS_ENSURE_ARG_POINTER(aCharacterCount);
*aCharacterCount = 0;
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
*aCharacterCount = Intl()->CharacterCount();
if (mIntl.IsAccessible()) {
*aCharacterCount = Intl()->CharacterCount();
} else {
*aCharacterCount = mIntl.AsProxy()->CharacterCount();
}
return NS_OK;
}
@ -54,10 +58,16 @@ xpcAccessibleHyperText::GetText(int32_t aStartOffset, int32_t aEndOffset,
{
aText.Truncate();
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->TextSubstring(aStartOffset, aEndOffset, aText);
if (mIntl.IsAccessible()) {
Intl()->TextSubstring(aStartOffset, aEndOffset, aText);
} else {
nsString text;
mIntl.AsProxy()->TextSubstring(aStartOffset, aEndOffset, text);
aText = text;
}
return NS_OK;
}
@ -73,10 +83,18 @@ xpcAccessibleHyperText::GetTextBeforeOffset(int32_t aOffset,
*aStartOffset = *aEndOffset = 0;
aText.Truncate();
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->TextBeforeOffset(aOffset, aBoundaryType, aStartOffset, aEndOffset, aText);
if (mIntl.IsAccessible()) {
Intl()->TextBeforeOffset(aOffset, aBoundaryType, aStartOffset, aEndOffset,
aText);
} else {
nsString text;
mIntl.AsProxy()->GetTextBeforeOffset(aOffset, aBoundaryType, text,
aStartOffset, aEndOffset);
aText = text;
}
return NS_OK;
}
@ -91,10 +109,18 @@ xpcAccessibleHyperText::GetTextAtOffset(int32_t aOffset,
*aStartOffset = *aEndOffset = 0;
aText.Truncate();
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->TextAtOffset(aOffset, aBoundaryType, aStartOffset, aEndOffset, aText);
if (mIntl.IsAccessible()) {
Intl()->TextAtOffset(aOffset, aBoundaryType, aStartOffset, aEndOffset,
aText);
} else {
nsString text;
mIntl.AsProxy()->GetTextAtOffset(aOffset, aBoundaryType, text,
aStartOffset, aEndOffset);
aText = text;
}
return NS_OK;
}
@ -109,10 +135,18 @@ xpcAccessibleHyperText::GetTextAfterOffset(int32_t aOffset,
*aStartOffset = *aEndOffset = 0;
aText.Truncate();
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->TextAfterOffset(aOffset, aBoundaryType, aStartOffset, aEndOffset, aText);
if (mIntl.IsAccessible()) {
Intl()->TextAfterOffset(aOffset, aBoundaryType, aStartOffset, aEndOffset,
aText);
} else {
nsString text;
mIntl.AsProxy()->GetTextAfterOffset(aOffset, aBoundaryType, text,
aStartOffset, aEndOffset);
aText = text;
}
return NS_OK;
}
@ -123,10 +157,14 @@ xpcAccessibleHyperText::GetCharacterAtOffset(int32_t aOffset,
NS_ENSURE_ARG_POINTER(aCharacter);
*aCharacter = L'\0';
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
*aCharacter = Intl()->CharAt(aOffset);
if (mIntl.IsAccessible()) {
*aCharacter = Intl()->CharAt(aOffset);
} else {
*aCharacter = mIntl.AsProxy()->CharAt(aOffset);
}
return NS_OK;
}
@ -143,12 +181,24 @@ xpcAccessibleHyperText::GetTextAttributes(bool aIncludeDefAttrs,
*aStartOffset = *aEndOffset = 0;
*aAttributes = nullptr;
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
nsCOMPtr<nsIPersistentProperties> attrs =
Intl()->TextAttributes(aIncludeDefAttrs, aOffset, aStartOffset, aEndOffset);
attrs.swap(*aAttributes);
nsCOMPtr<nsIPersistentProperties> props;
if (mIntl.IsAccessible()) {
props = Intl()->TextAttributes(aIncludeDefAttrs, aOffset, aStartOffset,
aEndOffset);
} else {
AutoTArray<Attribute, 10> attrs;
mIntl.AsProxy()->TextAttributes(aIncludeDefAttrs, aOffset, &attrs,
aStartOffset, aEndOffset);
uint32_t attrCount = attrs.Length();
nsAutoString unused;
for (uint32_t i = 0; i < attrCount; i++) {
props->SetStringProperty(attrs[i].Name(), attrs[i].Value(), unused);
}
}
props.forget(aAttributes);
return NS_OK;
}
@ -159,11 +209,23 @@ xpcAccessibleHyperText::GetDefaultTextAttributes(nsIPersistentProperties** aAttr
NS_ENSURE_ARG_POINTER(aAttributes);
*aAttributes = nullptr;
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
nsCOMPtr<nsIPersistentProperties> attrs = Intl()->DefaultTextAttributes();
attrs.swap(*aAttributes);
nsCOMPtr<nsIPersistentProperties> props;
if (mIntl.IsAccessible()) {
props = Intl()->DefaultTextAttributes();
} else {
AutoTArray<Attribute, 10> attrs;
mIntl.AsProxy()->DefaultTextAttributes(&attrs);
uint32_t attrCount = attrs.Length();
nsAutoString unused;
for (uint32_t i = 0; i < attrCount; i++) {
props->SetStringProperty(attrs[i].Name(), attrs[i].Value(), unused);
}
}
props.forget(aAttributes);
return NS_OK;
}
@ -179,10 +241,15 @@ xpcAccessibleHyperText::GetCharacterExtents(int32_t aOffset,
NS_ENSURE_ARG_POINTER(aHeight);
*aX = *aY = *aWidth = *aHeight;
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
nsIntRect rect = Intl()->CharBounds(aOffset, aCoordType);
nsIntRect rect;
if (mIntl.IsAccessible()) {
rect = Intl()->CharBounds(aOffset, aCoordType);
} else {
rect = mIntl.AsProxy()->CharBounds(aOffset, aCoordType);
}
*aX = rect.x; *aY = rect.y;
*aWidth = rect.width; *aHeight = rect.height;
return NS_OK;
@ -200,10 +267,15 @@ xpcAccessibleHyperText::GetRangeExtents(int32_t aStartOffset, int32_t aEndOffset
NS_ENSURE_ARG_POINTER(aHeight);
*aX = *aY = *aWidth = *aHeight = 0;
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
nsIntRect rect = Intl()->TextBounds(aStartOffset, aEndOffset, aCoordType);
nsIntRect rect;
if (mIntl.IsAccessible()) {
rect = Intl()->TextBounds(aStartOffset, aEndOffset, aCoordType);
} else {
rect = mIntl.AsProxy()->TextBounds(aStartOffset, aEndOffset, aCoordType);
}
*aX = rect.x; *aY = rect.y;
*aWidth = rect.width; *aHeight = rect.height;
return NS_OK;
@ -216,10 +288,14 @@ xpcAccessibleHyperText::GetOffsetAtPoint(int32_t aX, int32_t aY,
NS_ENSURE_ARG_POINTER(aOffset);
*aOffset = -1;
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
*aOffset = Intl()->OffsetAtPoint(aX, aY, aCoordType);
if (mIntl.IsAccessible()) {
*aOffset = Intl()->OffsetAtPoint(aX, aY, aCoordType);
} else {
*aOffset = mIntl.AsProxy()->OffsetAtPoint(aX, aY, aCoordType);
}
return NS_OK;
}
@ -229,20 +305,28 @@ xpcAccessibleHyperText::GetCaretOffset(int32_t* aCaretOffset)
NS_ENSURE_ARG_POINTER(aCaretOffset);
*aCaretOffset = -1;
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
*aCaretOffset = Intl()->CaretOffset();
if (mIntl.IsAccessible()) {
*aCaretOffset = Intl()->CaretOffset();
} else {
*aCaretOffset = mIntl.AsProxy()->CaretOffset();
}
return NS_OK;
}
NS_IMETHODIMP
xpcAccessibleHyperText::SetCaretOffset(int32_t aCaretOffset)
{
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->SetCaretOffset(aCaretOffset);
if (mIntl.IsAccessible()) {
Intl()->SetCaretOffset(aCaretOffset);
} else {
mIntl.AsProxy()->SetCaretOffset(aCaretOffset);
}
return NS_OK;
}
@ -252,10 +336,14 @@ xpcAccessibleHyperText::GetSelectionCount(int32_t* aSelectionCount)
NS_ENSURE_ARG_POINTER(aSelectionCount);
*aSelectionCount = 0;
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
*aSelectionCount = Intl()->SelectionCount();
if (mIntl.IsAccessible()) {
*aSelectionCount = Intl()->SelectionCount();
} else {
*aSelectionCount = mIntl.AsProxy()->SelectionCount();
}
return NS_OK;
}
@ -268,13 +356,22 @@ xpcAccessibleHyperText::GetSelectionBounds(int32_t aSelectionNum,
NS_ENSURE_ARG_POINTER(aEndOffset);
*aStartOffset = *aEndOffset = 0;
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
if (aSelectionNum < 0 || aSelectionNum >= Intl()->SelectionCount())
if (aSelectionNum < 0)
return NS_ERROR_INVALID_ARG;
Intl()->SelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset);
if (mIntl.IsAccessible()) {
if (aSelectionNum >= Intl()->SelectionCount())
return NS_ERROR_INVALID_ARG;
Intl()->SelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset);
} else {
nsString unused;
mIntl.AsProxy()->SelectionBoundsAt(aSelectionNum, unused, aStartOffset,
aEndOffset);
}
return NS_OK;
}
@ -283,33 +380,51 @@ xpcAccessibleHyperText::SetSelectionBounds(int32_t aSelectionNum,
int32_t aStartOffset,
int32_t aEndOffset)
{
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
if (aSelectionNum < 0 ||
!Intl()->SetSelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset))
if (aSelectionNum < 0)
return NS_ERROR_INVALID_ARG;
if (mIntl.IsAccessible()) {
if (!Intl()->SetSelectionBoundsAt(aSelectionNum, aStartOffset,
aEndOffset)) {
return NS_ERROR_INVALID_ARG;
}
} else {
if (!mIntl.AsProxy()->SetSelectionBoundsAt(aSelectionNum, aStartOffset,
aEndOffset)) {
return NS_ERROR_INVALID_ARG;
}
}
return NS_OK;
}
NS_IMETHODIMP
xpcAccessibleHyperText::AddSelection(int32_t aStartOffset, int32_t aEndOffset)
{
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->AddToSelection(aStartOffset, aEndOffset);
if (mIntl.IsAccessible()) {
Intl()->AddToSelection(aStartOffset, aEndOffset);
} else {
mIntl.AsProxy()->AddToSelection(aStartOffset, aEndOffset);
}
return NS_OK;
}
NS_IMETHODIMP
xpcAccessibleHyperText::RemoveSelection(int32_t aSelectionNum)
{
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->RemoveFromSelection(aSelectionNum);
if (mIntl.IsAccessible()) {
Intl()->RemoveFromSelection(aSelectionNum);
} else {
mIntl.AsProxy()->RemoveFromSelection(aSelectionNum);
}
return NS_OK;
}
@ -318,10 +433,14 @@ xpcAccessibleHyperText::ScrollSubstringTo(int32_t aStartOffset,
int32_t aEndOffset,
uint32_t aScrollType)
{
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->ScrollSubstringTo(aStartOffset, aEndOffset, aScrollType);
if (mIntl.IsAccessible()) {
Intl()->ScrollSubstringTo(aStartOffset, aEndOffset, aScrollType);
} else {
mIntl.AsProxy()->ScrollSubstringTo(aStartOffset, aEndOffset, aScrollType);
}
return NS_OK;
}
@ -331,10 +450,16 @@ xpcAccessibleHyperText::ScrollSubstringToPoint(int32_t aStartOffset,
uint32_t aCoordinateType,
int32_t aX, int32_t aY)
{
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->ScrollSubstringToPoint(aStartOffset, aEndOffset, aCoordinateType, aX, aY);
if (mIntl.IsAccessible()) {
Intl()->ScrollSubstringToPoint(aStartOffset, aEndOffset, aCoordinateType,
aX, aY);
} else {
mIntl.AsProxy()->ScrollSubstringToPoint(aStartOffset, aEndOffset,
aCoordinateType, aX, aY);
}
return NS_OK;
}
@ -452,60 +577,86 @@ xpcAccessibleHyperText::GetRangeAtPoint(int32_t aX, int32_t aY,
NS_IMETHODIMP
xpcAccessibleHyperText::SetTextContents(const nsAString& aText)
{
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->ReplaceText(aText);
if (mIntl.IsAccessible()) {
Intl()->ReplaceText(aText);
} else {
nsString text(aText);
mIntl.AsProxy()->ReplaceText(text);
}
return NS_OK;
}
NS_IMETHODIMP
xpcAccessibleHyperText::InsertText(const nsAString& aText, int32_t aOffset)
{
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->InsertText(aText, aOffset);
if (mIntl.IsAccessible()) {
Intl()->InsertText(aText, aOffset);
} else {
nsString text(aText);
mIntl.AsProxy()->InsertText(text, aOffset);
}
return NS_OK;
}
NS_IMETHODIMP
xpcAccessibleHyperText::CopyText(int32_t aStartOffset, int32_t aEndOffset)
{
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->CopyText(aStartOffset, aEndOffset);
if (mIntl.IsAccessible()) {
Intl()->CopyText(aStartOffset, aEndOffset);
} else {
mIntl.AsProxy()->CopyText(aStartOffset, aEndOffset);
}
return NS_OK;
}
NS_IMETHODIMP
xpcAccessibleHyperText::CutText(int32_t aStartOffset, int32_t aEndOffset)
{
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->CutText(aStartOffset, aEndOffset);
if (mIntl.IsAccessible()) {
Intl()->CutText(aStartOffset, aEndOffset);
} else {
mIntl.AsProxy()->CutText(aStartOffset, aEndOffset);
}
return NS_OK;
}
NS_IMETHODIMP
xpcAccessibleHyperText::DeleteText(int32_t aStartOffset, int32_t aEndOffset)
{
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->DeleteText(aStartOffset, aEndOffset);
if (mIntl.IsAccessible()) {
Intl()->DeleteText(aStartOffset, aEndOffset);
} else {
mIntl.AsProxy()->DeleteText(aStartOffset, aEndOffset);
}
return NS_OK;
}
NS_IMETHODIMP
xpcAccessibleHyperText::PasteText(int32_t aOffset)
{
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
Intl()->PasteText(aOffset);
if (mIntl.IsAccessible()) {
Intl()->PasteText(aOffset);
} else {
mIntl.AsProxy()->PasteText(aOffset);
}
return NS_OK;
}
@ -518,10 +669,14 @@ xpcAccessibleHyperText::GetLinkCount(int32_t* aLinkCount)
NS_ENSURE_ARG_POINTER(aLinkCount);
*aLinkCount = 0;
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
*aLinkCount = Intl()->LinkCount();
if (mIntl.IsAccessible()) {
*aLinkCount = Intl()->LinkCount();
} else {
*aLinkCount = mIntl.AsProxy()->LinkCount();
}
return NS_OK;
}
@ -531,10 +686,14 @@ xpcAccessibleHyperText::GetLinkAt(int32_t aIndex, nsIAccessibleHyperLink** aLink
NS_ENSURE_ARG_POINTER(aLink);
*aLink = nullptr;
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
NS_IF_ADDREF(*aLink = ToXPC(Intl()->LinkAt(aIndex)));
if (mIntl.IsAccessible()) {
NS_IF_ADDREF(*aLink = ToXPC(Intl()->LinkAt(aIndex)));
} else {
NS_IF_ADDREF(*aLink = ToXPC(mIntl.AsProxy()->LinkAt(aIndex)));
}
return NS_OK;
}
@ -546,13 +705,20 @@ xpcAccessibleHyperText::GetLinkIndex(nsIAccessibleHyperLink* aLink,
NS_ENSURE_ARG_POINTER(aIndex);
*aIndex = -1;
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
nsCOMPtr<nsIAccessible> xpcLink(do_QueryInterface(aLink));
Accessible* link = xpcLink->ToInternalAccessible();
if (link)
*aIndex = Intl()->LinkIndexOf(link);
if (Accessible* accLink = xpcLink->ToInternalAccessible()) {
*aIndex = Intl()->LinkIndexOf(accLink);
} else {
xpcAccessibleHyperText* linkHyperText =
static_cast<xpcAccessibleHyperText*>(xpcLink.get());
ProxyAccessible* proxyLink = linkHyperText->mIntl.AsProxy();
if (proxyLink) {
*aIndex = mIntl.AsProxy()->LinkIndexOf(proxyLink);
}
}
return NS_OK;
}
@ -564,9 +730,13 @@ xpcAccessibleHyperText::GetLinkIndexAtOffset(int32_t aOffset,
NS_ENSURE_ARG_POINTER(aLinkIndex);
*aLinkIndex = -1; // API says this magic value means 'not found'
if (!Intl())
if (mIntl.IsNull())
return NS_ERROR_FAILURE;
*aLinkIndex = Intl()->LinkIndexAtOffset(aOffset);
if (mIntl.IsAccessible()) {
*aLinkIndex = Intl()->LinkIndexAtOffset(aOffset);
} else {
*aLinkIndex = mIntl.AsProxy()->LinkIndexAtOffset(aOffset);
}
return NS_OK;
}

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

@ -584,7 +584,9 @@ pref("browser.xul.error_pages.enabled", true);
pref("browser.xul.error_pages.expert_bad_cert", false);
// Enable captive portal detection.
#ifdef NIGHTLY_BUILD
pref("network.captive-portal-service.enabled", true);
#endif
// If true, network link events will change the value of navigator.onLine
pref("network.manage-offline-status", true);
@ -1475,4 +1477,4 @@ pref("print.use_simplify_page", true);
// Space separated list of URLS that are allowed to send objects (instead of
// only strings) through webchannels. This list is duplicated in mobile/android/app/mobile.js
pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://hello.firefox.com https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");
pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");

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

@ -295,18 +295,6 @@
noautofocus="true"
position="topcenter topright"/>
<panel id="loop-notification-panel"
class="loop-panel social-panel"
type="arrow"
hidden="true"
noautofocus="true"/>
<panel id="loop-panel"
class="loop-panel social-panel"
type="arrow"
orient="horizontal"
hidden="true"/>
<menupopup id="toolbar-context-menu"
onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('viewToolbarsMenuSeparator'));">
<menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
@ -410,7 +398,7 @@
hidden="true"/>
</menupopup>
<panel id="ctrlTab-panel" class="KUI-panel" hidden="true" norestorefocus="true" level="top">
<panel id="ctrlTab-panel" hidden="true" norestorefocus="true" level="top">
<hbox>
<button class="ctrlTab-preview" flex="1"/>
<button class="ctrlTab-preview" flex="1"/>

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

@ -20,16 +20,6 @@ let whitelist = [
// Tracked in bug 1004428.
{sourceName: /aboutaccounts\/(main|normalize)\.css$/i,
isFromDevTools: false},
// TokBox SDK assets, see bug 1032469.
{sourceName: /loop\/.*sdk-content\/.*\.css$/i,
isFromDevTools: false},
// Loop standalone client CSS uses placeholder cross browser pseudo-element
{sourceName: /loop\/.*\.css$/i,
errorMessage: /Unknown pseudo-class.*placeholder/i,
isFromDevTools: false},
{sourceName: /loop\/.*shared\/css\/common.css$/i,
errorMessage: /Unknown property .user-select./i,
isFromDevTools: false},
// Highlighter CSS uses a UA-only pseudo-class, see bug 985597.
{sourceName: /highlighters\.css$/i,
errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i,

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

@ -6,7 +6,5 @@ support-files =
[browser_devices_get_user_media.js]
skip-if = buildapp == 'mulet' || (os == "linux" && debug) # linux: bug 976544
[browser_devices_get_user_media_about_urls.js]
skip-if = e10s && debug
[browser_devices_get_user_media_anim.js]
[browser_devices_get_user_media_in_frame.js]

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

@ -1,219 +0,0 @@
/* 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/. */
const PREF_LOOP_CSP = "loop.CSP";
var gTab;
// Taken from dom/media/tests/mochitest/head.js
function isMacOSX10_6orOlder() {
var is106orOlder = false;
if (navigator.platform.indexOf("Mac") == 0) {
var version = Cc["@mozilla.org/system-info;1"]
.getService(Ci.nsIPropertyBag2)
.getProperty("version");
// the next line is correct: Mac OS 10.6 corresponds to Darwin version 10.x !
// Mac OS 10.7 is Darwin version 11.x. the |version| string we've got here
// is the Darwin version.
is106orOlder = (parseFloat(version) < 11.0);
}
return is106orOlder;
}
// Screensharing is disabled on older platforms (WinXP and Mac 10.6).
function isOldPlatform() {
const isWinXP = navigator.userAgent.indexOf("Windows NT 5.1") != -1;
if (isMacOSX10_6orOlder() || isWinXP) {
info(true, "Screensharing disabled for OSX10.6 and WinXP");
return true;
}
return false;
}
// Linux prompts aren't working for screensharing.
function isLinux() {
return navigator.platform.indexOf("Linux") != -1;
}
function loadPage(aUrl) {
let deferred = Promise.defer();
gTab.linkedBrowser.addEventListener("load", function onload() {
gTab.linkedBrowser.removeEventListener("load", onload, true);
is(PopupNotifications._currentNotifications.length, 0,
"should start the test without any prior popup notification");
deferred.resolve();
}, true);
content.location = aUrl;
return deferred.promise;
}
registerCleanupFunction(function() {
gBrowser.removeCurrentTab();
});
const permissionError = "error: NotAllowedError: The request is not allowed " +
"by the user agent or the platform in the current context.";
var gTests = [
{
desc: "getUserMedia about:loopconversation shouldn't prompt",
run: function checkAudioVideoLoop() {
yield SpecialPowers.pushPrefEnv({
"set": [[PREF_LOOP_CSP, "default-src 'unsafe-inline'"]],
});
yield loadPage("about:loopconversation");
info("requesting devices");
let promise = promiseObserverCalled("recording-device-events");
yield promiseRequestDevice(true, true);
yield promise;
// Wait for the devices to actually be captured and running before
// proceeding.
yield promisePopupNotification("webRTC-sharingDevices");
is((yield getMediaCaptureState()), "CameraAndMicrophone",
"expected camera and microphone to be shared");
yield closeStream();
yield SpecialPowers.popPrefEnv();
}
},
{
desc: "getUserMedia about:loopconversation should prompt for window sharing",
run: function checkShareScreenLoop() {
if (isOldPlatform() || isLinux()) {
return;
}
yield SpecialPowers.pushPrefEnv({
"set": [[PREF_LOOP_CSP, "default-src 'unsafe-inline'"]],
});
yield loadPage("about:loopconversation");
info("requesting screen");
let promise = promiseObserverCalled("getUserMedia:request");
yield promiseRequestDevice(false, true, null, "window");
// Wait for the devices to actually be captured and running before
// proceeding.
yield promisePopupNotification("webRTC-shareDevices");
is((yield getMediaCaptureState()), "none",
"expected camera and microphone not to be shared");
yield promiseMessage(permissionError, () => {
PopupNotifications.panel.firstChild.button.click();
});
yield expectObserverCalled("getUserMedia:response:deny");
yield expectObserverCalled("recording-window-ended");
yield SpecialPowers.popPrefEnv();
}
},
{
desc: "getUserMedia about:evil should prompt",
run: function checkAudioVideoNonLoop() {
yield loadPage("about:evil");
let promise = promiseObserverCalled("getUserMedia:request");
yield promiseRequestDevice(true, true);
yield promise;
is((yield getMediaCaptureState()), "none",
"expected camera and microphone not to be shared");
}
},
];
function test() {
waitForExplicitFinish();
gTab = gBrowser.addTab();
gBrowser.selectedTab = gTab;
gTab.linkedBrowser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
Task.spawn(function () {
yield ContentTask.spawn(gBrowser.selectedBrowser,
getRootDirectory(gTestPath) + "get_user_media.html",
function* (url) {
const Ci = Components.interfaces;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
/* A fake about module to map get_user_media.html to different about urls. */
function fakeLoopAboutModule() {
}
fakeLoopAboutModule.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
newChannel: function (aURI, aLoadInfo) {
let uri = Services.io.newURI(url, null, null);
let chan = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
chan.owner = Services.scriptSecurityManager.getSystemPrincipal();
return chan;
},
getURIFlags: function (aURI) {
return Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
Ci.nsIAboutModule.ALLOW_SCRIPT |
Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD |
Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT;
}
};
var factory = XPCOMUtils._getFactory(fakeLoopAboutModule);
var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
let UUIDGenerator = Components.classes["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator);
registrar.registerFactory(UUIDGenerator.generateUUID(), "",
"@mozilla.org/network/protocol/about;1?what=loopconversation",
factory);
registrar.registerFactory(UUIDGenerator.generateUUID(), "",
"@mozilla.org/network/protocol/about;1?what=evil",
factory);
});
yield SpecialPowers.pushPrefEnv({
"set": [[PREF_PERMISSION_FAKE, true],
["media.getusermedia.screensharing.enabled", true]],
});
for (let test of gTests) {
info(test.desc);
yield test.run();
// Cleanup before the next test
expectNoObserverCalled();
}
yield ContentTask.spawn(gBrowser.selectedBrowser, null,
function* () {
const Ci = Components.interfaces;
const Cc = Components.classes;
var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
let cid = Cc["@mozilla.org/network/protocol/about;1?what=loopconversation"];
registrar.unregisterFactory(cid,
registrar.getClassObject(cid, Ci.nsIFactory));
cid = Cc["@mozilla.org/network/protocol/about;1?what=evil"];
registrar.unregisterFactory(cid,
registrar.getClassObject(cid, Ci.nsIFactory));
});
}).then(finish, ex => {
ok(false, "Unexpected Exception: " + ex);
finish();
});
}

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

@ -102,22 +102,6 @@ static RedirEntry kRedirMap[] = {
#endif
{ "accounts", "chrome://browser/content/aboutaccounts/aboutaccounts.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
// Linkable because of indexeddb use (bug 1228118)
{ "loopconversation", "chrome://loop/content/panels/conversation.html",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT |
nsIAboutModule::MAKE_LINKABLE |
nsIAboutModule::ENABLE_INDEXED_DB },
// Linkable because of indexeddb use (bug 1228118)
{ "looppanel", "chrome://loop/content/panels/panel.html",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT |
nsIAboutModule::MAKE_LINKABLE |
nsIAboutModule::ENABLE_INDEXED_DB,
// Shares an IndexedDB origin with about:loopconversation.
"loopconversation" },
{ "reader", "chrome://global/content/reader/aboutReader.html",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |

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

@ -108,8 +108,6 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
#ifdef MOZ_SERVICES_HEALTHREPORT
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "healthreport", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#endif
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "looppanel", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "loopconversation", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "reader", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#if defined(XP_WIN)
{ NS_IEHISTORYENUMERATOR_CONTRACTID, &kNS_WINIEHISTORYENUMERATOR_CID },

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

@ -64,7 +64,6 @@ var kVersion = 6;
* version the button is removed in as the value. e.g. "pocket-button": 5
*/
var ObsoleteBuiltinButtons = {
"loop-button": 5,
"pocket-button": 6
};
@ -238,7 +237,6 @@ var CustomizableUIInternal = {
"bookmarks-menu-button",
"downloads-button",
"home-button",
"loop-button",
];
if (AppConstants.MOZ_DEV_EDITION) {

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

@ -242,8 +242,6 @@
<panelview id="PanelUI-socialapi" flex="1"/>
<panelview id="PanelUI-loopapi" flex="1"/>
<panelview id="PanelUI-feeds" flex="1" oncommand="FeedHandler.subscribeToFeed(null, event);">
<label value="&feedsMenu2.label;" class="panel-subview-header"/>
</panelview>

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

@ -126,7 +126,7 @@ CommandList.prototype = {
// therefore the listeners for these elements will be garbage collected.
keyElement.addEventListener("command", (event) => {
if (name == "_execute_page_action") {
let win = event.target.ownerGlobal;
let win = event.target.ownerDocument.defaultView;
pageActionFor(this.extension).triggerAction(win);
} else {
this.emit("command", name);

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

@ -2,32 +2,160 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
Cu.import("resource://gre/modules/AppConstants.jsm");
add_task(function* test_user_defined_commands() {
const testCommands = [
// Ctrl Shortcuts
{
name: "toggle-ctrl-a",
shortcut: "Ctrl+A",
key: "A",
modifiers: {
accelKey: true,
},
},
{
name: "toggle-ctrl-up",
shortcut: "Ctrl+Up",
key: "VK_UP",
modifiers: {
accelKey: true,
},
},
// Alt Shortcuts
{
name: "toggle-alt-a",
shortcut: "Alt+A",
key: "A",
modifiers: {
altKey: true,
},
},
{
name: "toggle-alt-down",
shortcut: "Alt+Down",
key: "VK_DOWN",
modifiers: {
altKey: true,
},
},
// Mac Shortcuts
{
name: "toggle-command-shift-page-up",
shortcutMac: "Command+Shift+PageUp",
key: "VK_PAGE_UP",
modifiers: {
accelKey: true,
shiftKey: true,
},
},
{
name: "toggle-mac-control-shift+period",
shortcut: "Ctrl+Shift+Period",
shortcutMac: "MacCtrl+Shift+Period",
key: "VK_PERIOD",
modifiers: {
ctrlKey: true,
shiftKey: true,
},
},
// Ctrl+Shift Shortcuts
{
name: "toggle-ctrl-shift-left",
shortcut: "Ctrl+Shift+Left",
key: "VK_LEFT",
modifiers: {
accelKey: true,
shiftKey: true,
},
},
// Alt+Shift Shortcuts
{
name: "toggle-alt-shift-1",
shortcut: "Alt+Shift+1",
key: "1",
modifiers: {
altKey: true,
shiftKey: true,
},
},
{
name: "toggle-alt-shift-a",
shortcut: "Alt+Shift+A",
key: "A",
modifiers: {
altKey: true,
shiftKey: true,
},
},
{
name: "toggle-alt-shift-right",
shortcut: "Alt+Shift+Right",
key: "VK_RIGHT",
modifiers: {
altKey: true,
shiftKey: true,
},
},
// Misc Shortcuts
{
name: "valid-command-with-unrecognized-property-name",
shortcut: "Alt+Shift+3",
key: "3",
modifiers: {
altKey: true,
shiftKey: true,
},
unrecognized_property: "with-a-random-value",
},
{
name: "spaces-in-shortcut-name",
shortcut: " Alt + Shift + 2 ",
key: "2",
modifiers: {
altKey: true,
shiftKey: true,
},
},
];
// Create a window before the extension is loaded.
let win1 = yield BrowserTestUtils.openNewBrowserWindow();
yield BrowserTestUtils.loadURI(win1.gBrowser.selectedBrowser, "about:robots");
yield BrowserTestUtils.browserLoaded(win1.gBrowser.selectedBrowser);
let commands = {};
let isMac = AppConstants.platform == "macosx";
let totalMacOnlyCommands = 0;
for (let testCommand of testCommands) {
let command = {
suggested_key: {},
};
if (testCommand.shortcut) {
command.suggested_key.default = testCommand.shortcut;
}
if (testCommand.shortcutMac) {
command.suggested_key.mac = testCommand.shortcutMac;
}
if (testCommand.shortcutMac && !testCommand.shortcut) {
totalMacOnlyCommands++;
}
if (testCommand.unrecognized_property) {
command.unrecognized_property = testCommand.unrecognized_property;
}
commands[testCommand.name] = command;
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"commands": {
"toggle-feature-using-alt-shift-3": {
"suggested_key": {
"default": "Alt+Shift+3",
},
},
"toggle-feature-using-alt-shift-comma": {
"suggested_key": {
"default": "Alt+Shift+Comma",
},
"unrecognized_property": "with-a-random-value",
},
"toggle-feature-with-whitespace-in-suggested-key": {
"suggested_key": {
"default": " Alt + Shift + 2 ",
},
},
},
"commands": commands,
},
background: function() {
@ -38,7 +166,6 @@ add_task(function* test_user_defined_commands() {
},
});
SimpleTest.waitForExplicitFinish();
let waitForConsole = new Promise(resolve => {
SimpleTest.monitorConsole(resolve, [{
@ -49,35 +176,41 @@ add_task(function* test_user_defined_commands() {
yield extension.startup();
yield extension.awaitMessage("ready");
function* runTest() {
for (let testCommand of testCommands) {
if (testCommand.shortcutMac && !testCommand.shortcut && !isMac) {
continue;
}
EventUtils.synthesizeKey(testCommand.key, testCommand.modifiers);
let message = yield extension.awaitMessage("oncommand");
is(message, testCommand.name, `Expected onCommand listener to fire with the correct name: ${testCommand.name}`);
}
}
// Create another window after the extension is loaded.
let win2 = yield BrowserTestUtils.openNewBrowserWindow();
yield BrowserTestUtils.loadURI(win2.gBrowser.selectedBrowser, "about:config");
yield BrowserTestUtils.browserLoaded(win2.gBrowser.selectedBrowser);
let totalTestCommands = Object.keys(testCommands).length;
let expectedCommandsRegistered = isMac ? totalTestCommands : totalTestCommands - totalMacOnlyCommands;
// Confirm the keysets have been added to both windows.
let keysetID = `ext-keyset-id-${makeWidgetId(extension.id)}`;
let keyset = win1.document.getElementById(keysetID);
ok(keyset != null, "Expected keyset to exist");
is(keyset.childNodes.length, 3, "Expected keyset to have 3 children");
is(keyset.childNodes.length, expectedCommandsRegistered, "Expected keyset to have the correct number of children");
keyset = win2.document.getElementById(keysetID);
ok(keyset != null, "Expected keyset to exist");
is(keyset.childNodes.length, 3, "Expected keyset to have 3 children");
is(keyset.childNodes.length, expectedCommandsRegistered, "Expected keyset to have the correct number of children");
// Confirm that the commands are registered to both windows.
yield focusWindow(win1);
EventUtils.synthesizeKey("3", {altKey: true, shiftKey: true});
let message = yield extension.awaitMessage("oncommand");
is(message, "toggle-feature-using-alt-shift-3", "Expected onCommand listener to fire with correct message");
yield runTest();
yield focusWindow(win2);
EventUtils.synthesizeKey("VK_COMMA", {altKey: true, shiftKey: true});
message = yield extension.awaitMessage("oncommand");
is(message, "toggle-feature-using-alt-shift-comma", "Expected onCommand listener to fire with correct message");
EventUtils.synthesizeKey("2", {altKey: true, shiftKey: true});
message = yield extension.awaitMessage("oncommand");
is(message, "toggle-feature-with-whitespace-in-suggested-key", "Expected onCommand listener to fire with correct message");
yield runTest();
yield extension.unload();
@ -94,5 +227,3 @@ add_task(function* test_user_defined_commands() {
SimpleTest.endMonitorConsole();
yield waitForConsole;
});

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

@ -3370,6 +3370,9 @@ var SessionStoreInternal = {
let uri = activePageData ? activePageData.url || null : null;
if (aLoadArguments) {
uri = aLoadArguments.uri;
if (aLoadArguments.userContextId) {
browser.setAttribute("usercontextid", aLoadArguments.userContextId);
}
}
// We have to mark this tab as restoring first, otherwise

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

@ -153,54 +153,6 @@ this.UITour = {
query: "#panic-button",
widgetName: "panic-button",
}],
["loop", {
allowAdd: true,
query: "#loop-button",
widgetName: "loop-button",
}],
["loop-newRoom", {
infoPanelPosition: "leftcenter topright",
query: (aDocument) => {
let loopUI = aDocument.defaultView.LoopUI;
// Use the parentElement full-width container of the button so our arrow
// doesn't overlap the panel contents much.
return loopUI.browser.contentDocument.querySelector(".new-room-button").parentElement;
},
}],
["loop-roomList", {
infoPanelPosition: "leftcenter topright",
query: (aDocument) => {
let loopUI = aDocument.defaultView.LoopUI;
return loopUI.browser.contentDocument.querySelector(".room-list");
},
}],
["loop-selectedRoomButtons", {
infoPanelOffsetY: -20,
infoPanelPosition: "start_after",
query: (aDocument) => {
let chatbox = aDocument.querySelector("chatbox[src^='about\:loopconversation'][selected]");
// Check that the real target actually exists
if (!chatbox || !chatbox.contentDocument ||
!chatbox.contentDocument.querySelector(".call-action-group")) {
return null;
}
// But anchor on the <browser> in the chatbox so the panel doesn't jump to undefined
// positions when the copy/email buttons disappear e.g. when the feedback form opens or
// somebody else joins the room.
return chatbox.content;
},
}],
["loop-signInUpLink", {
query: (aDocument) => {
let loopBrowser = aDocument.defaultView.LoopUI.browser;
if (!loopBrowser) {
return null;
}
return loopBrowser.contentDocument.querySelector(".signin-link");
},
}],
["pocket", {
allowAdd: true,
query: "#pocket-button",
@ -895,16 +847,12 @@ this.UITour = {
this.hideInfo(aWindow);
// Ensure the menu panel is hidden before calling recreatePopup so popup events occur.
this.hideMenu(aWindow, "appMenu");
this.hideMenu(aWindow, "loop");
this.hideMenu(aWindow, "controlCenter");
// Clean up panel listeners after calling hideMenu above.
aWindow.PanelUI.panel.removeEventListener("popuphiding", this.hideAppMenuAnnotations);
aWindow.PanelUI.panel.removeEventListener("ViewShowing", this.hideAppMenuAnnotations);
aWindow.PanelUI.panel.removeEventListener("popuphidden", this.onPanelHidden);
let loopPanel = aWindow.document.getElementById("loop-notification-panel");
loopPanel.removeEventListener("popuphidden", this.onPanelHidden);
loopPanel.removeEventListener("popuphiding", this.hideLoopPanelAnnotations);
let controlCenterPanel = aWindow.gIdentityHandler._identityPopup;
controlCenterPanel.removeEventListener("popuphidden", this.onPanelHidden);
controlCenterPanel.removeEventListener("popuphiding", this.hideControlCenterAnnotations);
@ -1735,31 +1683,6 @@ this.UITour = {
popup.addEventListener("popupshown", onPopupShown);
}
aWindow.document.getElementById("identity-box").click();
} else if (aMenuName == "loop") {
let toolbarButton = aWindow.LoopUI.toolbarButton;
// It's possible to have a node that isn't placed anywhere
if (!toolbarButton || !toolbarButton.node ||
!CustomizableUI.getPlacementOfWidget(toolbarButton.node.id)) {
log.debug("Can't show the Loop menu since the toolbarButton isn't placed");
return;
}
let panel = aWindow.document.getElementById("loop-notification-panel");
panel.setAttribute("noautohide", true);
if (panel.state != "open") {
this.recreatePopup(panel);
this.clearAvailableTargetsCache();
}
// An event object is expected but we don't want to toggle the panel with a click if the panel
// is already open.
aWindow.LoopUI.openPanel({ target: toolbarButton.node, }, "rooms").then(() => {
if (aOpenCallback) {
aOpenCallback();
}
});
panel.addEventListener("popuphidden", this.onPanelHidden);
panel.addEventListener("popuphiding", this.hideLoopPanelAnnotations);
} else if (aMenuName == "pocket") {
this.getTarget(aWindow, "pocket").then(Task.async(function* onPocketTarget(target) {
let widgetGroupWrapper = CustomizableUI.getWidget(target.widgetName);
@ -1818,9 +1741,6 @@ this.UITour = {
} else if (aMenuName == "controlCenter") {
let panel = aWindow.gIdentityHandler._identityPopup;
panel.hidePopup();
} else if (aMenuName == "loop") {
let panel = aWindow.document.getElementById("loop-notification-panel");
panel.hidePopup();
}
},
@ -1853,12 +1773,6 @@ this.UITour = {
UITour.hideAnnotationsForPanel(aEvent, UITour.targetIsInAppMenu);
},
hideLoopPanelAnnotations: function(aEvent) {
UITour.hideAnnotationsForPanel(aEvent, (aTarget) => {
return aTarget.targetName.startsWith("loop-") && aTarget.targetName != "loop-selectedRoomButtons";
});
},
hideControlCenterAnnotations(aEvent) {
UITour.hideAnnotationsForPanel(aEvent, (aTarget) => {
return aTarget.targetName.startsWith("controlCenter-");
@ -1931,12 +1845,6 @@ this.UITour = {
case "availableTargets":
this.getAvailableTargets(aMessageManager, aWindow, aCallbackID);
break;
case "loop":
const FTU_VERSION = 1;
this.sendPageCallback(aMessageManager, aCallbackID, {
gettingStartedSeen: (Services.prefs.getIntPref("loop.gettingStarted.latestFTUVersion") >= FTU_VERSION),
});
break;
case "search":
case "selectedSearchEngine":
Services.search.init(rv => {
@ -1980,10 +1888,6 @@ this.UITour = {
}
} catch (e) {}
break;
case "Loop:ResumeTourOnFirstJoin":
// Ignore aValue in this case to avoid accidentally setting it to false.
Services.prefs.setBoolPref("loop.gettingStarted.resumeOnFirstJoin", true);
break;
default:
log.error("setConfiguration: Unknown configuration requested: " + aConfiguration);
break;

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

@ -31,9 +31,6 @@ skip-if = os == "linux" # Linux: Bug 986760, Bug 989101.
[browser_UITour_detach_tab.js]
[browser_UITour_forceReaderMode.js]
[browser_UITour_heartbeat.js]
[browser_UITour_loop.js]
skip-if = true # Bug 1225832 - New Loop architecture is not compatible with test.
[browser_UITour_loop_panel.js]
[browser_UITour_modalDialog.js]
skip-if = os != "mac" # modal dialog disabling only working on OS X.
[browser_UITour_observe.js]

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

@ -21,7 +21,6 @@ add_UITour_task(function* test_availableTargets() {
"customize",
"help",
"home",
"loop",
"devtools",
...(hasPocket ? ["pocket"] : []),
"privateWindow",
@ -50,7 +49,6 @@ add_UITour_task(function* test_availableTargets_changeWidgets() {
"backForward",
"customize",
"help",
"loop",
"devtools",
"home",
...(hasPocket ? ["pocket"] : []),
@ -86,7 +84,6 @@ add_UITour_task(function* test_availableTargets_exceptionFromGetTarget() {
"customize",
"help",
"home",
"loop",
"devtools",
...(hasPocket ? ["pocket"] : []),
"privateWindow",

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

@ -1,414 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var gTestTab;
var gContentAPI;
var gContentWindow;
var gMessageHandlers;
var loopButton;
var fakeRoom;
var loopPanel = document.getElementById("loop-notification-panel");
const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
const { LoopRooms } = Cu.import("chrome://loop/content/modules/LoopRooms.jsm", {});
const { MozLoopServiceInternal } = Cu.import("chrome://loop/content/modules/MozLoopService.jsm", {});
const FTU_VERSION = 1;
function test() {
UITourTest();
}
function runOffline(fun) {
return (done) => {
Services.io.offline = true;
fun(function onComplete() {
Services.io.offline = false;
done();
});
}
}
var tests = [
taskify(function* test_gettingStartedClicked_linkOpenedWithExpectedParams() {
// Set latestFTUVersion to lower number to show FTU panel.
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 0);
Services.prefs.setCharPref("loop.gettingStarted.url", "http://example.com");
is(loopButton.open, false, "Menu should initially be closed");
loopButton.click();
yield waitForConditionPromise(() => {
return loopButton.open;
}, "Menu should be visible after showMenu()");
gContentAPI.registerPageID("hello-tour_OpenPanel_testPage");
yield new Promise(resolve => {
gContentAPI.ping(() => resolve());
});
let loopDoc = document.getElementById("loop-notification-panel").children[0].contentDocument;
yield waitForConditionPromise(() => {
return loopDoc.readyState == 'complete';
}, "Loop notification panel document should be fully loaded.", 50);
let gettingStartedButton = loopDoc.getElementById("fte-button");
ok(gettingStartedButton, "Getting Started button should be found");
let newTabPromise = waitForConditionPromise(() => {
return gBrowser.currentURI.path.includes("utm_source=firefox-browser");
}, "New tab with utm_content=testPageNewID should have opened");
gettingStartedButton.click();
yield newTabPromise;
ok(gBrowser.currentURI.path.includes("utm_content=hello-tour_OpenPanel_testPage"),
"Expected URL opened (" + gBrowser.currentURI.path + ")");
yield gBrowser.removeCurrentTab();
checkLoopPanelIsHidden();
}),
taskify(function* test_gettingStartedClicked_linkOpenedWithExpectedParams2() {
// Set latestFTUVersion to lower number to show FTU panel.
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 0);
// Force a refresh of the loop panel since going from seen -> unseen doesn't trigger
// automatic re-rendering.
let loopWin = document.getElementById("loop-notification-panel").children[0].contentWindow;
var event = new loopWin.CustomEvent("GettingStartedSeen", { detail: false });
loopWin.dispatchEvent(event);
UITour.pageIDsForSession.clear();
Services.prefs.setCharPref("loop.gettingStarted.url", "http://example.com");
is(loopButton.open, false, "Menu should initially be closed");
loopButton.click();
yield waitForConditionPromise(() => {
return loopButton.open;
}, "Menu should be visible after showMenu()");
gContentAPI.registerPageID("hello-tour_OpenPanel_testPageOldId");
yield new Promise(resolve => {
gContentAPI.ping(() => resolve());
});
// Set the time of the page ID to 10 hours earlier, so that it is considered "expired".
UITour.pageIDsForSession.set("hello-tour_OpenPanel_testPageOldId",
{lastSeen: Date.now() - (10 * 60 * 60 * 1000)});
let loopDoc = loopWin.document;
let gettingStartedButton = loopDoc.getElementById("fte-button");
ok(gettingStartedButton, "Getting Started button should be found");
let newTabPromise = waitForConditionPromise(() => {
Services.console.logStringMessage(gBrowser.currentURI.path);
return gBrowser.currentURI.path.includes("utm_source=firefox-browser");
}, "New tab with utm_content=testPageNewID should have opened");
gettingStartedButton.click();
yield newTabPromise;
ok(!gBrowser.currentURI.path.includes("utm_content=hello-tour_OpenPanel_testPageOldId"),
"Expected URL opened without the utm_content parameter (" +
gBrowser.currentURI.path + ")");
yield gBrowser.removeCurrentTab();
checkLoopPanelIsHidden();
}),
// Test the menu was cleaned up in teardown.
taskify(function* setup_menu_cleanup() {
gContentAPI.showMenu("loop");
yield waitForConditionPromise(() => {
return loopButton.open;
}, "Menu should be visible after showMenu()");
// Leave it open so it gets torn down and we can test below that teardown was succesful.
}),
taskify(function* test_menu_cleanup() {
// Test that the open menu from above was torn down fully.
checkLoopPanelIsHidden();
}),
function test_availableTargets(done) {
gContentAPI.showMenu("loop");
gContentAPI.getConfiguration("availableTargets", (data) => {
for (let targetName of ["loop-newRoom", "loop-roomList", "loop-signInUpLink"]) {
isnot(data.targets.indexOf(targetName), -1, targetName + " should exist");
}
done();
});
},
function test_getConfigurationLoop(done) {
let gettingStartedSeen = Services.prefs.getIntPref("loop.gettingStarted.latestFTUVersion") >= FTU_VERSION;
gContentAPI.getConfiguration("loop", (data) => {
is(data.gettingStartedSeen, gettingStartedSeen,
"The configuration property should equal that of the pref");
done();
});
},
function test_hideMenuHidesAnnotations(done) {
let infoPanel = document.getElementById("UITourTooltip");
let highlightPanel = document.getElementById("UITourHighlightContainer");
gContentAPI.showMenu("loop", function menuCallback() {
gContentAPI.showHighlight("loop-roomList");
gContentAPI.showInfo("loop-newRoom", "Make a new room", "AKA. conversation");
UITour.getTarget(window, "loop-newRoom").then((target) => {
waitForPopupAtAnchor(infoPanel, target.node, Task.async(function* checkPanelIsOpen() {
isnot(loopPanel.state, "closed", "Loop panel should still be open");
ok(loopPanel.hasAttribute("noautohide"), "@noautohide should still be on the loop panel");
is(highlightPanel.getAttribute("targetName"), "loop-roomList", "Check highlight @targetname");
is(infoPanel.getAttribute("targetName"), "loop-newRoom", "Check info panel @targetname");
info("Close the loop menu and make sure the annotations inside disappear");
let hiddenPromises = [promisePanelElementHidden(window, infoPanel),
promisePanelElementHidden(window, highlightPanel)];
gContentAPI.hideMenu("loop");
yield Promise.all(hiddenPromises);
isnot(infoPanel.state, "open", "Info panel should have automatically hid");
isnot(highlightPanel.state, "open", "Highlight panel should have automatically hid");
done();
}), "Info panel should be anchored to the new room button");
});
});
},
runOffline(function test_notifyLoopChatWindowOpenedClosed(done) {
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowOpened", "Check Loop:ChatWindowOpened notification");
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowShown", "Check Loop:ChatWindowShown notification");
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowClosed", "Check Loop:ChatWindowClosed notification");
gContentAPI.observe((event, params) => {
ok(false, "No more notifications should have arrived");
});
});
done();
});
document.querySelector("#pinnedchats > chatbox").close();
});
LoopRooms.open("fakeTourRoom");
}),
runOffline(function test_notifyLoopRoomURLCopied(done) {
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowOpened", "Loop chat window should've opened");
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowShown", "Check Loop:ChatWindowShown notification");
let chat = document.querySelector("#pinnedchats > chatbox");
gContentAPI.observe((event, params) => {
is(event, "Loop:RoomURLCopied", "Check Loop:RoomURLCopied notification");
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowClosed", "Check Loop:ChatWindowClosed notification");
});
chat.close();
done();
});
let window = chat.content.contentWindow;
waitForConditionPromise(
() => chat.content.contentDocument.querySelector(".btn-copy"),
"Copy button should be there"
).then(() => chat.content.contentDocument.querySelector(".btn-copy").click());
});
});
LoopRooms.open("fakeTourRoom");
}),
runOffline(function test_notifyLoopRoomURLEmailed(done) {
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowOpened", "Loop chat window should've opened");
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowShown", "Check Loop:ChatWindowShown notification");
let chat = document.querySelector("#pinnedchats > chatbox");
let composeEmailCalled = false;
gContentAPI.observe((event, params) => {
is(event, "Loop:RoomURLEmailed", "Check Loop:RoomURLEmailed notification");
ok(composeEmailCalled, "mozLoop.composeEmail should be called");
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowClosed", "Check Loop:ChatWindowClosed notification");
});
chat.close();
done();
});
gMessageHandlers.ComposeEmail = function(message, reply) {
let [subject, body, recipient] = message.data;
ok(subject, "composeEmail should be invoked with at least a subject value");
composeEmailCalled = true;
reply();
};
waitForConditionPromise(
() => chat.content.contentDocument.querySelector(".btn-email"),
"Email button should be there"
).then(() => chat.content.contentDocument.querySelector(".btn-email").click());
});
});
LoopRooms.open("fakeTourRoom");
}),
taskify(function* test_arrow_panel_position() {
is(loopButton.open, false, "Menu should initially be closed");
let popup = document.getElementById("UITourTooltip");
yield showMenuPromise("loop");
let currentTarget = "loop-newRoom";
yield showInfoPromise(currentTarget, "This is " + currentTarget, "My arrow should be on the side");
is(popup.popupBoxObject.alignmentPosition, "start_before", "Check " + currentTarget + " position");
currentTarget = "loop-roomList";
yield showInfoPromise(currentTarget, "This is " + currentTarget, "My arrow should be on the side");
is(popup.popupBoxObject.alignmentPosition, "start_before", "Check " + currentTarget + " position");
currentTarget = "loop-signInUpLink";
yield showInfoPromise(currentTarget, "This is " + currentTarget, "My arrow should be underneath");
is(popup.popupBoxObject.alignmentPosition, "after_end", "Check " + currentTarget + " position");
}),
taskify(function* test_setConfiguration() {
is(Services.prefs.getBoolPref("loop.gettingStarted.resumeOnFirstJoin"), false, "pref should be false but exist");
gContentAPI.setConfiguration("Loop:ResumeTourOnFirstJoin", true);
yield waitForConditionPromise(() => {
return Services.prefs.getBoolPref("loop.gettingStarted.resumeOnFirstJoin");
}, "Pref should change to true via setConfiguration");
Services.prefs.clearUserPref("loop.gettingStarted.resumeOnFirstJoin");
}),
taskify(function* test_resumeViaMenuPanel_roomClosedTabOpen() {
Services.prefs.setBoolPref("loop.gettingStarted.resumeOnFirstJoin", true);
// Create a fake room and then add a fake non-owner participant
let roomsMap = setupFakeRoom();
roomsMap.get("fakeTourRoom").participants = [{
owner: false,
}];
// Set the tour URL to be the current page with a different query param
let gettingStartedURL = gTestTab.linkedBrowser.currentURI.resolve("?gettingstarted=1");
Services.prefs.setCharPref("loop.gettingStarted.url", gettingStartedURL);
let observationPromise = new Promise((resolve) => {
gContentAPI.observe((event, params) => {
is(event, "Loop:IncomingConversation", "Page should have been notified about incoming conversation");
is(params.conversationOpen, false, "conversationOpen should be false");
is(gBrowser.selectedTab, gTestTab, "The same tab should be selected");
resolve();
});
});
// Now open the menu while that non-owner is in the fake room to trigger resuming the tour
yield showMenuPromise("loop");
yield observationPromise;
Services.prefs.clearUserPref("loop.gettingStarted.resumeOnFirstJoin");
}),
taskify(function* test_resumeViaMenuPanel_roomClosedTabClosed() {
Services.prefs.setBoolPref("loop.gettingStarted.resumeOnFirstJoin", true);
// Create a fake room and then add a fake non-owner participant
let roomsMap = setupFakeRoom();
roomsMap.get("fakeTourRoom").participants = [{
owner: false,
}];
// Set the tour URL to a page that's not open yet
Services.prefs.setCharPref("loop.gettingStarted.url", gBrowser.currentURI.prePath);
let newTabPromise = waitForConditionPromise(() => {
return gBrowser.currentURI.path.includes("incomingConversation=waiting");
}, "New tab with incomingConversation=waiting should have opened");
// Now open the menu while that non-owner is in the fake room to trigger resuming the tour
yield showMenuPromise("loop");
yield newTabPromise;
yield gBrowser.removeCurrentTab();
Services.prefs.clearUserPref("loop.gettingStarted.resumeOnFirstJoin");
}),
];
// End tests
function checkLoopPanelIsHidden() {
ok(!loopPanel.hasAttribute("noautohide"), "@noautohide on the loop panel should have been cleaned up");
ok(!loopPanel.hasAttribute("panelopen"), "The panel shouldn't have @panelopen");
isnot(loopPanel.state, "open", "The panel shouldn't be open");
is(loopButton.hasAttribute("open"), false, "Loop button should know that the panel is closed");
}
function setupFakeRoom() {
let room = Object.create(fakeRoom);
let roomsMap = new Map([
[room.roomToken, room]
]);
LoopRooms.stubCache(roomsMap);
return roomsMap;
}
if (Services.prefs.getBoolPref("loop.enabled")) {
loopButton = window.LoopUI.toolbarButton.node;
fakeRoom = {
decryptedContext: { roomName: "fakeTourRoom" },
participants: [],
maxSize: 2,
ctime: Date.now()
};
for (let prop of ["roomToken", "roomOwner", "roomUrl"])
fakeRoom[prop] = "fakeTourRoom";
LoopAPI.stubMessageHandlers(gMessageHandlers = {
// Stub the rooms object API to fully control the test behavior.
"Rooms:*": function(action, message, reply) {
switch (action.split(":").pop()) {
case "GetAll":
reply([fakeRoom]);
break;
case "Get":
reply(fakeRoom);
break;
case "Join":
reply({
apiKey: "fakeTourRoom",
sessionToken: "fakeTourRoom",
sessionId: "fakeTourRoom",
expires: Date.now() + 240000
});
break;
case "RefreshMembership":
reply({ expires: Date.now() + 240000 });
default:
reply();
}
},
// Stub the metadata retrieval to suppress console warnings and return faster.
GetSelectedTabMetadata: function(message, reply) {
reply({ favicon: null });
}
});
registerCleanupFunction(() => {
Services.prefs.clearUserPref("loop.gettingStarted.resumeOnFirstJoin");
Services.prefs.clearUserPref("loop.gettingStarted.latestFTUVersion");
Services.prefs.clearUserPref("loop.gettingStarted.url");
Services.io.offline = false;
// Copied from browser/components/loop/test/mochitest/head.js
// Remove the iframe after each test. This also avoids mochitest complaining
// about leaks on shutdown as we intentionally hold the iframe open for the
// life of the application.
let frameId = loopButton.getAttribute("notificationFrameId");
let frame = document.getElementById(frameId);
if (frame) {
frame.remove();
}
// Remove the stubbed rooms.
LoopRooms.stubCache(null);
// Restore the stubbed handlers.
LoopAPI.restore();
});
} else {
ok(true, "Loop is disabled so skip the UITour Loop tests");
tests = [];
}

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

@ -1,67 +0,0 @@
"use strict";
var gTestTab;
var gContentAPI;
var gContentWindow;
var gMessageHandlers;
var loopButton;
var fakeRoom;
var loopPanel = document.getElementById("loop-notification-panel");
const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
const { LoopRooms } = Cu.import("chrome://loop/content/modules/LoopRooms.jsm", {});
if (!Services.prefs.getBoolPref("loop.enabled")) {
ok(true, "Loop is disabled so skip the UITour Loop tests");
} else {
function checkLoopPanelIsHidden() {
ok(!loopPanel.hasAttribute("noautohide"), "@noautohide on the loop panel should have been cleaned up");
ok(!loopPanel.hasAttribute("panelopen"), "The panel shouldn't have @panelopen");
isnot(loopPanel.state, "open", "The panel shouldn't be open");
is(loopButton.hasAttribute("open"), false, "Loop button should know that the panel is closed");
}
add_task(setup_UITourTest);
add_task(function() {
loopButton = window.LoopUI.toolbarButton.node;
registerCleanupFunction(() => {
Services.prefs.clearUserPref("loop.gettingStarted.latestFTUVersion");
Services.io.offline = false;
// Copied from browser/components/loop/test/mochitest/head.js
// Remove the iframe after each test. This also avoids mochitest complaining
// about leaks on shutdown as we intentionally hold the iframe open for the
// life of the application.
let frameId = loopButton.getAttribute("notificationFrameId");
let frame = document.getElementById(frameId);
if (frame) {
frame.remove();
}
});
});
add_UITour_task(function* test_menu_show_hide() {
// The targets to highlight only appear after getting started is launched.
// Set latestFTUVersion to lower number to show FTU panel.
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 0);
is(loopButton.open, false, "Menu should initially be closed");
gContentAPI.showMenu("loop");
yield waitForConditionPromise(() => {
return loopPanel.state == "open";
}, "Menu should be visible after showMenu()");
ok(loopPanel.hasAttribute("noautohide"), "@noautohide should be on the loop panel");
ok(loopPanel.hasAttribute("panelopen"), "The panel should have @panelopen");
ok(loopButton.hasAttribute("open"), "Loop button should know that the menu is open");
gContentAPI.hideMenu("loop");
yield waitForConditionPromise(() => {
return !loopButton.open;
}, "Menu should be hidden after hideMenu()");
checkLoopPanelIsHidden();
});
}

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

@ -1,7 +0,0 @@
.module-cache
test/coverage/desktop
test/coverage/shared_standalone
test/node_modules
test/visual-regression/diff
test/visual-regression/new
test/visual-regression/refs

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

@ -1,3 +0,0 @@
This is the loop project output, https://github.com/mozilla/loop
Current extension version is: 0.1

1495
browser/extensions/loop/bootstrap.js поставляемый

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

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

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

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

@ -1,157 +0,0 @@
/* 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";var _Components =
Components;var Cu = _Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");var _Cu$import =
Cu.import("chrome://loop/content/modules/MozLoopService.jsm", {});var MozLoopService = _Cu$import.MozLoopService;var LOOP_SESSION_TYPE = _Cu$import.LOOP_SESSION_TYPE;
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
"resource://services-common/utils.js");
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
this.EXPORTED_SYMBOLS = ["LoopRoomsCache"];
var LOOP_ROOMS_CACHE_FILENAME = "loopRoomsCache.json";
XPCOMUtils.defineConstant(this, "LOOP_ROOMS_CACHE_FILENAME", LOOP_ROOMS_CACHE_FILENAME);
/**
* RoomsCache is a cache for saving simple rooms data to the disk in case we
* need it for back-up purposes, e.g. recording room keys for FxA if the user
* changes their password.
*
* The format of the data is:
*
* {
* <sessionType>: {
* <roomToken>: {
* "key": <roomKey>
* }
* }
* }
*
* It is intended to try and keep the data forward and backwards compatible in
* a reasonable manner, hence why the structure is more complex than it needs
* to be to store tokens and keys.
*
* @param {Object} options The options for the RoomsCache, containing:
* - {String} baseDir The base directory in which to save the file.
* - {String} filename The filename for the cache file.
*/
function LoopRoomsCache(options) {
options = options || {};
this.baseDir = options.baseDir || OS.Constants.Path.profileDir;
this.path = OS.Path.join(
this.baseDir,
options.filename || LOOP_ROOMS_CACHE_FILENAME);
this._cache = null;}
LoopRoomsCache.prototype = {
/**
* Updates the local copy of the cache and saves it to disk.
*
* @param {Object} contents An object to be saved in json format.
* @return {Promise} A promise that is resolved once the save is complete.
*/
_setCache: function _setCache(contents) {var _this = this;
this._cache = contents;
return OS.File.makeDir(this.baseDir, { ignoreExisting: true }).then(function () {return (
CommonUtils.writeJSON(contents, _this.path));});},
/**
* Returns the local copy of the cache if there is one, otherwise it reads
* it from the disk.
*
* @return {Promise} A promise that is resolved once the read is complete.
*/
_getCache: Task.async(function* () {
if (this._cache) {
return this._cache;}
try {
return this._cache = yield CommonUtils.readJSON(this.path);}
catch (error) {
if (!error.becauseNoSuchFile) {
MozLoopService.log.debug("Error reading the cache:", error);}
return this._cache = {};}}),
/**
* Function for testability purposes. Clears the cache.
*
* @return {Promise} A promise that is resolved once the clear is complete.
*/
clear: function clear() {
this._cache = null;
return OS.File.remove(this.path);},
/**
* Gets a room key from the cache.
*
* @param {LOOP_SESSION_TYPE} sessionType The session type for the room.
* @param {String} roomToken The token for the room.
* @return {Promise} A promise that is resolved when the data has been read
* with the value of the key, or null if it isn't present.
*/
getKey: Task.async(function* (sessionType, roomToken) {
if (sessionType != LOOP_SESSION_TYPE.FXA) {
return null;}
var sessionData = (yield this._getCache())[sessionType];
if (!sessionData || !sessionData[roomToken]) {
return null;}
return sessionData[roomToken].key;}),
/**
* Stores a room key into the cache. Note, if the key has not changed,
* the store will not be re-written.
*
* @param {LOOP_SESSION_TYPE} sessionType The session type for the room.
* @param {String} roomToken The token for the room.
* @param {String} roomKey The encryption key for the room.
* @return {Promise} A promise that is resolved when the data has been stored.
*/
setKey: Task.async(function* (sessionType, roomToken, roomKey) {
if (sessionType != LOOP_SESSION_TYPE.FXA) {
return Promise.resolve();}
var cache = yield this._getCache();
// Create these objects if they don't exist.
// We aim to do this creation and setting of the room key in a
// forwards-compatible way so that if new fields are added to rooms later
// then we don't mess them up (if there's no keys).
if (!cache[sessionType]) {
cache[sessionType] = {};}
if (!cache[sessionType][roomToken]) {
cache[sessionType][roomToken] = {};}
// Only save it if there's no key, or it is different.
if (!cache[sessionType][roomToken].key ||
cache[sessionType][roomToken].key != roomKey) {
cache[sessionType][roomToken].key = roomKey;
return yield this._setCache(cache);}
return Promise.resolve();}) };

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

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

@ -1,824 +0,0 @@
/* 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";function _toConsumableArray(arr) {if (Array.isArray(arr)) {for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) {arr2[i] = arr[i];}return arr2;} else {return Array.from(arr);}}var _Components =
Components;var Cc = _Components.classes;var Ci = _Components.interfaces;var Cu = _Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Timer.jsm");var _Cu$import =
Cu.import("chrome://loop/content/modules/MozLoopService.jsm", {});var MozLoopService = _Cu$import.MozLoopService;
var consoleLog = MozLoopService.log;
/* exported MozLoopPushHandler */
this.EXPORTED_SYMBOLS = ["MozLoopPushHandler"];
var CONNECTION_STATE_CLOSED = 0;
var CONNECTION_STATE_CONNECTING = 1;
var CONNECTION_STATE_OPEN = 2;
var SERVICE_STATE_OFFLINE = 0;
var SERVICE_STATE_PENDING = 1;
var SERVICE_STATE_ACTIVE = 2;
function PushSocket() {var webSocket = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
this._websocket = webSocket;}
PushSocket.prototype = {
/**
* Open push-notification websocket.
*
* @param {String} pushUri
* @param {Function} onMsg(aMsg) callback receives any incoming messages
* aMsg is constructed from the json payload; both
* text and binary message reception are mapped to this
* callback.
* @param {Function} onStart called when the socket is connected
* @param {Function} onClose(aCode, aReason) called when the socket closes;
* both near and far side close events map to this
* callback.
* aCode is any status code returned on close
* aReason is any string returned on close
*/
connect: function connect(pushUri, onMsg, onStart, onClose) {
if (!pushUri || !onMsg || !onStart || !onClose) {
throw new Error("PushSocket: missing required parameter(s):" + (
pushUri ? "" : " pushUri") + (
onMsg ? "" : " onMsg") + (
onStart ? "" : " onStart") + (
onClose ? "" : " onClose"));}
this._onMsg = onMsg;
this._onStart = onStart;
this._onClose = onClose;
if (!this._websocket) {
this._websocket = Cc["@mozilla.org/network/protocol;1?name=wss"].
createInstance(Ci.nsIWebSocketChannel);
this._websocket.initLoadInfo(null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_WEBSOCKET);}
var uri = Services.io.newURI(pushUri, null, null);
this._websocket.protocol = "push-notification";
this._websocket.asyncOpen(uri, pushUri, 0, this, null);},
/**
* nsIWebSocketListener method, handles the start of the websocket stream.
*
* @param {nsISupports} aContext Not used
*/
onStart: function onStart() {
this._socketOpen = true;
this._onStart();},
/**
* nsIWebSocketListener method, called when the websocket is closed locally.
*
* @param {nsISupports} aContext Not used
* @param {nsresult} aStatusCode
*/
onStop: function onStop(aContext, aStatusCode) {
this._socketOpen = false;
this._onClose(aStatusCode, "websocket onStop");},
/**
* nsIWebSocketListener method, called when the websocket is closed
* by the far end.
*
* @param {nsISupports} aContext Not used
* @param {integer} aCode the websocket closing handshake close code
* @param {String} aReason the websocket closing handshake close reason
*/
onServerClose: function onServerClose(aContext, aCode, aReason) {
this._socketOpen = false;
this._onClose(aCode, aReason);},
/**
* nsIWebSocketListener method, called when the websocket receives
* a text message (normally json encoded).
*
* @param {nsISupports} aContext Not used
* @param {String} aMsg The message data
*/
onMessageAvailable: function onMessageAvailable(aContext, aMsg) {
consoleLog.log("PushSocket: Message received: ", aMsg);
if (!this._socketOpen) {
consoleLog.error("Message received in Winsocket closed state");
return;}
try {
this._onMsg(JSON.parse(aMsg));}
catch (error) {
consoleLog.error("PushSocket: error parsing message payload - ", error);}},
/**
* nsIWebSocketListener method, called when the websocket receives a binary message.
* This class assumes that it is connected to a SimplePushServer and therefore treats
* the message payload as json encoded.
*
* @param {nsISupports} aContext Not used
* @param {String} aMsg The message data
*/
onBinaryMessageAvailable: function onBinaryMessageAvailable(aContext, aMsg) {
consoleLog.log("PushSocket: Binary message received: ", aMsg);
if (!this._socketOpen) {
consoleLog.error("PushSocket: message receive in Winsocket closed state");
return;}
try {
this._onMsg(JSON.parse(aMsg));}
catch (error) {
consoleLog.error("PushSocket: error parsing message payload - ", error);}},
/**
* Create a JSON encoded message payload and send via websocket.
*
* @param {Object} aMsg Message to send.
*
* @returns {Boolean} true if message has been sent, false otherwise
*/
send: function send(aMsg) {
if (!this._socketOpen) {
consoleLog.error("PushSocket: attempt to send before websocket is open");
return false;}
var msg = void 0;
try {
msg = JSON.stringify(aMsg);}
catch (error) {
consoleLog.error("PushSocket: JSON generation error - ", error);
return false;}
try {
this._websocket.sendMsg(msg);
consoleLog.log("PushSocket: Message sent: ", msg);}
// guard against the case that the websocket has closed before this call.
catch (e) {
consoleLog.warn("PushSocket: websocket send error", e);
return false;}
return true;},
/**
* Close the websocket.
*/
close: function close() {
if (!this._socketOpen) {
return;}
this._socketOpen = false;
consoleLog.info("PushSocket: websocket closing");
// Do not pass through any callbacks after this point.
this._onStart = function () {};
this._onMsg = this._onStart;
this._onClose = this._onStart;
try {
this._websocket.close(this._websocket.CLOSE_NORMAL);}
catch (e) {
// Do nothing
}} };
/**
* Create a RetryManager object. Class to handle retrying a UserAgent
* to PushServer request following a retry back-off scheme managed by
* this class. The current delay mechanism is to double the delay
* each time an operation to be retried until a maximum is met.
*
* @param {Integer} startDelay The initial delay interval in milliseconds.
* @param {Integer} maxDelay Maximum time delay value in milliseconds.
*/
function RetryManager(startDelay, maxDelay) {
if (!startDelay || !maxDelay) {
throw new Error("RetryManager: missing required parameters(s)" + (
startDelay ? "" : " startDelay") + (
maxDelay ? "" : " maxDelay"));}
this._startDelay = startDelay;
// The maximum delay cannot be less than the starting delay.
this._maxDelay = maxDelay > startDelay ? maxDelay : startDelay;}
RetryManager.prototype = {
/**
* Method to handle retrying a UserAgent to PushServer request.
*
* @param {Function} delayedOp Function to call after current delay is satisfied
*/
retry: function retry(delayedOp) {
if (!this._timeoutID) {
this._retryDelay = this._startDelay;} else
{
clearTimeout(this._timeoutID);
var nextDelay = this._retryDelay * 2;
this._retryDelay = nextDelay > this._maxDelay ? this._maxDelay : nextDelay;}
this._timeoutID = setTimeout(delayedOp, this._retryDelay);
consoleLog.log("PushHandler: retry delay set for ", this._retryDelay);},
/**
* Method used to reset the delay back-off logic and clear any currently
* running delay timeout.
*/
reset: function reset() {
if (this._timeoutID) {
clearTimeout(this._timeoutID);
this._timeoutID = null;}} };
/**
* Create a PingMonitor object. An object instance will periodically execute
* a ping send function and if not reset, will then execute an error function.
*
* @param {Function} pingFunc Function that is called after a ping interval
* has expired without being restart.
* @param {Function} onTimeout Function that is called after a ping timeout
* interval has expired without restart being called.
* @param {Integer} interval Timeout value in milliseconds between successive
* pings or between the last restart call and a ping.
* When this interval expires, pingFunc is called and the
* timeout interval is started.
* @param {Integer} timeout Timeout value in milliseconds between a call to
* pingFunc and a call to onTimeout unless restart is called.
* Restart will begin the ping timeout interval again.
*/
function PingMonitor(pingFunc, onTimeout, interval, timeout) {
if (!pingFunc || !onTimeout || !interval || !timeout) {
throw new Error("PingMonitor: missing required parameters");}
this._onTimeout = onTimeout;
this._pingFunc = pingFunc;
this._pingInterval = interval;
this._pingTimeout = timeout;}
PingMonitor.prototype = {
/**
* Function to restart the ping timeout and cancel any current timeout operation.
*/
restart: function restart() {var _this = this;
consoleLog.info("PushHandler: ping timeout restart");
this.stop();
this._pingTimerID = setTimeout(function () {return _this._pingSend();}, this._pingInterval);},
/**
* Function to stop the PingMonitor.
*/
stop: function stop() {
if (this._pingTimerID) {
clearTimeout(this._pingTimerID);
this._pingTimerID = undefined;}},
_pingSend: function _pingSend() {
consoleLog.info("PushHandler: ping sent");
this._pingTimerID = setTimeout(this._onTimeout, this._pingTimeout);
this._pingFunc();} };
/**
* We don't have push notifications on desktop currently, so this is a
* workaround to get them going for us.
*/
var MozLoopPushHandler = {
// This is the uri of the push server.
pushServerUri: undefined,
// Records containing the registration and notification callbacks indexed by channelID.
// Each channel will be registered with the PushServer.
channels: new Map(),
// This is the UserAgent UUID assigned by the PushServer
uaID: undefined,
// Each successfully registered channelID is used as a key to hold its pushEndpoint URL.
registeredChannels: {},
// Push protocol state variable
serviceState: SERVICE_STATE_OFFLINE,
// Websocket connection state variable
connectionState: CONNECTION_STATE_CLOSED,
// Contains channels that need to be registered with the PushServer
_channelsToRegister: [],
get _startRetryDelay_ms() {
try {
return Services.prefs.getIntPref("loop.retry_delay.start");}
catch (e) {
return 60000; // 1 minute
}},
get _maxRetryDelay_ms() {
try {
return Services.prefs.getIntPref("loop.retry_delay.limit");}
catch (e) {
return 300000; // 5 minutes
}},
get _pingInterval_ms() {
try {
return Services.prefs.getIntPref("loop.ping.interval");}
catch (e) {
return 18000000; // 30 minutes
}},
get _pingTimeout_ms() {
try {
return Services.prefs.getIntPref("loop.ping.timeout");}
catch (e) {
return 10000; // 10 seconds
}},
/**
* Inializes the PushHandler and opens a socket with the PushServer.
* It will automatically say hello and register any channels
* that are found in the work queue at that point.
*
* @param {Object} options Set of configuration options. Currently,
* the only option is mocketWebSocket which will be
* used for testing.
*/
initialize: function initialize() {var _this2 = this;var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
consoleLog.info("PushHandler: initialize options = ", options);
if (this._initDone) {
return;}
this._initDone = true;
this._retryManager = new RetryManager(this._startRetryDelay_ms,
this._maxRetryDelay_ms);
// Send an empty json payload as a ping.
// Close the websocket and re-open if a timeout occurs.
this._pingMonitor = new PingMonitor(function () {return _this2._pushSocket.send({});},
function () {return _this2._restartConnection();},
this._pingInterval_ms,
this._pingTimeout_ms);
if ("mockWebSocket" in options) {
this._mockWebSocket = options.mockWebSocket;}
this.pushServerUri = Services.prefs.getCharPref("dom.push.serverURL");
this._openSocket();},
/**
* Reset and clear PushServer connection.
* Returns MozLoopPushHandler to pre-initialized state.
*/
shutdown: function shutdown() {var _this3 = this;
consoleLog.info("PushHandler: shutdown");
if (!this._initDone) {
return;}
this._initDone = false;
this._retryManager.reset();
this._pingMonitor.stop();
// Un-register each active notification channel
if (this.connectionState === CONNECTION_STATE_OPEN) {
Object.keys(this.registeredChannels).forEach(function (id) {
var unRegMsg = { messageType: "unregister",
channelID: id };
_this3._pushSocket.send(unRegMsg);});
this.registeredChannels = {};}
this.connectionState = CONNECTION_STATE_CLOSED;
this.serviceState = SERVICE_STATE_OFFLINE;
this._pushSocket.close();
this._pushSocket = undefined;
// NOTE: this PushSocket instance will not be released until at least
// the websocket referencing it as an nsIWebSocketListener is released.
this.channels.clear();
this.uaID = undefined;
this.pushUrl = undefined;
this.pushServerUri = undefined;},
/**
* Assign a channel to be registered with the PushServer
* This channel will be registered when a connection to the PushServer
* has been established or re-registered after a connection has been lost
* and re-established. Calling this more than once for the same channel
* has no additional effect.
*
* onRegistered callback parameters:
* - {String|null} err: Encountered error, if any
* - {String} url: The push url obtained from the server
* - {String} channelID The channelID on which the notification was sent.
*
* onNotification parameters:
* - {String} version The version string received from the push server for
* the notification.
* - {String} channelID The channelID on which the notification was sent.
*
* @param {String} channelID Channel ID to use in registration.
*
* @param {Function} onRegistered Callback to be called once we are
* registered.
* NOTE: This function can be called multiple times if
* the PushServer generates new pushURLs due to
* re-registration due to network loss or PushServer
* initiated re-assignment.
* @param {Function} onNotification Callback to be called when a
* push notification is received (may be called multiple
* times).
*/
register: function register(channelID, onRegistered, onNotification) {
if (!channelID || !onRegistered || !onNotification) {
throw new Error("missing required parameter(s):" + (
channelID ? "" : " channelID") + (
onRegistered ? "" : " onRegistered") + (
onNotification ? "" : " onNotification"));}
consoleLog.info("PushHandler: channel registration: ", channelID);
if (this.channels.has(channelID)) {
// If this channel has an active registration with the PushServer
// call the onRegister callback with the URL.
if (this.registeredChannels[channelID]) {
onRegistered(null, this.registeredChannels[channelID], channelID);}
// Update the channel record.
this.channels.set(channelID, { onRegistered: onRegistered,
onNotification: onNotification });
return;}
this.channels.set(channelID, { onRegistered: onRegistered,
onNotification: onNotification });
this._channelsToRegister.push(channelID);
this._registerChannels();},
/**
* Un-register a notification channel.
*
* @param {String} channelID Notification channel ID.
*/
unregister: function unregister(channelID) {
consoleLog.info("MozLoopPushHandler: un-register channel ", channelID);
if (!this.channels.has(channelID)) {
return;}
this.channels.delete(channelID);
if (this.registeredChannels[channelID]) {
delete this.registeredChannels[channelID];
if (this.connectionState === CONNECTION_STATE_OPEN) {
this._pushSocket.send({ messageType: "unregister",
channelID: channelID });}}},
/**
* Handles the start of the websocket stream.
* Sends a hello message to the server.
*
*/
_onStart: function _onStart() {var _this4 = this;
consoleLog.info("PushHandler: websocket open, sending 'hello' to PushServer");
this.connectionState = CONNECTION_STATE_OPEN;
// If a uaID has already been assigned, assume this is a re-connect;
// send the uaID and channelIDs in order to re-synch with the
// PushServer. The PushServer does not need to accept the existing channelIDs
// and may issue new channelIDs along with new pushURLs.
this.serviceState = SERVICE_STATE_PENDING;
var helloMsg = {
messageType: "hello",
uaid: this.uaID || "",
channelIDs: this.uaID ? Object.keys(this.registeredChannels) : [] };
// The Simple PushServer spec does not allow a retry of the Hello handshake but requires that the socket
// be closed and another socket openned in order to re-attempt the handshake.
// Here, the retryManager is not set up to retry the sending another 'hello' message: the timeout will
// trigger closing the websocket and starting the connection again from the start.
this._retryManager.reset();
this._retryManager.retry(function () {return _this4._restartConnection();});
this._pushSocket.send(helloMsg);},
/**
* Handles websocket close callbacks.
*
* This method will continually try to re-establish a connection
* to the PushServer unless shutdown has been called.
*/
_onClose: function _onClose(aCode) {var _this5 = this;
this._pingMonitor.stop();
switch (this.connectionState) {
case CONNECTION_STATE_OPEN:
this.connectionState = CONNECTION_STATE_CLOSED;
consoleLog.info("PushHandler: websocket closed: begin reconnect - ", aCode);
// The first retry is immediate
this._retryManager.reset();
this._openSocket();
break;
case CONNECTION_STATE_CONNECTING:
// Wait before re-attempting to open the websocket.
consoleLog.info("PushHandler: websocket closed: delay and retry - ", aCode);
this._retryManager.retry(function () {return _this5._openSocket();});
break;}},
/**
* Listener method, called when the websocket receives a message.
*
* @param {Object} aMsg The message data
*/
_onMsg: function _onMsg(aMsg) {
// If an error property exists in the message object ignore the other
// properties.
if (aMsg.error) {
consoleLog.error("PushHandler: received error response msg: ", aMsg.error);
return;}
// The recommended response to a ping message when the push server has nothing
// else to send is a blank JSON message body: {}
if (!aMsg.messageType && this.serviceState === SERVICE_STATE_ACTIVE) {
// Treat this as a ping response
this._pingMonitor.restart();
return;}
switch (aMsg.messageType) {
case "hello":
this._onHello(aMsg);
break;
case "register":
this._onRegister(aMsg);
break;
case "notification":
this._onNotification(aMsg);
break;
default:
consoleLog.warn("PushHandler: unknown message type = ", aMsg.messageType);
if (this.serviceState === SERVICE_STATE_ACTIVE) {
// Treat this as a ping response
this._pingMonitor.restart();}
break;}},
/**
* Handles hello message.
*
* This method will parse the hello response from the PushServer
* and determine whether registration is necessary.
*
* @param {aMsg} hello message body
*/
_onHello: function _onHello(aMsg) {
if (this.serviceState !== SERVICE_STATE_PENDING) {
consoleLog.error("PushHandler: extra 'hello' response received from PushServer");
return;}
// Clear any pending timeout that will restart the connection.
this._retryManager.reset();
this.serviceState = SERVICE_STATE_ACTIVE;
consoleLog.info("PushHandler: 'hello' handshake complete");
// Start the PushServer ping monitor
this._pingMonitor.restart();
// If a new uaID is received, then any previous channel registrations
// are no longer valid and a Registration request is generated.
if (this.uaID !== aMsg.uaid) {
consoleLog.log("PushHandler: registering all channels");
this.uaID = aMsg.uaid;
// Re-register all channels.
this._channelsToRegister = [].concat(_toConsumableArray(this.channels.keys()));
this.registeredChannels = {};}
// Allow queued registrations to start (or all if cleared above).
this._registerChannels();},
/**
* Handles notification message.
*
* This method will parse the Array of updates and trigger
* the callback of any registered channel.
* This method will construct an ack message containing
* a set of channel version update notifications.
*
* @param {aMsg} notification message body
*/
_onNotification: function _onNotification(aMsg) {var _this6 = this;
if (this.serviceState !== SERVICE_STATE_ACTIVE ||
this.registeredChannels.length === 0) {
// Treat reception of a notification before handshake and registration
// are complete as a fatal error.
consoleLog.error("PushHandler: protocol error - notification received in wrong state");
this._restartConnection();
return;}
this._pingMonitor.restart();
if (Array.isArray(aMsg.updates) && aMsg.updates.length > 0) {(function () {
var ackChannels = [];
aMsg.updates.forEach(function (update) {
if (update.channelID in _this6.registeredChannels) {
consoleLog.log("PushHandler: notification: version = ", update.version,
", channelID = ", update.channelID);
_this6.channels.get(update.channelID).
onNotification(update.version, update.channelID);
ackChannels.push(update);} else
{
consoleLog.error("PushHandler: notification received for unknown channelID: ",
update.channelID);}});
consoleLog.log("PushHandler: PusherServer 'ack': ", ackChannels);
_this6._pushSocket.send({ messageType: "ack",
updates: ackChannels });})();}},
/**
* Handles the PushServer registration response.
*
* @param {Object} msg PushServer to UserAgent registration response (parsed from JSON).
*/
_onRegister: function _onRegister(msg) {var _this7 = this;
if (this.serviceState !== SERVICE_STATE_ACTIVE ||
msg.channelID != this._pendingChannelID) {
// Treat reception of a register response outside of a completed handshake
// or for a channelID not currently pending a response
// as an indication that the connections should be reset.
consoleLog.error("PushHandler: registration protocol error");
this._restartConnection();
return;}
this._retryManager.reset();
this._pingMonitor.restart();
switch (msg.status) {
case 200:
consoleLog.info("PushHandler: channel registered: ", msg.channelID);
this.registeredChannels[msg.channelID] = msg.pushEndpoint;
this.channels.get(msg.channelID).
onRegistered(null, msg.pushEndpoint, msg.channelID);
this._registerNext();
break;
case 500:
consoleLog.info("PushHandler: eeceived a 500 retry response from the PushServer: ",
msg.channelID);
// retry the registration request after a suitable delay
this._retryManager.retry(function () {return _this7._sendRegistration(msg.channelID);});
break;
case 409:
consoleLog.error("PushHandler: received a 409 response from the PushServer: ",
msg.channelID);
this.channels.get(this._pendingChannelID).onRegistered("409");
// Remove this channel from the channel list.
this.channels.delete(this._pendingChannelID);
this._registerNext();
break;
default:
consoleLog.error("PushHandler: received error ", msg.status,
" from the PushServer: ", msg.channelID);
this.channels.get(this._pendingChannelID).onRegistered(msg.status);
this.channels.delete(this._pendingChannelID);
this._registerNext();
break;}},
/**
* Attempts to open a websocket.
*
* A new websocket interface is used each time. If an onStop callback
* was received, calling asyncOpen() on the same interface will
* trigger an "already open socket" exception even though the channel
* is logically closed.
*/
_openSocket: function _openSocket() {var _this8 = this;
this.connectionState = CONNECTION_STATE_CONNECTING;
// For tests, use the mock instance.
this._pushSocket = new PushSocket(this._mockWebSocket);
consoleLog.info("PushHandler: attempt to open websocket to PushServer: ", this.pushServerUri);
this._pushSocket.connect(this.pushServerUri,
function (aMsg) {return _this8._onMsg(aMsg);},
function () {return _this8._onStart();},
function (aCode, aReason) {return _this8._onClose(aCode, aReason);});},
/**
* Closes websocket and begins re-establishing a connection with the PushServer
*/
_restartConnection: function _restartConnection() {
this._retryManager.reset();
this._pingMonitor.stop();
this.serviceState = SERVICE_STATE_OFFLINE;
this._pendingChannelID = null;
if (this.connectionState === CONNECTION_STATE_OPEN) {
// Close the current PushSocket and start the operation to open a new one.
this.connectionState = CONNECTION_STATE_CLOSED;
this._pushSocket.close();
consoleLog.warn("PushHandler: connection error: re-establishing connection to PushServer");
this._openSocket();}},
/**
* Begins registering the channelIDs with the PushServer
*/
_registerChannels: function _registerChannels() {
// Hold off registration operation until handshake is complete.
// If a registration cycle is in progress, do nothing.
if (this.serviceState !== SERVICE_STATE_ACTIVE ||
this._pendingChannelID) {
return;}
this._registerNext();},
/**
* Gets the next channel to register from the worklist and kicks of its registration
*/
_registerNext: function _registerNext() {
this._pendingChannelID = this._channelsToRegister.pop();
this._sendRegistration(this._pendingChannelID);},
/**
* Handles registering a service
*
* @param {string} channelID - identification token to use in registration for this channel.
*/
_sendRegistration: function _sendRegistration(channelID) {
if (channelID) {
this._pushSocket.send({ messageType: "register",
channelID: channelID });}} };

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

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

@ -1,169 +0,0 @@
/* 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/. */
/**
* A worker dedicated to loop-report sanitation and writing for MozLoopService.
*/
"use strict";var _slicedToArray = function () {function sliceIterator(arr, i) {var _arr = [];var _n = true;var _d = false;var _e = undefined;try {for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {_arr.push(_s.value);if (i && _arr.length === i) break;}} catch (err) {_d = true;_e = err;} finally {try {if (!_n && _i["return"]) _i["return"]();} finally {if (_d) throw _e;}}return _arr;}return function (arr, i) {if (Array.isArray(arr)) {return arr;} else if (Symbol.iterator in Object(arr)) {return sliceIterator(arr, i);} else {throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}();
importScripts("resource://gre/modules/osfile.jsm");
var Encoder = new TextEncoder();
var Counter = 0;
var MAX_LOOP_LOGS = 5;
/**
* Communications with the controller.
*
* Accepts messages:
* { path: filepath, ping: data }
*
* Sends messages:
* { ok: true }
* { fail: serialized_form_of_OS.File.Error }
*/
onmessage = function onmessage(e) {
if (++Counter > MAX_LOOP_LOGS) {
postMessage({
fail: "Maximum " + MAX_LOOP_LOGS + "loop reports reached for this session" });
return;}
var directory = e.data.directory;
var filename = e.data.filename;
var ping = e.data.ping;
// Anonymize data
resetIpMask();
ping.payload.localSdp = redactSdp(ping.payload.localSdp);
ping.payload.remoteSdp = redactSdp(ping.payload.remoteSdp);
ping.payload.log = sanitizeLogs(ping.payload.log);
var pingStr = anonymizeIPv4(sanitizeUrls(JSON.stringify(ping)));
// Save to disk
var array = Encoder.encode(pingStr);
try {
OS.File.makeDir(directory, {
unixMode: OS.Constants.S_IRWXU,
ignoreExisting: true });
OS.File.writeAtomic(OS.Path.join(directory, filename), array);
postMessage({ ok: true });}
catch (ex) {
// Instances of OS.File.Error know how to serialize themselves
if (ex instanceof OS.File.Error) {
postMessage({ fail: OS.File.Error.toMsg(ex) });} else
{
throw ex;}}};
/**
* Mask upper 24-bits of ip address with fake numbers. Call resetIpMask() first.
*/
var IpMap = {};
var IpCount = 0;
function resetIpMask() {
IpMap = {};
IpCount = Math.floor(Math.random() * 16777215) + 1;}
/**
* Masks upper 24-bits of ip address with fake numbers. Grunt function.
*
* @param {DOMString} ip address
*/
function maskIp(ip) {
var isInvalidOrRfc1918or3927 = function isInvalidOrRfc1918or3927(p1, p2, p3, p4) {
var invalid = function invalid(octet) {return octet < 0 || octet > 255;};
return invalid(p1) || invalid(p2) || invalid(p3) || invalid(p4) ||
p1 == 10 ||
p1 == 172 && p2 >= 16 && p2 <= 31 ||
p1 == 192 && p2 == 168 ||
p1 == 169 && p2 == 254;};var _ip$split =
ip.split(".");var _ip$split2 = _slicedToArray(_ip$split, 4);var p1 = _ip$split2[0];var p2 = _ip$split2[1];var p3 = _ip$split2[2];var p4 = _ip$split2[3];
if (isInvalidOrRfc1918or3927(p1, p2, p3, p4)) {
return ip;}
var key = [p1, p2, p3].join();
if (!IpMap[key]) {
do {
IpCount = (IpCount + 1049039) % 16777216; // + prime % 2^24
p1 = (IpCount >> 16) % 256;
p2 = (IpCount >> 8) % 256;
p3 = IpCount % 256;} while (
isInvalidOrRfc1918or3927(p1, p2, p3, p4));
IpMap[key] = p1 + "." + p2 + "." + p3;}
return IpMap[key] + "." + p4;}
/**
* Partially masks ip numbers in input text.
*
* @param {DOMString} text Input text containing IP numbers as words.
*/
function anonymizeIPv4(text) {
return text.replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g,
maskIp.bind(this));}
/**
* Sanitizes any urls of session information, like
*
* - (id=31 url=https://call.services.mozilla.com/#call/ongoing/AQHYjqH_...)
* + (id=31 url=https://call.services.mozilla.com/#call/xxxx)
*
* - (id=35 url=about:loopconversation#incoming/1403134352854)
* + (id=35 url=about:loopconversation#incoming/xxxx)
*
* - (id=35 url=about:loopconversation#1403134352854)
* + (id=35 url=about:loopconversation#/xxxx)
*
* @param {DOMString} text The text.
*/
function sanitizeUrls(text) {
var trimUrl = function trimUrl(url) {return url.replace(/(#call|#incoming|#).*/g,
function (match, type) {return type + "/xxxx";});};
return text.replace(/\(id=(\d+) url=([^\)]+)\)/g,
function (match, id, url) {return (
"(id=" + id + " url=" + trimUrl(url) + ")");});}
/**
* Removes privacy sensitive information from SDP input text outright, like
*
* a=fingerprint:sha-256 E9:DE:6A:FE:2A:2F:05: etc.
* a=identity ...
*
* Redacts lines from match to EOL. Assumes \r\n\ linebreaks.
*
* @param {DOMString} sdp The sdp text.
*/
var redactSdp = function redactSdp(sdp) {return sdp.replace(/\r\na=(fingerprint|identity):.*?\r\n/g,
"\r\n");};
/**
* Sanitizes log text of sensitive information, like
*
* - srflx(IP4:192.168.1.3:60348/UDP|turn402-oak.tokbox.com:3478)
* + srflx(IP4:192.168.1.3:60348/UDP|xxxx.xxx)
*
* @param {DOMString} log The log text.
*/
function sanitizeLogs(log) {
var rex = /(srflx|relay)\(IP4:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}\/(UDP|TCP)\|[^\)]+\)/g;
return log.replace(rex, function (match) {return match.replace(/\|[^\)]+\)/, "|xxxx.xxx)");});}

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

@ -1,20 +0,0 @@
/* 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";
/* global sendAsyncMessage */
/**
* This script runs in the content process and is attached to browsers when
* they are created.
*/
// Listen for when the title is changed and send a message back to the chrome
// process.
addEventListener("DOMTitleChanged", function (_ref) {var target = _ref.target;
sendAsyncMessage("loop@mozilla.org:DOMTitleChanged", {
details: "titleChanged" },
{
target: target });});

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

@ -1,57 +0,0 @@
<!DOCTYPE html>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html>
<head>
<meta charset="utf-8">
<!-- Title is set in conversation.js -->
<title></title>
<base href="chrome://loop/content">
<link rel="stylesheet" type="text/css" href="shared/css/reset.css">
<link rel="stylesheet" type="text/css" href="shared/css/common.css">
<link rel="stylesheet" type="text/css" href="panels/css/desktop.css">
<link rel="stylesheet" type="text/css" href="shared/css/conversation.css">
</head>
<body class="fx-embedded">
<div id="messages"></div>
<div id="main"></div>
<script type="text/javascript" src="panels/vendor/l10n.js"></script>
<script type="text/javascript" src="panels/js/otconfig.js"></script>
<script type="text/javascript" src="shared/vendor/sdk.js"></script>
<script type="text/javascript" src="shared/vendor/react.js"></script>
<script type="text/javascript" src="shared/vendor/react-dom.js"></script>
<script type="text/javascript" src="shared/vendor/lodash.js"></script>
<script type="text/javascript" src="shared/vendor/backbone.js"></script>
<script type="text/javascript" src="shared/vendor/classnames.js"></script>
<script type="text/javascript" src="shared/js/loopapi-client.js"></script>
<script type="text/javascript" src="shared/js/utils.js"></script>
<script type="text/javascript" src="shared/js/urlRegExps.js"></script>
<script type="text/javascript" src="shared/js/mixins.js"></script>
<script type="text/javascript" src="shared/js/actions.js"></script>
<script type="text/javascript" src="shared/js/validate.js"></script>
<script type="text/javascript" src="shared/js/dispatcher.js"></script>
<script type="text/javascript" src="shared/js/otSdkDriver.js"></script>
<!-- Stores need to be loaded before the views that uses them -->
<script type="text/javascript" src="shared/js/store.js"></script>
<script type="text/javascript" src="panels/js/roomStore.js"></script>
<script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
<script type="text/javascript" src="panels/js/conversationAppStore.js"></script>
<script type="text/javascript" src="shared/js/textChatStore.js"></script>
<script type="text/javascript" src="shared/js/remoteCursorStore.js"></script>
<!-- Views -->
<script type="text/javascript" src="shared/js/views.js"></script>
<script type="text/javascript" src="shared/js/textChatView.js"></script>
<script type="text/javascript" src="shared/js/linkifiedTextView.js"></script>
<script type="text/javascript" src="panels/js/desktopViews.js"></script>
<script type="text/javascript" src="panels/js/feedbackViews.js"></script>
<script type="text/javascript" src="panels/js/roomViews.js"></script>
<script type="text/javascript" src="panels/js/conversation.js"></script>
</body>
</html>

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

@ -1,23 +0,0 @@
<!DOCTYPE html>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html>
<head>
<meta charset="utf-8">
<base href="chrome://loop/content">
<link rel="stylesheet" type="text/css" href="shared/css/reset.css">
<link rel="stylesheet" type="text/css" href="shared/css/common.css">
<link rel="stylesheet" type="text/css" href="panels/css/copy.css">
</head>
<body class="panel">
<div id="main"></div>
<script type="text/javascript" src="panels/vendor/l10n.js"></script>
<script type="text/javascript" src="shared/vendor/react.js"></script>
<script type="text/javascript" src="shared/vendor/react-dom.js"></script>
<script type="text/javascript" src="panels/js/copy.js"></script>
</body>
</html>

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

@ -1,49 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
html {
font-family: menu;
font-size: 10px;
}
.copy-body {
display: flex;
padding: 1.5rem;
}
.copy-logo {
height: 32px;
margin-inline-end: 1.5rem;
}
.copy-message {
color: #000;
font-size: 1.2rem;
}
.copy-toggle-label {
display: block;
margin-top: 1.5rem;
}
.copy-toggle-label input {
margin-inline-start: 0;
}
.copy-button {
background: #efefef;
border: 0;
line-height: 1rem;
outline: 1px solid #d1d1d1;
padding: 1.5rem;
width: 50%;
}
.copy-button:active,
.copy-button:focus,
.copy-button:hover {
background: #00a9dc;
color: #fff;
outline-color: #0097c5;
}

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

@ -1,210 +0,0 @@
/* 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/. */
.panel-content.showing-share-panel,
.panel-content.showing-share-panel > .panel-container {
min-height: 220px;
}
/* Room share panel */
.room-invitation-overlay {
position: absolute;
background: rgba(255, 255, 255, 0.85);
top: 0;
height: 100%;
right: 0;
left: 0;
color: #000;
z-index: 1010;
display: flex;
flex-flow: column nowrap;
align-items: stretch;
}
.room-invitation-content {
display: flex;
flex-flow: column nowrap;
margin: 12px 0;
font-size: 1.4rem;
}
.room-invitation-content > * {
margin: 0 15px;
}
.room-context-header {
font-weight: bold;
font-size: 1.6rem;
margin-bottom: 10px;
text-align: center;
}
/* Input Button Combo group */
.input-button-content {
margin: 0 15px 10px 15px;
min-width: 64px;
border-radius: 4px;
}
.input-button-group-label {
color: #898a8a;
margin: 0 15px;
margin-bottom: 2px;
font-size: 1.2rem;
}
.input-button-content > * {
width: 100%;
padding: 0 4px;
}
.input-button-content > .input-group input {
font-size: 1.4rem;
padding: 0.7rem;
width: 100%;
border: 0;
}
.input-button-content > .group-item-top {
border: 1px solid #d2cece;;
border-radius: 4px 4px 0 0;
border-bottom: 0;
}
.input-button-content > .group-item-bottom {
border-radius: 0 0 4px 4px;
}
.input-button-content > .input-group {
background: #FFF;
}
.input-button-content > .invite-button {
background: #00a9dc;
height: 34px;
text-align: center;
display: flex;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
}
.input-button-content > .invite-button.triggered {
background-color: #00a9dc;
}
.input-button-content > .invite-button:hover {
background-color: #008ACB;
}
.share-action-group {
display: flex;
padding: 0 15px;
width: 100%;
flex-wrap: nowrap;
flex-direction: row;
justify-content: center;
}
.share-action-group > .invite-button {
cursor: pointer;
height: 34px;
border-radius: 4px;
background-color: #ebebeb;
display: flex;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
flex-grow: 1;
margin-right: 20px;
}
html[dir="rtl"] .share-action-group > .invite-button {
margin-left: 20px;
margin-right: initial;
}
.share-action-group > .invite-button:last-child {
margin-right: 0;
}
html[dir="rtl"] .share-action-group > .invite-button:last-child {
margin-left: 0;
}
.share-action-group > .invite-button:hover {
background-color: #d4d4d4;
}
.share-action-group > .invite-button.triggered {
background-color: #d4d4d4;
}
.share-action-group > .invite-button > img {
height: 28px;
width: 28px;
}
.share-action-group > .invite-button > div {
display: inline;
color: #4a4a4a;
}
.share-panel-container {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 999;
}
.share-panel-container > .share-panel-overlay {
background-color: rgba(0, 0, 0, .3);
bottom: 0;
display: none;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.share-panel-container > .room-invitation-overlay {
background-color: #fff;
flex: 1;
flex-flow: column nowrap;
justify-content: center;
transform: translateX(100%);
transition: transform ease 300ms;
width: 294px;
right: 0;
left: initial;
}
html[dir="rtl"] .share-panel-container > .room-invitation-overlay {
left: 0;
right: initial;
transform: translate(-100%);
}
.share-panel-container > .room-invitation-overlay > .room-invitation-content {
margin: 0 0 12px;
}
.share-panel-open > .room-invitation-overlay,
html[dir="rtl"] .share-panel-open > .room-invitation-overlay {
transform: translateX(0);
}
.share-panel-open > .share-panel-overlay {
display: block;
}
.share-panel-container > .room-invitation-overlay > .room-invitation-content > .room-context-header {
text-align: left;
}
html[dir="rtl"] .share-panel-container > .room-invitation-overlay > .room-invitation-content > .room-context-header {
text-align: right;
}

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

@ -1,866 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
html {
font-size: 10px;
font-family: menu;
}
body {
background: none;
}
/* Beta Ribbon */
.beta-ribbon {
background: url("../../shared/img/beta-ribbon.svg") no-repeat;
background-size: 30px;
width: 30px;
height: 30px;
}
/* Panel styles */
.panel {
/* hide the extra margin space that the panel resizer now wants to show */
overflow: hidden;
font: menu;
background-color: #fbfbfb;
width: 330px;
}
/* Panel container */
.panel-content {
width: 330px;
display: block;
}
.panel-content > .beta-ribbon {
position: fixed;
left: 0;
top: 0;
z-index: 1000;
}
.panel-container {
flex: 1;
display: flex;
flex-flow: column nowrap;
width: 100%;
height: 100%;
position: relative;
}
/* Notifications displayed over tabs */
.panel .messages {
width: 100%;
margin: 0;
}
.panel .messages .alert {
margin: 0;
}
/* Sign-in request view */
.sign-in-request {
text-align: center;
vertical-align: middle;
margin: 2em 0;
}
.sign-in-request > h1 {
font-size: 1.7em;
margin-bottom: .2em;
}
.sign-in-request > h2,
.sign-in-request > a {
font-size: 1.2em;
}
.sign-in-request > a {
cursor: pointer;
color: #0295df;
}
.sign-in-request > a:hover:active {
text-decoration: underline;
}
.sign-in-request-button {
font-size: 1rem;
margin: 1rem;
width: 80%;
padding: .5rem 1rem;
border-radius: 3px;
}
/* Rename closing room panel */
.rename-newRoom > img {
margin-left: 16px;
position: absolute;
width: 32px;
}
html[dir="rtl"] .rename-newRoom > img {
margin-left: 0;
margin-right: 16px;
}
.rename-container {
margin: 16px;
/* margin is img-width + 16px separation */
padding-left: 48px;
}
html[dir="rtl"] .rename-container {
padding-left: 0;
padding-right: 48px;
}
.rename-container > .rename-header {
color: #000;
font-size: 1.2em;
padding-top: 5px;
}
.rename-container > .rename-prompt {
margin-bottom: 8px;
}
.rename-container > .input-group {
background: #fff;
border: 1px solid #d1d1d1;
border-radius: 2px;
margin-left: -2px;
width: 100%;
padding: 2px 4px;
}
html[dir="rtl"] .rename-container > .input-group {
margin-left: 0;
margin-right: -2px;
}
.rename-container > .input-group.focused {
border: 1px solid #0078f9;
box-shadow: 0 0 0 3px #9eccfe;
}
.input-group > .rename-input {
border: 0px none;
font-size: 1.4rem;
width: 100%;
}
.rename-button {
background: #efefef;
border-top: 1px solid #d1d1d1;
display: block;
padding: 12px;
width: 100%;
}
/* Content area and input fields */
.content-area {
padding: .5rem 1rem;
}
.content-area header {
font-weight: 700;
}
/* Need to remove when these rules when the Beta tag is removed */
#share-link-header {
padding-inline-start: 20px;
}
.fte-get-started-container + .generate-url > #share-link-header {
/* The header shouldn't be indented if the tabs are present. */
padding-inline-start: 0;
}
.content-area label {
display: block;
width: 100%;
margin-top: 10px;
font-size: 1rem;
color: #777;
}
.content-area input {
display: block;
width: 100%;
outline: none;
border-radius: 4px;
margin: 10px 0;
border: 1px solid #c3c3c3;
height: 2.6rem;
padding: 0 6px;
font-size: 1.1rem;
color: #4a4a4a;
box-shadow: none;
}
.content-area input::-moz-placeholder {
color: #999;
}
.content-area input:not(.pristine):invalid {
border: 0.1rem solid #d13f1a;
}
.content-area input:focus {
border: 0.1rem solid #5cccee;
}
/* Rooms CSS */
.room-list-loading {
text-align: center;
padding-top: 5px;
padding-bottom: 5px;
}
.room-list-loading > img {
width: 66px;
}
/* Rooms */
.rooms {
display: block;
width: 100%;
flex-grow: 2;
}
.rooms > .fte-get-started-content {
padding-top: 2.4rem;
}
.rooms > h1 {
color: #666;
font-size: 1rem;
padding: .5rem 15px;
height: 3rem;
line-height: 3rem;
}
.new-room-view {
display: flex;
flex-direction: column;
}
.new-room-view + h1 {
border-top: 1px solid #d8d8d8;
}
.new-room-view > .btn {
border-radius: 5px;
font-size: 1.2rem;
font-weight: bold;
margin: 1.5rem;
padding: 1rem;
}
.new-room-view > .stop-sharing-button {
background-color: #d13f1a;
border-color: #d13f1a;
}
.new-room-view > .stop-sharing-button:hover {
background-color: #ef6745;
border-color: #ef6745;
}
.room-list {
overflow-y: auto;
overflow-x: hidden;
flex-flow: column nowrap;
width: 100%;
/* max number of rooms shown @ 28px */
max-height: calc(5 * 28px);
/* min-height because there is a browser min-height on panel */
min-height: 53px;
padding-top: 4px;
transition: opacity .2s ease-in-out .2s;
}
.room-list-empty {
display: inline-block;
/* min height so empty empty room list panel is same height as loading and
room-list panels */
min-height: 80px;
}
/* Adds blur to bottom of room-list to indicate more items in list */
.room-list-blur {
/* height of room-list item */
height: 2.4rem;
/* -15px for scrollbar */
width: calc(100% - 15px);
/* At same DOM level as room-list, positioned after and moved to bottom of room-list */
position: absolute;
margin-top: -2.4rem;
/* starts gradient at about the 50% mark on the text, eyeballed the percentages */
background: linear-gradient(
to bottom,
rgba(251, 251, 251, 0) 0%,
rgba(251, 251, 251, 0) 38%, /* Because of padding and lineheight, start gradient at 38% */
rgba(251, 251, 251, 1) 65%, /* and end around 65% */
rgba(251, 251, 251, 1) 100%
);
pointer-events: none;
}
.room-list > .room-entry {
padding: .2rem 15px;
/* Always show the default pointer, even over the text part of the entry. */
cursor: default;
}
/* Adds margin to the last room-entry to make gradient fade disappear for last item */
.room-list-add-space > .room-entry:last-child {
margin-bottom: 8px;
}
/* Disable interacting with the room list when creating */
.room-list-pending-creation {
opacity: .5;
pointer-events: none;
}
.room-list > .room-entry > h2 {
display: inline-block;
vertical-align: middle;
/* See .room-entry-context-item for the margin/size reductions.
* An extra 16px to make space for the edit button. */
width: calc(100% - 1rem - 32px);
font-size: 1.3rem;
line-height: 2.4rem;
color: #000;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.room-list > .room-entry > input {
display: inline-block;
vertical-align: middle;
/* See .room-entry-context-item for the margin/size reductions. */
width: calc(100% - 1rem - 16px);
font-size: 1.3rem;
}
.room-list > .room-entry.room-active:not(.room-opened) > h2 {
font-weight: bold;
}
.room-list > .room-entry:not(.room-opened):hover {
background: #dbf7ff;
}
.room-list > .room-entry > p {
margin: 0;
padding: .2rem 0;
}
.room-list > .room-entry > p > a {
color: #777;
opacity: .5;
transition: opacity .1s ease-in-out 0s;
text-decoration: none;
}
.room-list > .room-entry > p > a:hover {
opacity: 1;
text-decoration: underline;
}
@keyframes drop-and-fade-in {
0% {opacity: 0; top: -15px;}
25% {opacity: 0; top: -15px;}
100% {opacity: 1; top: 0;}
}
.room-list > .room-entry > h2 > button {
display: inline-block;
position: relative;
width: 24px;
height: 24px;
border: none;
margin: .1em; /* relative to _this_ line's font, not the document's */
margin-inline-start: .5em;
background-color: transparent; /* override browser default for button tags */
top: -15px;
}
.room-list > .room-entry:hover > h2 > button {
animation: drop-and-fade-in 0.250s;
animation-fill-mode: forwards;
}
.room-list > .room-entry:hover > h2 > .copy-link {
background-image: url(../../shared/img/icons-16x16.svg#copy);
}
.room-list > .room-entry:hover > h2 > .delete-link {
background-image: url(../../shared/img/icons-16x16.svg#trash);
}
/* scale this up to 1.1x and then back to the original size */
@keyframes pulse {
0%, 100% { transform: scale(1.0); }
50% { transform: scale(1.1); }
}
.room-list > .room-entry > h2 > .copy-link.checked {
background: transparent url(../../shared/img/icons-16x16.svg#checkmark);
animation: pulse .150s;
animation-timing-function: ease-in-out;
top: 0;
}
/* Room entry context button (edit button) */
.room-entry-context-actions {
display: none;
vertical-align: top;
}
.room-entry:hover .room-entry-context-actions {
display: inline-block;
}
/* Room entry edit button */
.room-entry-context-edit-btn {
background-image: url("../../shared/img/icons-10x10.svg#edit-darkgrey");
background-position: center;
background-repeat: no-repeat;
background-size: 12px;
cursor: pointer;
display: inline-block;
height: 24px;
vertical-align: middle;
width: 16px;
}
html[dir="rtl"] .room-entry-context-actions > .dropdown-menu {
right: auto;
left: 21px;
}
.room-entry-context-actions > .dropdown-menu {
right: 21px;
bottom: auto;
left: auto;
z-index: 1;
}
/* Keep ".room-list > .room-entry > h2" in sync with these. */
.room-entry-context-item {
display: inline-block;
margin-inline-end: 1rem;
vertical-align: middle;
}
.room-entry-context-item > img,
.room-entry-context-item > a > img {
width: 16px;
}
.button-close {
background-color: transparent;
background-image: url(../../shared/img/icons-10x10.svg#close);
background-repeat: no-repeat;
background-size: 8px 8px;
border: none;
padding: 0;
height: 8px;
width: 8px;
}
.button-close:hover,
.button-close:hover:active {
background-color: transparent;
border: none;
}
/* Spinner */
@keyframes spinnerRotate {
to { transform: rotate(360deg); }
}
.spinner {
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-size: 16px 16px;
}
.spinner.busy {
background-image: url(../../shared/img/spinner.png);
animation-name: spinnerRotate;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@media (min-resolution: 2dppx) {
.spinner.busy {
background-image: url(../../shared/img/spinner@2x.png);
}
}
/* Share tab */
.generate-url-stack {
margin: 14px 0;
position: relative;
}
.generate-url-input {
outline: 0;
border: 1px solid #ccc; /* Overriding background style for a text input (see
below) resets its borders to a weird beveled style;
defining a default 1px border solves the issue. */
border-radius: 2px;
height: 26px;
padding: 0 10px;
font-size: 1em;
}
.generate-url-spinner {
position: absolute;
pointer-events: none;
z-index: 1;
top: 4px;
left: auto;
right: 4px;
}
html[dir="rtl"] .generate-url-spinner {
left: 4px;
right: auto;
}
.generate-url .button {
background-color: #00a9dc;
border-color: #0096dd;
color: #fff;
}
.generate-url .button:hover {
background-color: #008acb;
border-color: #008acb;
}
#powered-by,
.terms-service {
color: #4a4a4a;
font-size: 1.2rem;
}
#powered-by {
border-top: 1px solid #ccc;
padding-top: 1.5rem;
text-align: center;
margin-top: 0;
}
#powered-by-logo {
display: inline-block;
margin-left: 10px;
margin-right: 10px;
vertical-align: middle;
background-image: url("../../shared/img/telefonica-logo.svg");
width: 84px;
height: 23px;
background-repeat: no-repeat;
}
#powered-by-logo.en-GB,
#powered-by-logo.de {
background-image: url("../../shared/img/02.png");
background-size: 21px 20px;
width: 21px;
height: 20px;
}
#powered-by-logo.pt-BR {
background-image: url("../../shared/img/vivo.png");
background-size: 53px 26px;
width: 53px;
height: 26px;
}
#powered-by-logo[class^="es-"] {
background-image: url("../../shared/img/movistar.png");
background-size: 92px 20px;
width: 92px;
height: 20px;
}
@media (min-resolution: 2dppx) {
#powered-by-logo.en-GB,
#powered-by-logo.de {
background-image: url("../../shared/img/02@2x.png");
}
#powered-by-logo.pt-BR {
background-image: url("../../shared/img/vivo@2x.png");
}
#powered-by-logo[class^="es-"] {
background-image: url("../../shared/img/movistar@2x.png");
}
}
.terms-service {
padding-left: 5rem;
padding-right: 5rem;
padding-bottom: 1rem;
text-align: center;
}
.terms-service > a {
color: #00a9dc;
text-decoration: none;
}
/* Status badges -- Available/Unavailable */
.status {
display: inline-block;
width: 8px;
height: 8px;
margin: 0 5px;
border-radius: 50%;
}
/* Sign in/up link */
.signin-link {
flex: 1;
margin: 0;
}
.signin-link > a {
font-weight: 500;
text-decoration: none;
color: #00A9DC;
}
/* Settings (gear) menu */
.button-settings {
width: 14px;
height: 14px;
margin: 0;
padding: 0;
border: none;
cursor: pointer;
vertical-align: middle;
background: transparent url("../../shared/img/icons-10x10.svg#settings-cog");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
.settings-menu .dropdown-menu {
/* The panel can't have dropdown menu overflowing its iframe boudaries;
let's anchor it from the bottom-right, while resetting the top & left values
set by .dropdown-menu */
top: auto;
left: auto;
bottom: 1.1em;
right: 14px;
}
.entries-divider {
border-left: 0;
border-right: 0;
border-bottom: solid 1px #d8d8d8;
}
html[dir="rtl"] .settings-menu .dropdown-menu {
/* This is specified separately rather than using margin-inline-start etc, as
we need to override .dropdown-menu's values which can't use the gecko
specific extensions. */
left: 14px;
right: auto;
}
/* Footer */
.footer {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
align-content: stretch;
align-items: center;
font-size: 1rem;
color: #666666;
padding: 10px 15px 6px 15px;
width: 100%;
height: 42px;
}
.footer > .signin-details {
align-items: center;
}
.footer > .user-identity {
flex: 1;
color: #000;
font-weight: bold;
margin: 0;
margin-inline-end: 1rem;
overflow-x: hidden;
text-overflow: ellipsis;
}
/* First time use */
.fte-get-started-content {
/* Manual vertical centering */
padding: 2rem 0 0;
}
.fte-title {
margin: 0 20px;
}
.fte-title > img {
width: 100%;
}
img.fte-logo {
height: 32px;
/* Setting svg img to display:block fixed svg/panel sizing issue */
display: block;
}
.fte-subheader, .fte-content {
font-size: 1.6rem;
text-align: center;
margin: 2rem auto;
color: #4a4a4a;
line-height: 2.4rem;
}
.fte-content {
font-size: 1.5rem;
font-weight: 500;
color: #9a9a9a;
}
.fte-separator {
/* Constrain separator width with magic number. */
width: 164px;
border: solid #c5c5c5 1px;
height: 1px;
margin: 0 auto;
}
img.fte-hello-web-share {
/* Set custom SVG size to align with powered-by-wrapper element. */
width: 290px;
height: 168px;
/* Setting svg img to display:block fixed svg/panel sizing issue */
display: block;
}
.fte-get-started-content + .powered-by-wrapper {
width: 100%;
background: #ebebeb;
}
.fte-get-started-content + .powered-by-wrapper > #powered-by {
border: none;
}
.fte-get-started-container {
display: flex;
flex-flow: column nowrap;
background: #fbfbfb;
}
.fte-get-started-content-borders {
border-top: 1px solid #D8D8D8;
border-bottom: 1px solid #D8D8D8;
}
.fte-button-container {
border-top: 1px solid #ccc;
padding: 8px 8px 0;
background: #ebebeb;
display: flex;
flex-direction: column;
}
.fte-get-started-button {
border: none;
color: #fff;
background-color: #00a9dc;
line-height: 43px;
margin: 10px;
padding: 0;
border-radius: 4px;
font-size: 1.4rem;
font-weight: bold;
}
.fte-get-started-button:hover,
.fte-get-started-button:focus,
.fte-get-started-button:active {
background-color: #5cccee;
color: #fff;
}
/* E10s not supported */
.error-content {
/* Manual vertical centering */
flex: 1;
padding: 3.5rem 0 1.5rem 0;
display: flex;
flex-direction: column;
}
.error-title {
margin: 0 15px;
text-align: center;
}
.error-title > img {
width: 72px;
}
.error-subheader {
text-align: center;
font-size: 1.6rem;
margin: 2.5rem 0;
color: #4a4a4a;
line-height: 2.2rem;
}
.e10s-not-supported-button {
border: none;
color: #fff;
background-color: #00a9dc;
line-height: 43px;
margin: 0 15px;
padding: 0;
border-radius: 4px;
font-size: 1.4rem;
font-weight: bold;
}
.e10s-not-supported-button:hover,
.e10s-not-supported-button:focus,
.e10s-not-supported-button:active {
background-color: #5cccee;
color: #fff;
}

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

@ -1,97 +0,0 @@
/* 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/. */
.slideshow {
-moz-user-select: none;
cursor: default;
}
/* slide content */
.slide > div {
height: 100%;
width: 100%;
}
.slide-layout {
padding-top: 90px;
padding-left: 50px;
padding-bottom: 20px;
width: 590px;
}
.slide-layout > h2 {
font-family: sans-serif;
font-size: 2.5rem;
font-weight: 600;
white-space: normal;
color: #fff;
line-height: 2.8rem;
margin-bottom: 1.4rem;
}
.slide-layout > .slide-text {
font-family: sans-serif;
font-size: 1.8rem;
font-weight: 400;
white-space: normal;
color: #fff;
line-height: 2.8rem;
}
.slide-layout > img {
display: block;
-moz-box-sizing: border-box;
box-sizing: border-box;
border: none;
background-color: transparent;
background-repeat: no-repeat;
background-position: center;
padding: 0;
float: right;
margin-top: -40px;
}
.slide1 {
background: #06a6e0;
}
.slide1-image {
background-image: url(../../shared/img/firefox-hello_tour-slide-01.svg);
height: 260px;
width: 295px;
background-size: 310px 310px;
}
.slide2 {
background: #0183b2;
}
.slide2-image {
background-image: url(../../shared/img/firefox-hello_tour-slide-02.svg);
height: 245px;
width: 245px;
background-size: 320px 320px;
}
.slide3 {
background: #005da5;
}
.slide3-image {
background-image: url(../../shared/img/firefox-hello_tour-slide-03.svg);
height: 260px;
width: 260px;
background-size: 310px 310px;
}
.slide4 {
background: #7ec24c;
}
.slide4-image {
background-image: url(../../shared/img/firefox-hello_tour-slide-04.svg);
height: 240px;
width: 240px;
background-size: 320px 320px;
}

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

@ -1,240 +0,0 @@
"use strict"; /* 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/. */
var loop = loop || {};
loop.conversation = function (mozL10n) {
"use strict";
var sharedMixins = loop.shared.mixins;
var sharedActions = loop.shared.actions;
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
var FeedbackView = loop.feedbackViews.FeedbackView;
var RoomFailureView = loop.roomViews.RoomFailureView;
/**
* Master controller view for handling if incoming or outgoing calls are
* in progress, and hence, which view to display.
*/
var AppControllerView = React.createClass({ displayName: "AppControllerView",
mixins: [
Backbone.Events,
loop.store.StoreMixin("conversationAppStore"),
sharedMixins.DocumentTitleMixin,
sharedMixins.WindowCloseMixin],
propTypes: {
cursorStore: React.PropTypes.instanceOf(loop.store.RemoteCursorStore).isRequired,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore) },
componentWillMount: function componentWillMount() {
this.listenTo(this.props.cursorStore, "change:remoteCursorPosition",
this._onRemoteCursorPositionChange);
this.listenTo(this.props.cursorStore, "change:remoteCursorClick",
this._onRemoteCursorClick);},
_onRemoteCursorPositionChange: function _onRemoteCursorPositionChange() {
loop.request("AddRemoteCursorOverlay",
this.props.cursorStore.getStoreState("remoteCursorPosition"));},
_onRemoteCursorClick: function _onRemoteCursorClick() {
var click = this.props.cursorStore.getStoreState("remoteCursorClick");
// if the click is 'false', assume it is a storeState reset,
// so don't do anything
if (!click) {
return;}
this.props.cursorStore.setStoreState({
remoteCursorClick: false });
loop.request("ClickRemoteCursor", click);},
getInitialState: function getInitialState() {
return this.getStoreState();},
_renderFeedbackForm: function _renderFeedbackForm() {
this.setTitle(mozL10n.get("conversation_has_ended"));
return React.createElement(FeedbackView, {
onAfterFeedbackReceived: this.closeWindow });},
/**
* We only show the feedback for once every 6 months, otherwise close
* the window.
*/
handleCallTerminated: function handleCallTerminated() {
this.props.dispatcher.dispatch(new sharedActions.LeaveConversation());},
render: function render() {
if (this.state.showFeedbackForm) {
return this._renderFeedbackForm();}
switch (this.state.windowType) {
case "room":{
return React.createElement(DesktopRoomConversationView, {
chatWindowDetached: this.state.chatWindowDetached,
cursorStore: this.props.cursorStore,
dispatcher: this.props.dispatcher,
facebookEnabled: this.state.facebookEnabled,
onCallTerminated: this.handleCallTerminated,
roomStore: this.props.roomStore });}
case "failed":{
return React.createElement(RoomFailureView, {
dispatcher: this.props.dispatcher,
failureReason: FAILURE_DETAILS.UNKNOWN });}
default:{
// If we don't have a windowType, we don't know what we are yet,
// so don't display anything.
return null;}}} });
/**
* Conversation initialisation.
*/
function init() {
// Obtain the windowId and pass it through
var locationHash = loop.shared.utils.locationData().hash;
var windowId;
var hash = locationHash.match(/#(.*)/);
if (hash) {
windowId = hash[1];}
var requests = [
["GetAllConstants"],
["GetAllStrings"],
["GetLocale"],
["GetLoopPref", "ot.guid"],
["GetLoopPref", "feedback.periodSec"],
["GetLoopPref", "feedback.dateLastSeenSec"],
["GetLoopPref", "facebook.enabled"]];
var prefetch = [
["GetConversationWindowData", windowId]];
return loop.requestMulti.apply(null, requests.concat(prefetch)).then(function (results) {
// `requestIdx` is keyed off the order of the `requests` and `prefetch`
// arrays. Be careful to update both when making changes.
var requestIdx = 0;
var constants = results[requestIdx];
// Do the initial L10n setup, we do this before anything
// else to ensure the L10n environment is setup correctly.
var stringBundle = results[++requestIdx];
var locale = results[++requestIdx];
mozL10n.initialize({
locale: locale,
getStrings: function getStrings(key) {
if (!(key in stringBundle)) {
console.error("No string found for key: ", key);
return "{ textContent: '' }";}
return JSON.stringify({ textContent: stringBundle[key] });} });
// Plug in an alternate client ID mechanism, as localStorage and cookies
// don't work in the conversation window
var currGuid = results[++requestIdx];
window.OT.overrideGuidStorage({
get: function get(callback) {
callback(null, currGuid);},
set: function set(guid, callback) {
// See nsIPrefBranch
var PREF_STRING = 32;
currGuid = guid;
loop.request("SetLoopPref", "ot.guid", guid, PREF_STRING);
callback(null);} });
var dispatcher = new loop.Dispatcher();
var sdkDriver = new loop.OTSdkDriver({
constants: constants,
isDesktop: true,
useDataChannels: true,
dispatcher: dispatcher,
sdk: OT });
// Create the stores.
var activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, {
isDesktop: true,
sdkDriver: sdkDriver });
var conversationAppStore = new loop.store.ConversationAppStore(dispatcher, {
activeRoomStore: activeRoomStore,
feedbackPeriod: results[++requestIdx],
feedbackTimestamp: results[++requestIdx],
facebookEnabled: results[++requestIdx] });
prefetch.forEach(function (req) {
req.shift();
loop.storeRequest(req, results[++requestIdx]);});
var roomStore = new loop.store.RoomStore(dispatcher, {
activeRoomStore: activeRoomStore,
constants: constants });
var textChatStore = new loop.store.TextChatStore(dispatcher, {
sdkDriver: sdkDriver });
var remoteCursorStore = new loop.store.RemoteCursorStore(dispatcher, {
sdkDriver: sdkDriver });
loop.store.StoreMixin.register({
conversationAppStore: conversationAppStore,
remoteCursorStore: remoteCursorStore,
textChatStore: textChatStore });
ReactDOM.render(
React.createElement(AppControllerView, {
cursorStore: remoteCursorStore,
dispatcher: dispatcher,
roomStore: roomStore }), document.querySelector("#main"));
document.documentElement.setAttribute("lang", mozL10n.language.code);
document.documentElement.setAttribute("dir", mozL10n.language.direction);
document.body.setAttribute("platform", loop.shared.utils.getPlatform());
dispatcher.dispatch(new sharedActions.GetWindowData({
windowId: windowId }));
loop.request("TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", constants.LOOP_MAU_TYPE.OPEN_CONVERSATION);});}
return {
AppControllerView: AppControllerView,
init: init };}(
document.mozL10n);
document.addEventListener("DOMContentLoaded", loop.conversation.init);

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

@ -1,205 +0,0 @@
"use strict"; /* 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/. */
var loop = loop || {};
loop.store = loop.store || {};
/**
* Manages the conversation window app controller view. Used to get
* the window data and store the window type.
*/
loop.store.ConversationAppStore = function () {
"use strict";
/**
* Constructor
*
* @param {Object} options Options for the store. Should contain the
* activeRoomStore and dispatcher objects.
*/
var ConversationAppStore = loop.store.createStore({
initialize: function initialize(options) {
if (!options.activeRoomStore) {
throw new Error("Missing option activeRoomStore");}
if (!("feedbackPeriod" in options)) {
throw new Error("Missing option feedbackPeriod");}
if (!("feedbackTimestamp" in options)) {
throw new Error("Missing option feedbackTimestamp");}
this._activeRoomStore = options.activeRoomStore;
this._facebookEnabled = options.facebookEnabled;
this._feedbackPeriod = options.feedbackPeriod;
this._feedbackTimestamp = options.feedbackTimestamp;
this._rootObj = "rootObject" in options ? options.rootObject : window;
this._storeState = this.getInitialStoreState();
// Start listening for specific events, coming from the window object.
this._eventHandlers = {};
["unload", "LoopHangupNow", "socialFrameAttached", "socialFrameDetached", "ToggleBrowserSharing"].
forEach(function (eventName) {
var handlerName = eventName + "Handler";
this._eventHandlers[eventName] = this[handlerName].bind(this);
this._rootObj.addEventListener(eventName, this._eventHandlers[eventName]);}.
bind(this));
this.dispatcher.register(this, [
"getWindowData",
"showFeedbackForm",
"leaveConversation"]);},
getInitialStoreState: function getInitialStoreState() {
return {
chatWindowDetached: false,
facebookEnabled: this._facebookEnabled,
// How often to display the form. Convert seconds to ms.
feedbackPeriod: this._feedbackPeriod * 1000,
// Date when the feedback form was last presented. Convert to ms.
feedbackTimestamp: this._feedbackTimestamp * 1000,
showFeedbackForm: false };},
/**
* Retrieves current store state.
*
* @return {Object}
*/
getStoreState: function getStoreState() {
return this._storeState;},
/**
* Updates store states and trigger a "change" event.
*
* @param {Object} state The new store state.
*/
setStoreState: function setStoreState(state) {
this._storeState = _.extend({}, this._storeState, state);
this.trigger("change");},
/**
* Sets store state which will result in the feedback form rendered.
* Saves a timestamp of when the feedback was last rendered.
*/
showFeedbackForm: function showFeedbackForm() {
var timestamp = Math.floor(new Date().getTime() / 1000);
loop.request("SetLoopPref", "feedback.dateLastSeenSec", timestamp);
this.setStoreState({
showFeedbackForm: true });},
/**
* Handles the get window data action - obtains the window data,
* updates the store and notifies interested components.
*
* @param {sharedActions.GetWindowData} actionData The action data
*/
getWindowData: function getWindowData(actionData) {
var windowData = loop.getStoredRequest(["GetConversationWindowData",
actionData.windowId]);
if (!windowData) {
console.error("Failed to get the window data");
this.setStoreState({ windowType: "failed" });
return;}
this.setStoreState({ windowType: windowData.type });
this.dispatcher.dispatch(new loop.shared.actions.SetupWindowData(_.extend({
windowId: actionData.windowId }, windowData)));},
/**
* Event handler; invoked when the 'unload' event is dispatched from the
* window object.
* It will dispatch a 'WindowUnload' action that other stores may listen to
* and will remove all event handlers attached to the window object.
*/
unloadHandler: function unloadHandler() {
this.dispatcher.dispatch(new loop.shared.actions.WindowUnload());
// Unregister event handlers.
var eventNames = Object.getOwnPropertyNames(this._eventHandlers);
eventNames.forEach(function (eventName) {
this._rootObj.removeEventListener(eventName, this._eventHandlers[eventName]);}.
bind(this));
this._eventHandlers = null;},
/**
* Event handler; invoked when the 'LoopHangupNow' event is dispatched from
* the window object.
* It'll attempt to gracefully disconnect from an active session, or close
* the window when no session is currently active.
*/
LoopHangupNowHandler: function LoopHangupNowHandler() {
this.dispatcher.dispatch(new loop.shared.actions.LeaveConversation());},
/**
* Handles leaving the conversation, displaying the feedback form if it
* is time to.
*/
leaveConversation: function leaveConversation() {
if (this.getStoreState().windowType !== "room" ||
!this._activeRoomStore.getStoreState().used ||
this.getStoreState().showFeedbackForm) {
loop.shared.mixins.WindowCloseMixin.closeWindow();
return;}
var delta = new Date() - new Date(this.getStoreState().feedbackTimestamp);
// Show timestamp if feedback period (6 months) passed.
// 0 is default value for pref. Always show feedback form on first use.
if (this.getStoreState().feedbackTimestamp === 0 ||
delta >= this.getStoreState().feedbackPeriod) {
this.dispatcher.dispatch(new loop.shared.actions.LeaveRoom({
windowStayingOpen: true }));
this.dispatcher.dispatch(new loop.shared.actions.ShowFeedbackForm());
return;}
loop.shared.mixins.WindowCloseMixin.closeWindow();},
/**
* Event handler; invoked when the 'PauseScreenShare' event is dispatched from
* the window object.
* It'll attempt to pause or resume the screen share as appropriate.
*/
ToggleBrowserSharingHandler: function ToggleBrowserSharingHandler(actionData) {
this.dispatcher.dispatch(new loop.shared.actions.ToggleBrowserSharing({
enabled: !actionData.detail }));},
/**
* Event handler; invoked when the 'socialFrameAttached' event is dispatched
* from the window object.
*/
socialFrameAttachedHandler: function socialFrameAttachedHandler() {
this.setStoreState({ chatWindowDetached: false });},
/**
* Event handler; invoked when the 'socialFrameDetached' event is dispatched
* from the window object.
*/
socialFrameDetachedHandler: function socialFrameDetachedHandler() {
this.setStoreState({ chatWindowDetached: true });} });
return ConversationAppStore;}();

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

@ -1,107 +0,0 @@
"use strict";var _slicedToArray = function () {function sliceIterator(arr, i) {var _arr = [];var _n = true;var _d = false;var _e = undefined;try {for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {_arr.push(_s.value);if (i && _arr.length === i) break;}} catch (err) {_d = true;_e = err;} finally {try {if (!_n && _i["return"]) _i["return"]();} finally {if (_d) throw _e;}}return _arr;}return function (arr, i) {if (Array.isArray(arr)) {return arr;} else if (Symbol.iterator in Object(arr)) {return sliceIterator(arr, i);} else {throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}(); /* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global Components */var _Components =
Components;var Cu = _Components.utils;
var loop = loop || {};
loop.copy = function (mozL10n) {
"use strict";
/**
* Copy panel view.
*/
var CopyView = React.createClass({ displayName: "CopyView",
mixins: [React.addons.PureRenderMixin],
getInitialState: function getInitialState() {
return { checked: false };},
/**
* Send a message to chrome/bootstrap to handle copy panel events.
* @param {Boolean} accept True if the user clicked accept.
*/
_dispatch: function _dispatch(accept) {
window.dispatchEvent(new CustomEvent("CopyPanelClick", {
detail: {
accept: accept,
stop: this.state.checked } }));},
handleAccept: function handleAccept() {
this._dispatch(true);},
handleCancel: function handleCancel() {
this._dispatch(false);},
handleToggle: function handleToggle() {
this.setState({ checked: !this.state.checked });},
render: function render() {
return (
React.createElement("div", { className: "copy-content" },
React.createElement("div", { className: "copy-body" },
React.createElement("img", { className: "copy-logo", src: "shared/img/helloicon.svg" }),
React.createElement("h1", { className: "copy-message" },
mozL10n.get("copy_panel_message"),
React.createElement("label", { className: "copy-toggle-label" },
React.createElement("input", { onChange: this.handleToggle, type: "checkbox" }),
mozL10n.get("copy_panel_dont_show_again_label")))),
React.createElement("button", { className: "copy-button", onClick: this.handleCancel },
mozL10n.get("copy_panel_cancel_button_label")),
React.createElement("button", { className: "copy-button", onClick: this.handleAccept },
mozL10n.get("copy_panel_accept_button_label"))));} });
/**
* Copy panel initialization.
*/
function init() {
// Wait for all LoopAPI message requests to complete before continuing init.
var _Cu$import = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});var LoopAPI = _Cu$import.LoopAPI;
var requests = ["GetAllStrings", "GetLocale", "GetPluralRule"];
return Promise.all(requests.map(function (name) {return new Promise(function (resolve) {
LoopAPI.sendMessageToHandler({ name: name }, resolve);});})).
then(function (results) {
// Extract individual requested values to initialize l10n.
var _results = _slicedToArray(results, 3);var stringBundle = _results[0];var locale = _results[1];var pluralRule = _results[2];
mozL10n.initialize({
getStrings: function getStrings(key) {
if (!(key in stringBundle)) {
console.error("No string found for key:", key);}
return JSON.stringify({ textContent: stringBundle[key] || "" });},
locale: locale,
pluralRule: pluralRule });
ReactDOM.render(React.createElement(CopyView, null), document.querySelector("#main"));
document.documentElement.setAttribute("dir", mozL10n.language.direction);
document.documentElement.setAttribute("lang", mozL10n.language.code);});}
return {
CopyView: CopyView,
init: init };}(
document.mozL10n);
document.addEventListener("DOMContentLoaded", loop.copy.init);

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

@ -1,220 +0,0 @@
"use strict"; /* 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/. */
var loop = loop || {};
loop.shared = loop.shared || {};
loop.shared.desktopViews = function (mozL10n) {
"use strict";
var sharedActions = loop.shared.actions;
var sharedMixins = loop.shared.mixins;
var sharedUtils = loop.shared.utils;
var CopyLinkButton = React.createClass({ displayName: "CopyLinkButton",
statics: {
TRIGGERED_RESET_DELAY: 2000 },
mixins: [React.addons.PureRenderMixin],
propTypes: {
callback: React.PropTypes.func,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
locationForMetrics: React.PropTypes.string.isRequired,
roomData: React.PropTypes.object.isRequired },
getInitialState: function getInitialState() {
return {
copiedUrl: false };},
handleCopyButtonClick: function handleCopyButtonClick(event) {
event.preventDefault();
this.props.dispatcher.dispatch(new sharedActions.CopyRoomUrl({
roomUrl: this.props.roomData.roomUrl,
from: this.props.locationForMetrics }));
this.setState({ copiedUrl: true });
setTimeout(this.resetTriggeredButtons, this.constructor.TRIGGERED_RESET_DELAY);},
/**
* Reset state of triggered buttons if necessary
*/
resetTriggeredButtons: function resetTriggeredButtons() {
if (this.state.copiedUrl) {
this.setState({ copiedUrl: false });
this.props.callback && this.props.callback();}},
render: function render() {
var cx = classNames;
return (
React.createElement("div", { className: cx({
"group-item-bottom": true,
"btn": true,
"invite-button": true,
"btn-copy": true,
"triggered": this.state.copiedUrl }),
onClick: this.handleCopyButtonClick }, mozL10n.get(this.state.copiedUrl ?
"invite_copied_link_button" : "invite_copy_link_button")));} });
var EmailLinkButton = React.createClass({ displayName: "EmailLinkButton",
mixins: [React.addons.PureRenderMixin],
propTypes: {
callback: React.PropTypes.func,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
locationForMetrics: React.PropTypes.string.isRequired,
roomData: React.PropTypes.object.isRequired },
handleEmailButtonClick: function handleEmailButtonClick(event) {
event.preventDefault();
var roomData = this.props.roomData;
var contextURL = roomData.roomContextUrls && roomData.roomContextUrls[0];
if (contextURL) {
if (contextURL.location === null) {
contextURL = undefined;} else
{
contextURL = sharedUtils.formatURL(contextURL.location).hostname;}}
this.props.dispatcher.dispatch(
new sharedActions.EmailRoomUrl({
roomUrl: roomData.roomUrl,
roomDescription: contextURL,
from: this.props.locationForMetrics }));
this.props.callback && this.props.callback();},
render: function render() {
return (
React.createElement("div", { className: "btn-email invite-button",
onClick: this.handleEmailButtonClick },
React.createElement("img", { src: "shared/img/glyph-email-16x16.svg" }),
React.createElement("p", null, mozL10n.get("invite_email_link_button"))));} });
var FacebookShareButton = React.createClass({ displayName: "FacebookShareButton",
mixins: [React.addons.PureRenderMixin],
propTypes: {
callback: React.PropTypes.func,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
locationForMetrics: React.PropTypes.string.isRequired,
roomData: React.PropTypes.object.isRequired },
handleFacebookButtonClick: function handleFacebookButtonClick(event) {
event.preventDefault();
this.props.dispatcher.dispatch(new sharedActions.FacebookShareRoomUrl({
from: this.props.locationForMetrics,
roomUrl: this.props.roomData.roomUrl }));
this.props.callback && this.props.callback();},
render: function render() {
return (
React.createElement("div", { className: "btn-facebook invite-button",
onClick: this.handleFacebookButtonClick },
React.createElement("img", { src: "shared/img/glyph-facebook-16x16.svg" }),
React.createElement("p", null, mozL10n.get("invite_facebook_button3"))));} });
var SharePanelView = React.createClass({ displayName: "SharePanelView",
mixins: [sharedMixins.DropdownMenuMixin(".room-invitation-overlay")],
propTypes: {
callback: React.PropTypes.func,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
error: React.PropTypes.object,
facebookEnabled: React.PropTypes.bool.isRequired,
locationForMetrics: React.PropTypes.string.isRequired,
// This data is supplied by the activeRoomStore.
roomData: React.PropTypes.object.isRequired,
show: React.PropTypes.bool.isRequired },
render: function render() {var _this = this;
if (!this.props.show || !this.props.roomData.roomUrl) {
return null;}
var cx = classNames;
return (
React.createElement("div", { className: "room-invitation-overlay" },
React.createElement("div", { className: "room-invitation-content" },
React.createElement("div", { className: "room-context-header" }, mozL10n.get("invite_header_text_bold2")),
React.createElement("div", null, mozL10n.get("invite_header_text4"))),
React.createElement("div", { className: "input-button-group" },
React.createElement("div", { className: "input-button-group-label" }, mozL10n.get("invite_your_link")),
React.createElement("div", { className: "input-button-content" },
React.createElement("div", { className: "input-group group-item-top" },
React.createElement("input", { readOnly: true, type: "text", value: this.props.roomData.roomUrl })),
React.createElement(CopyLinkButton, {
callback: this.props.callback,
dispatcher: this.props.dispatcher,
locationForMetrics: this.props.locationForMetrics,
roomData: this.props.roomData }))),
React.createElement("div", { className: cx({
"btn-group": true,
"share-action-group": true }) },
React.createElement(EmailLinkButton, {
callback: this.props.callback,
dispatcher: this.props.dispatcher,
locationForMetrics: this.props.locationForMetrics,
roomData: this.props.roomData }),
function () {
if (_this.props.facebookEnabled) {
return React.createElement(FacebookShareButton, {
callback: _this.props.callback,
dispatcher: _this.props.dispatcher,
locationForMetrics: _this.props.locationForMetrics,
roomData: _this.props.roomData });}
return null;}())));} });
return {
CopyLinkButton: CopyLinkButton,
EmailLinkButton: EmailLinkButton,
FacebookShareButton: FacebookShareButton,
SharePanelView: SharePanelView };}(
navigator.mozL10n || document.mozL10n);

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

@ -1,55 +0,0 @@
"use strict"; /* 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/. */
var loop = loop || {};
loop.feedbackViews = function (_, mozL10n) {
"use strict";
/**
* Feedback view is displayed once every 6 months (loop.feedback.periodSec)
* after a conversation has ended.
*/
var FeedbackView = React.createClass({ displayName: "FeedbackView",
propTypes: {
onAfterFeedbackReceived: React.PropTypes.func.isRequired },
/**
* Pressing the button to leave feedback will open the form in a new page
* and close the conversation window.
*/
onFeedbackButtonClick: function onFeedbackButtonClick() {
loop.requestMulti(
["GetLoopPref", "feedback.formURL"],
["GetAddonVersion"]).
then(function (results) {
if (results[0] && results[1]) {
var finalURL = results[0].replace("%APP_VERSION%", results[1]);
loop.request("OpenURL", finalURL).then(this.props.onAfterFeedbackReceived);}}.
bind(this));},
render: function render() {
return (
React.createElement("div", { className: "feedback-view-container" },
React.createElement("h2", { className: "feedback-heading" },
mozL10n.get("feedback_window_heading")),
React.createElement("div", { className: "feedback-hello-logo" }),
React.createElement("div", { className: "feedback-button-container" },
React.createElement("button", { onClick: this.onFeedbackButtonClick,
ref: "feedbackFormBtn" },
mozL10n.get("feedback_request_button")))));} });
return {
FeedbackView: FeedbackView };}(
_, navigator.mozL10n || document.mozL10n);

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

@ -1,32 +0,0 @@
"use strict"; /* 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/. */
var loop = loop || {};
loop.panel = loop.panel || {};
loop.panel.models = function () {
"use strict";
/**
* Notification model.
*/
var NotificationModel = Backbone.Model.extend({
defaults: {
details: "",
detailsButtonLabel: "",
detailsButtonCallback: null,
level: "info",
message: "" } });
/**
* Notification collection
*/
var NotificationCollection = Backbone.Collection.extend({
model: NotificationModel });
return {
NotificationCollection: NotificationCollection,
NotificationModel: NotificationModel };}();

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

@ -1,12 +0,0 @@
"use strict"; /* 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/. */
window.OTProperties = {
cdnURL: "" };
window.OTProperties.assetURL = window.OTProperties.cdnURL + "sdk-content/";
window.OTProperties.configURL = window.OTProperties.assetURL + "js/dynamic_config.min.js";
// We don't use the SDK's CSS. This will prevent spurious 404 errors.
window.OTProperties.cssURL = "about:blank";

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

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

@ -1,592 +0,0 @@
"use strict"; /* 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/. */
var loop = loop || {};
loop.store = loop.store || {};
(function (mozL10n) {
"use strict";
/**
* Shared actions.
* @type {Object}
*/
var sharedActions = loop.shared.actions;
/**
* Maximum size given to createRoom; only 2 is supported (and is
* always passed) because that's what the user-experience is currently
* designed and tested to handle.
* @type {Number}
*/
var MAX_ROOM_CREATION_SIZE = loop.store.MAX_ROOM_CREATION_SIZE = 2;
/**
* Room validation schema. See validate.js.
* @type {Object}
*/
var roomSchema = {
roomToken: String,
roomUrl: String,
// roomName: String - Optional.
// roomKey: String - Optional.
maxSize: Number,
participants: Array,
ctime: Number };
/**
* Room type. Basically acts as a typed object constructor.
*
* @param {Object} values Room property values.
*/
function Room(values) {var _this = this;
var validatedData = new loop.validate.Validator(roomSchema || {}).
validate(values || {});
Object.keys(validatedData).forEach(function (prop) {
_this[prop] = validatedData[prop];});}
loop.store.Room = Room;
/**
* Room store.
*
* @param {loop.Dispatcher} dispatcher The dispatcher for dispatching actions
* and registering to consume actions.
* @param {Object} options Options object:
* - {ActiveRoomStore} activeRoomStore An optional substore for active room
* state.
* - {Notifications} notifications A notifications item that is required.
* - {Object} constants A set of constants that are used
* throughout the store.
*/
loop.store.RoomStore = loop.store.createStore({
/**
* Maximum size given to createRoom; only 2 is supported (and is
* always passed) because that's what the user-experience is currently
* designed and tested to handle.
* @type {Number}
*/
maxRoomCreationSize: MAX_ROOM_CREATION_SIZE,
/**
* Registered actions.
* @type {Array}
*/
actions: [
"createRoom",
"createdRoom",
"createRoomError",
"copyRoomUrl",
"deleteRoom",
"deleteRoomError",
"emailRoomUrl",
"facebookShareRoomUrl",
"getAllRooms",
"getAllRoomsError",
"openRoom",
"updateRoomContext",
"updateRoomContextDone",
"updateRoomContextError",
"updateRoomList"],
initialize: function initialize(options) {
if (!options.constants) {
throw new Error("Missing option constants");}
this._notifications = options.notifications;
this._constants = options.constants;
this._gotAllRooms = false;
if (options.activeRoomStore) {
this.activeRoomStore = options.activeRoomStore;
this.activeRoomStore.on("change",
this._onActiveRoomStoreChange.bind(this));}},
getInitialStoreState: function getInitialStoreState() {
return {
activeRoom: this.activeRoomStore ? this.activeRoomStore.getStoreState() : {},
closingNewRoom: false,
error: null,
lastCreatedRoom: null,
openedRoom: null,
pendingCreation: false,
pendingInitialRetrieval: true,
rooms: [],
savingContext: false };},
/**
* Registers Loop API rooms events.
*/
startListeningToRoomEvents: function startListeningToRoomEvents() {
// Rooms event registration
loop.request("Rooms:PushSubscription", ["add", "close", "delete", "open",
"refresh", "update"]);
loop.subscribe("Rooms:Add", this._onRoomAdded.bind(this));
loop.subscribe("Rooms:Close", this._onRoomClose.bind(this));
loop.subscribe("Rooms:Open", this._onRoomOpen.bind(this));
loop.subscribe("Rooms:Update", this._onRoomUpdated.bind(this));
loop.subscribe("Rooms:Delete", this._onRoomRemoved.bind(this));
loop.subscribe("Rooms:Refresh", this._onRoomsRefresh.bind(this));},
/**
* Updates active room store state.
*/
_onActiveRoomStoreChange: function _onActiveRoomStoreChange() {
this.setStoreState({ activeRoom: this.activeRoomStore.getStoreState() });},
/**
* Updates current room list when a new room is available.
*
* @param {Object} addedRoomData The added room data.
*/
_onRoomAdded: function _onRoomAdded(addedRoomData) {
addedRoomData.participants = addedRoomData.participants || [];
addedRoomData.ctime = addedRoomData.ctime || new Date().getTime();
this.dispatchAction(new sharedActions.UpdateRoomList({
// Ensure the room isn't part of the list already, then add it.
roomList: this._storeState.rooms.filter(function (room) {
return addedRoomData.roomToken !== room.roomToken;}).
concat(new Room(addedRoomData)) }));},
/**
* Clears the current active room.
*/
_onRoomClose: function _onRoomClose() {
var state = this.getStoreState();
// If the room getting closed has been just created, then open the panel.
if (state.lastCreatedRoom && state.openedRoom === state.lastCreatedRoom) {
this.setStoreState({
closingNewRoom: true });
loop.request("SetNameNewRoom");}
// reset state for closed room
this.setStoreState({
closingNewRoom: false,
lastCreatedRoom: null,
openedRoom: null });},
/**
* Updates the current active room.
*
* @param {String} roomToken Identifier of the room.
*/
_onRoomOpen: function _onRoomOpen(roomToken) {
this.setStoreState({
openedRoom: roomToken });},
/**
* Executed when a room is updated.
*
* @param {Object} updatedRoomData The updated room data.
*/
_onRoomUpdated: function _onRoomUpdated(updatedRoomData) {
this.dispatchAction(new sharedActions.UpdateRoomList({
roomList: this._storeState.rooms.map(function (room) {
return room.roomToken === updatedRoomData.roomToken ?
updatedRoomData : room;}) }));},
/**
* Executed when a room is deleted.
*
* @param {Object} removedRoomData The removed room data.
*/
_onRoomRemoved: function _onRoomRemoved(removedRoomData) {
this.dispatchAction(new sharedActions.UpdateRoomList({
roomList: this._storeState.rooms.filter(function (room) {
return room.roomToken !== removedRoomData.roomToken;}) }));},
/**
* Executed when the user switches accounts.
*/
_onRoomsRefresh: function _onRoomsRefresh() {
this.dispatchAction(new sharedActions.UpdateRoomList({
roomList: [] }));},
/**
* Maps and sorts the raw room list received from the Loop API.
*
* @param {Array} rawRoomList Raw room list.
* @return {Array}
*/
_processRoomList: function _processRoomList(rawRoomList) {
if (!rawRoomList) {
return [];}
return rawRoomList.
map(function (rawRoom) {
return new Room(rawRoom);}).
slice().
sort(function (a, b) {
return b.ctime - a.ctime;});},
/**
* Creates a new room.
*
* @param {sharedActions.CreateRoom} actionData The new room information.
*/
createRoom: function createRoom(actionData) {
this.setStoreState({
pendingCreation: true,
error: null });
var roomCreationData = {
decryptedContext: {},
maxSize: this.maxRoomCreationSize };
if ("urls" in actionData) {
roomCreationData.decryptedContext.urls = actionData.urls;}
this._notifications.remove("create-room-error");
loop.request("Rooms:Create", roomCreationData).then(function (result) {
var buckets = this._constants.ROOM_CREATE;
if (!result || result.isError) {
loop.request("TelemetryAddValue", "LOOP_ROOM_CREATE", buckets.CREATE_FAIL);
this.dispatchAction(new sharedActions.CreateRoomError({
error: result ? result : new Error("no result") }));
return;}
// Keep the token for the last created room.
this.setStoreState({
lastCreatedRoom: result.roomToken });
this.dispatchAction(new sharedActions.CreatedRoom({
decryptedContext: result.decryptedContext,
roomToken: result.roomToken,
roomUrl: result.roomUrl }));
loop.request("TelemetryAddValue", "LOOP_ROOM_CREATE", buckets.CREATE_SUCCESS);}.
bind(this));},
/**
* Executed when a room has been created
*/
createdRoom: function createdRoom(actionData) {
this.setStoreState({
activeRoom: {
decryptedContext: actionData.decryptedContext,
roomToken: actionData.roomToken,
roomUrl: actionData.roomUrl },
pendingCreation: false });},
/**
* Executed when a room creation error occurs.
*
* @param {sharedActions.CreateRoomError} actionData The action data.
*/
createRoomError: function createRoomError(actionData) {
this.setStoreState({
error: actionData.error,
pendingCreation: false });
// XXX Needs a more descriptive error - bug 1109151.
this._notifications.set({
id: "create-room-error",
level: "error",
message: mozL10n.get("generic_failure_message") });},
/**
* Copy a room url.
*
* @param {sharedActions.CopyRoomUrl} actionData The action data.
*/
copyRoomUrl: function copyRoomUrl(actionData) {
loop.requestMulti(
["CopyString", actionData.roomUrl],
["NotifyUITour", "Loop:RoomURLCopied"]);
var from = actionData.from;
var bucket = this._constants.SHARING_ROOM_URL["COPY_FROM_" + from.toUpperCase()];
if (typeof bucket === "undefined") {
console.error("No URL sharing type bucket found for '" + from + "'");
return;}
loop.request("TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_SHARE);},
/**
* Emails a room url.
*
* @param {sharedActions.EmailRoomUrl} actionData The action data.
*/
emailRoomUrl: function emailRoomUrl(actionData) {
var from = actionData.from;
loop.shared.utils.composeCallUrlEmail(actionData.roomUrl, null,
actionData.roomDescription);
var bucket = this._constants.SHARING_ROOM_URL[
"EMAIL_FROM_" + (from || "").toUpperCase()];
if (typeof bucket === "undefined") {
console.error("No URL sharing type bucket found for '" + from + "'");
return;}
loop.requestMulti(
["NotifyUITour", "Loop:RoomURLEmailed"],
["TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_SHARE]);},
/**
* Share a room url with Facebook
*
* @param {sharedActions.FacebookShareRoomUrl} actionData The action data.
*/
facebookShareRoomUrl: function facebookShareRoomUrl(actionData) {
var encodedRoom = encodeURIComponent(actionData.roomUrl);
loop.requestMulti(
["GetLoopPref", "facebook.appId"],
["GetLoopPref", "facebook.fallbackUrl"],
["GetLoopPref", "facebook.shareUrl"]).
then(function (results) {
var app_id = results[0];
var fallback_url = results[1];
var redirect_url = encodeURIComponent(actionData.originUrl ||
fallback_url);
var finalURL = results[2].replace("%ROOM_URL%", encodedRoom).
replace("%APP_ID%", app_id).
replace("%REDIRECT_URI%", redirect_url);
return loop.request("OpenURL", finalURL);}).
then(function () {
loop.request("NotifyUITour", "Loop:RoomURLShared");});
var from = actionData.from;
var bucket = this._constants.SHARING_ROOM_URL["FACEBOOK_FROM_" + from.toUpperCase()];
if (typeof bucket === "undefined") {
console.error("No URL sharing type bucket found for '" + from + "'");
return;}
loop.request("TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_SHARE);},
/**
* Creates a new room.
*
* @param {sharedActions.DeleteRoom} actionData The action data.
*/
deleteRoom: function deleteRoom(actionData) {
loop.request("Rooms:Delete", actionData.roomToken).then(function (result) {
var isError = result && result.isError;
if (isError) {
this.dispatchAction(new sharedActions.DeleteRoomError({ error: result }));}
loop.request("TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_DELETE);}.
bind(this));},
/**
* Executed when a room deletion error occurs.
*
* @param {sharedActions.DeleteRoomError} actionData The action data.
*/
deleteRoomError: function deleteRoomError(actionData) {
this.setStoreState({ error: actionData.error });},
/**
* Gather the list of all available rooms from the Loop API.
*/
getAllRooms: function getAllRooms() {
// XXX Ideally, we'd have a specific command to "start up" the room store
// to get the rooms. We should address this alongside bug 1074665.
if (this._gotAllRooms) {
return;}
loop.request("Rooms:GetAll", null).then(function (result) {
var action;
this.setStoreState({ pendingInitialRetrieval: false });
if (result && result.isError) {
action = new sharedActions.GetAllRoomsError({ error: result });} else
{
action = new sharedActions.UpdateRoomList({ roomList: result });}
this.dispatchAction(action);
this._gotAllRooms = true;
// We can only start listening to room events after getAll() has been
// called executed first.
this.startListeningToRoomEvents();}.
bind(this));},
/**
* Updates current error state in case getAllRooms failed.
*
* @param {sharedActions.GetAllRoomsError} actionData The action data.
*/
getAllRoomsError: function getAllRoomsError(actionData) {
this.setStoreState({ error: actionData.error });},
/**
* Updates current room list.
*
* @param {sharedActions.UpdateRoomList} actionData The action data.
*/
updateRoomList: function updateRoomList(actionData) {
this.setStoreState({
error: undefined,
rooms: this._processRoomList(actionData.roomList) });},
/**
* Opens a room
*
* @param {sharedActions.OpenRoom} actionData The action data.
*/
openRoom: function openRoom(actionData) {
loop.requestMulti(
["Rooms:Open", actionData.roomToken],
["TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_OPEN]);},
/**
* Updates the context data attached to a room.
*
* @param {sharedActions.UpdateRoomContext} actionData
*/
updateRoomContext: function updateRoomContext(actionData) {
this.setStoreState({ savingContext: true });
loop.request("Rooms:Get", actionData.roomToken).then(function (result) {
if (result.isError) {
this.dispatchAction(new sharedActions.UpdateRoomContextError({
error: result }));
return;}
var roomData = {};
var context = result.decryptedContext;
var oldRoomName = context.roomName;
var newRoomName = (actionData.newRoomName || "").trim();
if (newRoomName && oldRoomName !== newRoomName) {
roomData.roomName = newRoomName;}
var oldRoomURLs = context.urls;
var oldRoomURL = oldRoomURLs && oldRoomURLs[0];
// Since we want to prevent storing falsy (i.e. empty) values for context
// data, there's no need to send that to the server as an update.
var newRoomURL = loop.shared.utils.stripFalsyValues({
location: (actionData.newRoomURL || "").trim(),
thumbnail: (actionData.newRoomThumbnail || "").trim(),
description: (actionData.newRoomDescription || "").trim() });
// Only attach a context to the room when
// 1) there was already a URL set,
// 2) a new URL is provided as of now,
// 3) the URL data has changed.
var diff = loop.shared.utils.objectDiff(oldRoomURL, newRoomURL);
if (diff.added.length || diff.updated.length) {
newRoomURL = _.extend(oldRoomURL || {}, newRoomURL);
var isValidURL = false;
try {
isValidURL = new URL(newRoomURL.location);}
catch (ex) {
// URL may throw, default to false;
}
if (isValidURL) {
roomData.urls = [newRoomURL];}}
// TODO: there currently is no clear UX defined on what to do when all
// context data was cleared, e.g. when diff.removed contains all the
// context properties. Until then, we can't deal with context removal here.
// When no properties have been set on the roomData object, there's nothing
// to save.
if (!Object.getOwnPropertyNames(roomData).length) {
// Ensure async actions so that we get separate setStoreState events
// that React components won't miss.
setTimeout(function () {
this.dispatchAction(new sharedActions.UpdateRoomContextDone());}.
bind(this), 0);
return;}
this.setStoreState({ error: null });
loop.request("Rooms:Update", actionData.roomToken, roomData).then(function (result2) {
var isError = result2 && result2.isError;
var action = isError ?
new sharedActions.UpdateRoomContextError({ error: result2 }) :
new sharedActions.UpdateRoomContextDone();
this.dispatchAction(action);}.
bind(this));}.
bind(this));},
/**
* Handles the updateRoomContextDone action.
*/
updateRoomContextDone: function updateRoomContextDone() {
this.setStoreState({ savingContext: false });},
/**
* Updating the context data attached to a room error.
*
* @param {sharedActions.UpdateRoomContextError} actionData
*/
updateRoomContextError: function updateRoomContextError(actionData) {
this.setStoreState({
error: actionData.error,
savingContext: false });} });})(
document.mozL10n || navigator.mozL10n);

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

@ -1,382 +0,0 @@
"use strict";var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {return typeof obj;} : function (obj) {return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj;}; /* 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/. */
var loop = loop || {};
loop.roomViews = function (mozL10n) {
"use strict";
var ROOM_STATES = loop.store.ROOM_STATES;
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
var sharedActions = loop.shared.actions;
var sharedMixins = loop.shared.mixins;
var sharedDesktopViews = loop.shared.desktopViews;
var sharedViews = loop.shared.views;
/**
* ActiveRoomStore mixin.
* @type {Object}
*/
var ActiveRoomStoreMixin = {
mixins: [Backbone.Events],
propTypes: {
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired },
componentWillMount: function componentWillMount() {
this.listenTo(this.props.roomStore, "change:activeRoom",
this._onActiveRoomStateChanged);
this.listenTo(this.props.roomStore, "change:error",
this._onRoomError);
this.listenTo(this.props.roomStore, "change:savingContext",
this._onRoomSavingContext);},
componentWillUnmount: function componentWillUnmount() {
this.stopListening(this.props.roomStore);},
_onActiveRoomStateChanged: function _onActiveRoomStateChanged() {
// Only update the state if we're mounted, to avoid the problem where
// stopListening doesn't nuke the active listeners during a event
// processing.
if (this.isMounted()) {
this.setState(this.props.roomStore.getStoreState("activeRoom"));}},
_onRoomError: function _onRoomError() {
// Only update the state if we're mounted, to avoid the problem where
// stopListening doesn't nuke the active listeners during a event
// processing.
if (this.isMounted()) {
this.setState({ error: this.props.roomStore.getStoreState("error") });}},
_onRoomSavingContext: function _onRoomSavingContext() {
// Only update the state if we're mounted, to avoid the problem where
// stopListening doesn't nuke the active listeners during a event
// processing.
if (this.isMounted()) {
this.setState({ savingContext: this.props.roomStore.getStoreState("savingContext") });}},
getInitialState: function getInitialState() {
var storeState = this.props.roomStore.getStoreState("activeRoom");
return _.extend({
// Used by the UI showcase.
roomState: this.props.roomState || storeState.roomState,
savingContext: false },
storeState);} };
/**
* Used to display errors in direct calls and rooms to the user.
*/
var FailureInfoView = React.createClass({ displayName: "FailureInfoView",
propTypes: {
failureReason: React.PropTypes.string.isRequired },
/**
* Returns the translated message appropraite to the failure reason.
*
* @return {String} The translated message for the failure reason.
*/
_getMessage: function _getMessage() {
switch (this.props.failureReason) {
case FAILURE_DETAILS.NO_MEDIA:
case FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA:
return mozL10n.get("no_media_failure_message");
case FAILURE_DETAILS.TOS_FAILURE:
return mozL10n.get("tos_failure_message",
{ clientShortname: mozL10n.get("clientShortname2") });
case FAILURE_DETAILS.ICE_FAILED:
return mozL10n.get("ice_failure_message");
default:
return mozL10n.get("generic_failure_message");}},
render: function render() {
return (
React.createElement("div", { className: "failure-info" },
React.createElement("div", { className: "failure-info-logo" }),
React.createElement("h2", { className: "failure-info-message" }, this._getMessage())));} });
/**
* Something went wrong view. Displayed when there's a big problem.
*/
var RoomFailureView = React.createClass({ displayName: "RoomFailureView",
mixins: [sharedMixins.AudioMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
failureReason: React.PropTypes.string },
componentDidMount: function componentDidMount() {
this.play("failure");},
handleRejoinCall: function handleRejoinCall() {
this.props.dispatcher.dispatch(new sharedActions.JoinRoom());},
render: function render() {
var btnTitle;
if (this.props.failureReason === FAILURE_DETAILS.ICE_FAILED) {
btnTitle = mozL10n.get("retry_call_button");} else
{
btnTitle = mozL10n.get("rejoin_button");}
return (
React.createElement("div", { className: "room-failure" },
React.createElement(FailureInfoView, { failureReason: this.props.failureReason }),
React.createElement("div", { className: "btn-group call-action-group" },
React.createElement("button", { className: "btn btn-info btn-rejoin",
onClick: this.handleRejoinCall },
btnTitle))));} });
/**
* Desktop room conversation view.
*/
var DesktopRoomConversationView = React.createClass({ displayName: "DesktopRoomConversationView",
mixins: [
ActiveRoomStoreMixin,
sharedMixins.DocumentTitleMixin,
sharedMixins.MediaSetupMixin,
sharedMixins.RoomsAudioMixin,
sharedMixins.WindowCloseMixin],
propTypes: {
chatWindowDetached: React.PropTypes.bool.isRequired,
cursorStore: React.PropTypes.instanceOf(loop.store.RemoteCursorStore).isRequired,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
facebookEnabled: React.PropTypes.bool.isRequired,
// The poster URLs are for UI-showcase testing and development.
localPosterUrl: React.PropTypes.string,
onCallTerminated: React.PropTypes.func.isRequired,
remotePosterUrl: React.PropTypes.string,
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired },
componentWillUpdate: function componentWillUpdate(nextProps, nextState) {
// The SDK needs to know about the configuration and the elements to use
// for display. So the best way seems to pass the information here - ideally
// the sdk wouldn't need to know this, but we can't change that.
if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT &&
nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
publisherConfig: this.getDefaultPublisherConfig({
publishVideo: !this.state.videoMuted }) }));}},
/**
* User clicked on the "Leave" button.
*/
leaveRoom: function leaveRoom() {
if (this.state.used) {
// The room has been used, so we might want to display the feedback view.
// Therefore we leave the room to ensure that we have stopped sharing etc,
// then trigger the call terminated handler that'll do the right thing
// for the feedback view.
this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
this.props.onCallTerminated();} else
{
// If the room hasn't been used, we simply close the window.
this.closeWindow();}},
/**
* Determine if the invitation controls should be shown.
*
* @return {Boolean} True if there's no guests.
*/
_shouldRenderInvitationOverlay: function _shouldRenderInvitationOverlay() {
var hasGuests = _typeof(this.state.participants) === "object" &&
this.state.participants.filter(function (participant) {
return !participant.owner;}).
length > 0;
// Don't show if the room has participants whether from the room state or
// there being non-owner guests in the participants array.
return this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS && !hasGuests;},
/**
* Works out if remote video should be rended or not, depending on the
* room state and other flags.
*
* @return {Boolean} True if remote video should be rended.
*
* XXX Refactor shouldRenderRemoteVideo & shouldRenderLoading into one fn
* that returns an enum
*/
shouldRenderRemoteVideo: function shouldRenderRemoteVideo() {
switch (this.state.roomState) {
case ROOM_STATES.HAS_PARTICIPANTS:
if (this.state.remoteVideoEnabled) {
return true;}
if (this.state.mediaConnected) {
// since the remoteVideo hasn't yet been enabled, if the
// media is connected, then we should be displaying an avatar.
return false;}
return true;
case ROOM_STATES.READY:
case ROOM_STATES.GATHER:
case ROOM_STATES.INIT:
case ROOM_STATES.JOINING:
case ROOM_STATES.SESSION_CONNECTED:
case ROOM_STATES.JOINED:
case ROOM_STATES.MEDIA_WAIT:
// this case is so that we don't show an avatar while waiting for
// the other party to connect
return true;
case ROOM_STATES.CLOSING:
return true;
default:
console.warn("DesktopRoomConversationView.shouldRenderRemoteVideo:" +
" unexpected roomState: ", this.state.roomState);
return true;}},
/**
* Should we render a visual cue to the user (e.g. a spinner) that a local
* stream is on its way from the camera?
*
* @returns {boolean}
* @private
*/
_isLocalLoading: function _isLocalLoading() {
return this.state.roomState === ROOM_STATES.MEDIA_WAIT &&
!this.state.localSrcMediaElement;},
/**
* Should we render a visual cue to the user (e.g. a spinner) that a remote
* stream is on its way from the other user?
*
* @returns {boolean}
* @private
*/
_isRemoteLoading: function _isRemoteLoading() {
return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
!this.state.remoteSrcMediaElement &&
!this.state.mediaConnected);},
handleContextMenu: function handleContextMenu(e) {
e.preventDefault();},
render: function render() {
if (this.state.roomName || this.state.roomContextUrls) {
var roomTitle = this.state.roomName ||
this.state.roomContextUrls[0].description ||
this.state.roomContextUrls[0].location;
if (!roomTitle) {
roomTitle = mozL10n.get("room_name_untitled_page");}
this.setTitle(roomTitle);}
var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
var roomData = this.props.roomStore.getStoreState("activeRoom");
switch (this.state.roomState) {
case ROOM_STATES.FAILED:
case ROOM_STATES.FULL:{
// Note: While rooms are set to hold a maximum of 2 participants, the
// FULL case should never happen on desktop.
return (
React.createElement(RoomFailureView, {
dispatcher: this.props.dispatcher,
failureReason: this.state.failureReason }));}
case ROOM_STATES.ENDED:{
// When conversation ended we either display a feedback form or
// close the window. This is decided in the AppControllerView.
return null;}
default:{
return (
React.createElement("div", { className: "room-conversation-wrapper desktop-room-wrapper",
onContextMenu: this.handleContextMenu },
React.createElement(sharedViews.MediaLayoutView, {
cursorStore: this.props.cursorStore,
dispatcher: this.props.dispatcher,
displayScreenShare: false,
isLocalLoading: this._isLocalLoading(),
isRemoteLoading: this._isRemoteLoading(),
isScreenShareLoading: false,
localPosterUrl: this.props.localPosterUrl,
localSrcMediaElement: this.state.localSrcMediaElement,
localVideoMuted: this.state.videoMuted,
matchMedia: this.state.matchMedia || window.matchMedia,
remotePosterUrl: this.props.remotePosterUrl,
remoteSrcMediaElement: this.state.remoteSrcMediaElement,
renderRemoteVideo: this.shouldRenderRemoteVideo(),
screenShareMediaElement: this.state.screenShareMediaElement,
screenSharePosterUrl: null,
showInitialContext: false,
showMediaWait: false,
showTile: false },
React.createElement(sharedViews.ConversationToolbar, {
audio: { enabled: !this.state.audioMuted, visible: true },
dispatcher: this.props.dispatcher,
hangup: this.leaveRoom,
showHangup: this.props.chatWindowDetached,
video: { enabled: !this.state.videoMuted, visible: true } }),
React.createElement(sharedDesktopViews.SharePanelView, {
dispatcher: this.props.dispatcher,
error: this.state.error,
facebookEnabled: this.props.facebookEnabled,
locationForMetrics: "conversation",
roomData: roomData,
show: shouldRenderInvitationOverlay }))));}}} });
return {
ActiveRoomStoreMixin: ActiveRoomStoreMixin,
FailureInfoView: FailureInfoView,
RoomFailureView: RoomFailureView,
DesktopRoomConversationView: DesktopRoomConversationView };}(
document.mozL10n || navigator.mozL10n);

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

@ -1,91 +0,0 @@
"use strict"; /* 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/. */
var loop = loop || {};
loop.slideshow = function (mozL10n) {
"use strict";
/**
* Slideshow initialisation.
*/
function init() {
var requests = [
["GetAllStrings"],
["GetLocale"],
["GetPluralRule"]];
return loop.requestMulti.apply(null, requests).then(function (results) {
// `requestIdx` is keyed off the order of the `requests`
// array. Be careful to update both when making changes.
var requestIdx = 0;
// Do the initial L10n setup, we do this before anything
// else to ensure the L10n environment is setup correctly.
var stringBundle = results[requestIdx];
var locale = results[++requestIdx];
var pluralRule = results[++requestIdx];
mozL10n.initialize({
locale: locale,
pluralRule: pluralRule,
getStrings: function getStrings(key) {
if (!(key in stringBundle)) {
return "{ textContent: '' }";}
return JSON.stringify({
textContent: stringBundle[key] });} });
document.documentElement.setAttribute("lang", mozL10n.language.code);
document.documentElement.setAttribute("dir", mozL10n.language.direction);
document.body.setAttribute("platform", loop.shared.utils.getPlatform());
var clientSuperShortname = mozL10n.get("clientSuperShortname");
var data = [
{
id: "slide1",
imageClass: "slide1-image",
title: mozL10n.get("fte_slide_1_title"),
text: mozL10n.get("fte_slide_1_copy", {
clientShortname2: mozL10n.get("clientShortname2") }) },
{
id: "slide2",
imageClass: "slide2-image",
title: mozL10n.get("fte_slide_2_title2"),
text: mozL10n.get("fte_slide_2_copy2", {
clientShortname2: mozL10n.get("clientShortname2") }) },
{
id: "slide3",
imageClass: "slide3-image",
title: mozL10n.get("fte_slide_3_title"),
text: mozL10n.get("fte_slide_3_copy", {
clientSuperShortname: clientSuperShortname }) },
{
id: "slide4",
imageClass: "slide4-image",
title: mozL10n.get("fte_slide_4_title", {
clientSuperShortname: clientSuperShortname }),
text: mozL10n.get("fte_slide_4_copy", {
brandShortname: mozL10n.get("brandShortname") }) }];
loop.SimpleSlideshow.init("#main", data);});}
return {
init: init };}(
document.mozL10n);
document.addEventListener("DOMContentLoaded", loop.slideshow.init);

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

@ -1,45 +0,0 @@
<!DOCTYPE html>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html>
<head>
<meta charset="utf-8">
<base href="chrome://loop/content">
<link rel="stylesheet" type="text/css" href="shared/css/reset.css">
<link rel="stylesheet" type="text/css" href="shared/css/common.css">
<link rel="stylesheet" type="text/css" href="panels/css/panel.css">
<link rel="stylesheet" type="text/css" href="panels/css/desktop.css">
</head>
<body class="panel">
<div id="main"></div>
<script type="text/javascript" src="panels/vendor/l10n.js"></script>
<script type="text/javascript" src="shared/vendor/react.js"></script>
<script type="text/javascript" src="shared/vendor/react-dom.js"></script>
<script type="text/javascript" src="shared/vendor/lodash.js"></script>
<script type="text/javascript" src="shared/vendor/backbone.js"></script>
<script type="text/javascript" src="shared/vendor/classnames.js"></script>
<script type="text/javascript" src="shared/js/loopapi-client.js"></script>
<script type="text/javascript" src="shared/js/utils.js"></script>
<script type="text/javascript" src="panels/js/models.js"></script>
<script type="text/javascript" src="shared/js/mixins.js"></script>
<script type="text/javascript" src="shared/js/actions.js"></script>
<script type="text/javascript" src="shared/js/dispatcher.js"></script>
<!-- Stores need to be loaded before the views that uses them -->
<script type="text/javascript" src="shared/js/store.js"></script>
<script type="text/javascript" src="panels/js/roomStore.js"></script>
<script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
<script type="text/javascript" src="shared/js/remoteCursorStore.js"></script>
<!-- Views -->
<script type="text/javascript" src="shared/js/views.js"></script>
<script type="text/javascript" src="shared/js/validate.js"></script>
<script type="text/javascript" src="panels/js/desktopViews.js"></script>
<script type="text/javascript" src="panels/js/panel.js"></script>
</body>
</html>

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

@ -1,41 +0,0 @@
<!DOCTYPE html>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html>
<head>
<meta charset="utf-8">
<base href="chrome://loop/content">
<link rel="stylesheet" type="text/css" href="shared/css/reset.css">
<link rel="stylesheet" type="text/css" href="shared/css/common.css">
<link rel="stylesheet" type="text/css" href="panels/vendor/simpleSlideshow.css">
<link rel="stylesheet" type="text/css" href="panels/css/slideshow.css">
</head>
<body class="panel">
<div id="main"></div>
<script type="text/javascript" src="panels/vendor/l10n.js"></script>
<script type="text/javascript" src="shared/vendor/react.js"></script>
<script type="text/javascript" src="shared/vendor/react-dom.js"></script>
<script type="text/javascript" src="shared/vendor/lodash.js"></script>
<script type="text/javascript" src="shared/js/utils.js"></script>
<script type="text/javascript" src="shared/js/loopapi-client.js"></script>
<script type="text/javascript" src="shared/vendor/classnames.js"></script>
<script type="text/javascript" src="panels/vendor/simpleSlideshow.js"></script>
<script type="text/javascript" src="panels/js/slideshow.js"></script>
<script type="text/javascript">
function clickHandler() {
try {
var e = new CustomEvent("CloseSlideshow"); window.dispatchEvent(e);
} catch (ex) {
console.warn('CloseSlideshow dispatch exploded' + ex);
}
return;
}
</script>
<button onclick="clickHandler();" class="button-close" />
</body>
</html>

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

@ -1,3 +0,0 @@
{
"extends": "../../../test/.eslintrc"
}

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

@ -1,361 +0,0 @@
"use strict"; /* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
describe("loop.store.ConversationAppStore", function () {
"use strict";
var expect = chai.expect;
var sharedActions = loop.shared.actions;
var sandbox, activeRoomStore, dispatcher, feedbackPeriodMs, roomUsed;
beforeEach(function () {
roomUsed = false;
feedbackPeriodMs = 15770000000;
activeRoomStore = {
getStoreState: function getStoreState() {return { used: roomUsed };} };
sandbox = LoopMochaUtils.createSandbox();
LoopMochaUtils.stubLoopRequest({
GetLoopPref: function GetLoopPref() {} });
dispatcher = new loop.Dispatcher();
sandbox.stub(dispatcher, "dispatch");});
afterEach(function () {
sandbox.restore();
LoopMochaUtils.restore();});
describe("#constructor", function () {
it("should throw an error if the activeRoomStore is missing", function () {
expect(function () {
new loop.store.ConversationAppStore(dispatcher, {
feedbackPeriod: 1,
feedbackTimestamp: 1 });}).
to.Throw(/activeRoomStore/);});
it("should throw an error if the dispatcher is missing", function () {
expect(function () {
new loop.store.ConversationAppStore(undefined, {
activeRoomStore: activeRoomStore,
feedbackPeriod: 1,
feedbackTimestamp: 1 });}).
to.Throw(/dispatcher/);});
it("should throw an error if feedbackPeriod is missing", function () {
expect(function () {
new loop.store.ConversationAppStore(dispatcher, {
activeRoomStore: activeRoomStore,
feedbackTimestamp: 1 });}).
to.Throw(/feedbackPeriod/);});
it("should throw an error if feedbackTimestamp is missing", function () {
expect(function () {
new loop.store.ConversationAppStore(dispatcher, {
activeRoomStore: activeRoomStore,
feedbackPeriod: 1 });}).
to.Throw(/feedbackTimestamp/);});
it("should start listening to events on the window object", function () {
var fakeWindow = {
addEventListener: sinon.stub() };
var store = new loop.store.ConversationAppStore(dispatcher, {
activeRoomStore: activeRoomStore,
feedbackPeriod: 1,
feedbackTimestamp: 1,
rootObject: fakeWindow });
var eventNames = Object.getOwnPropertyNames(store._eventHandlers);
sinon.assert.callCount(fakeWindow.addEventListener, eventNames.length);
eventNames.forEach(function (eventName) {
sinon.assert.calledWith(fakeWindow.addEventListener, eventName,
store._eventHandlers[eventName]);});});});
describe("#getWindowData", function () {
var fakeWindowData, fakeGetWindowData, store, getLoopPrefStub;
var setLoopPrefStub;
beforeEach(function () {
fakeWindowData = {
type: "room",
roomToken: "123456" };
fakeGetWindowData = {
windowId: "42" };
getLoopPrefStub = sandbox.stub();
setLoopPrefStub = sandbox.stub();
loop.storedRequests = {
"GetConversationWindowData|42": fakeWindowData };
LoopMochaUtils.stubLoopRequest({
GetLoopPref: getLoopPrefStub,
SetLoopPref: setLoopPrefStub });
store = new loop.store.ConversationAppStore(dispatcher, {
activeRoomStore: activeRoomStore,
feedbackPeriod: 42,
feedbackTimestamp: 42 });});
afterEach(function () {
sandbox.restore();});
it("should fetch the window type from the Loop API", function () {
store.getWindowData(new sharedActions.GetWindowData(fakeGetWindowData));
expect(store.getStoreState().windowType).eql("room");});
it("should have the feedback period in initial state", function () {
// Expect ms.
expect(store.getInitialStoreState().feedbackPeriod).to.eql(42 * 1000);});
it("should have the dateLastSeen in initial state", function () {
// Expect ms.
expect(store.getInitialStoreState().feedbackTimestamp).to.eql(42 * 1000);});
it("should set showFeedbackForm to true when action is triggered", function () {
var showFeedbackFormStub = sandbox.stub(store, "showFeedbackForm");
store.showFeedbackForm(new sharedActions.ShowFeedbackForm());
sinon.assert.calledOnce(showFeedbackFormStub);});
it("should set feedback timestamp on ShowFeedbackForm action", function () {
var clock = sandbox.useFakeTimers();
// Make sure we round down the value.
clock.tick(1001);
store.showFeedbackForm(new sharedActions.ShowFeedbackForm());
sinon.assert.calledOnce(setLoopPrefStub);
sinon.assert.calledWithExactly(setLoopPrefStub,
"feedback.dateLastSeenSec", 1);});
it("should dispatch a SetupWindowData action with the data from the Loop API",
function () {
store.getWindowData(new sharedActions.GetWindowData(fakeGetWindowData));
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.SetupWindowData(_.extend({
windowId: fakeGetWindowData.windowId },
fakeWindowData)));});});
describe("Window object event handlers", function () {
var store, fakeWindow;
beforeEach(function () {
fakeWindow = {
addEventListener: sinon.stub(),
removeEventListener: sinon.stub() };
LoopMochaUtils.stubLoopRequest({
GetLoopPref: function GetLoopPref() {} });
store = new loop.store.ConversationAppStore(dispatcher, {
activeRoomStore: activeRoomStore,
feedbackPeriod: 1,
feedbackTimestamp: 1,
rootObject: fakeWindow });});
describe("#unloadHandler", function () {
it("should dispatch a 'WindowUnload' action when invoked", function () {
store.unloadHandler();
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch, new sharedActions.WindowUnload());});
it("should remove all registered event handlers from the window object", function () {
var eventHandlers = store._eventHandlers;
var eventNames = Object.getOwnPropertyNames(eventHandlers);
store.unloadHandler();
sinon.assert.callCount(fakeWindow.removeEventListener, eventNames.length);
expect(store._eventHandlers).to.eql(null);
eventNames.forEach(function (eventName) {
sinon.assert.calledWith(fakeWindow.removeEventListener, eventName,
eventHandlers[eventName]);});});});
describe("#LoopHangupNowHandler", function () {
it("should dispatch a LeaveConversation action", function () {
store.LoopHangupNowHandler();
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.LeaveConversation());});});
describe("#leaveConversation", function () {
beforeEach(function () {
sandbox.stub(loop.shared.mixins.WindowCloseMixin, "closeWindow");
roomUsed = true;});
it("should close the window for window types other than `room`", function () {
store.setStoreState({ windowType: "foobar" });
store.leaveConversation();
sinon.assert.notCalled(dispatcher.dispatch);
sinon.assert.calledOnce(loop.shared.mixins.WindowCloseMixin.closeWindow);});
it("should close the window when a room was not used", function () {
store.setStoreState({ windowType: "room" });
roomUsed = false;
store.leaveConversation();
sinon.assert.notCalled(dispatcher.dispatch);
sinon.assert.calledOnce(loop.shared.mixins.WindowCloseMixin.closeWindow);});
it("should close the window when a room was used and it showed feedback", function () {
store.setStoreState({
showFeedbackForm: true,
windowType: "room" });
store.leaveConversation();
sinon.assert.notCalled(dispatcher.dispatch);
sinon.assert.calledOnce(loop.shared.mixins.WindowCloseMixin.closeWindow);});
it("should dispatch a LeaveRoom action if timestamp is 0", function () {
store.setStoreState({
feedbackTimestamp: 0,
windowType: "room" });
store.leaveConversation();
sinon.assert.calledTwice(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.LeaveRoom({
windowStayingOpen: true }));});
it("should dispatch a ShowFeedbackForm action if timestamp is 0", function () {
store.setStoreState({
feedbackTimestamp: 0,
windowType: "room" });
store.leaveConversation();
sinon.assert.calledTwice(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ShowFeedbackForm());});
it("should dispatch a LeaveRoom action if delta > feedback period", function () {
var feedbackTimestamp = new Date() - feedbackPeriodMs;
store.setStoreState({
feedbackTimestamp: feedbackTimestamp,
feedbackPeriod: feedbackPeriodMs,
windowType: "room" });
store.leaveConversation();
sinon.assert.calledTwice(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.LeaveRoom({
windowStayingOpen: true }));});
it("should dispatch a ShowFeedbackForm action if delta > feedback period", function () {
var feedbackTimestamp = new Date() - feedbackPeriodMs;
store.setStoreState({
feedbackTimestamp: feedbackTimestamp,
feedbackPeriod: feedbackPeriodMs,
windowType: "room" });
store.leaveConversation();
sinon.assert.calledTwice(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ShowFeedbackForm());});
it("should close the window if delta < feedback period", function () {
var feedbackTimestamp = new Date().getTime();
store.setStoreState({
feedbackTimestamp: feedbackTimestamp,
feedbackPeriod: feedbackPeriodMs });
store.leaveConversation();
sinon.assert.calledOnce(loop.shared.mixins.WindowCloseMixin.closeWindow);});});
describe("#socialFrameAttachedHandler", function () {
it("should update the store correctly to reflect the attached state", function () {
store.setStoreState({ chatWindowDetached: true });
store.socialFrameAttachedHandler();
expect(store.getStoreState().chatWindowDetached).to.eql(false);});});
describe("#socialFrameDetachedHandler", function () {
it("should update the store correctly to reflect the detached state", function () {
store.socialFrameDetachedHandler();
expect(store.getStoreState().chatWindowDetached).to.eql(true);});});
describe("#ToggleBrowserSharingHandler", function () {
it("should dispatch the correct action", function () {
store.ToggleBrowserSharingHandler({ detail: false });
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch, new sharedActions.ToggleBrowserSharing({
enabled: true }));});});});});

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

@ -1,341 +0,0 @@
"use strict"; /* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
describe("loop.conversation", function () {
"use strict";
var FeedbackView = loop.feedbackViews.FeedbackView;
var expect = chai.expect;
var TestUtils = React.addons.TestUtils;
var sharedActions = loop.shared.actions;
var fakeWindow, sandbox, setLoopPrefStub, mozL10nGet,
remoteCursorStore, dispatcher, requestStubs, clock;
beforeEach(function () {
sandbox = LoopMochaUtils.createSandbox();
// This ensures that the timers in ConversationToolbar are stubbed, as the
// get called when the AppControllerView is mounted.
clock = sandbox.useFakeTimers();
setLoopPrefStub = sandbox.stub();
LoopMochaUtils.stubLoopRequest(requestStubs = {
GetDoNotDisturb: function GetDoNotDisturb() {return true;},
GetAllStrings: function GetAllStrings() {
return JSON.stringify({ textContent: "fakeText" });},
GetLocale: function GetLocale() {
return "en-US";},
SetLoopPref: setLoopPrefStub,
GetLoopPref: function GetLoopPref(prefName) {
switch (prefName) {
case "debug.sdk":
case "debug.dispatcher":
return false;
default:
return "http://fake";}},
GetAllConstants: function GetAllConstants() {
return {
LOOP_SESSION_TYPE: {
GUEST: 1,
FXA: 2 },
LOOP_MAU_TYPE: {
OPEN_PANEL: 0,
OPEN_CONVERSATION: 1,
ROOM_OPEN: 2,
ROOM_SHARE: 3,
ROOM_DELETE: 4 } };},
EnsureRegistered: sinon.stub(),
GetAppVersionInfo: function GetAppVersionInfo() {
return {
version: "42",
channel: "test",
platform: "test" };},
GetAudioBlob: sinon.spy(function () {
return new Blob([new ArrayBuffer(10)], { type: "audio/ogg" });}),
GetSelectedTabMetadata: function GetSelectedTabMetadata() {
return {};},
GetConversationWindowData: function GetConversationWindowData() {
return {};},
TelemetryAddValue: sinon.stub() });
fakeWindow = {
close: sinon.stub(),
document: {},
addEventListener: function addEventListener() {},
removeEventListener: function removeEventListener() {} };
loop.shared.mixins.setRootObject(fakeWindow);
// XXX These stubs should be hoisted in a common file
// Bug 1040968
mozL10nGet = sandbox.stub(document.mozL10n, "get", function (x) {
return x;});
document.mozL10n.initialize({
getStrings: function getStrings() {return JSON.stringify({ textContent: "fakeText" });},
locale: "en_US" });
dispatcher = new loop.Dispatcher();
remoteCursorStore = new loop.store.RemoteCursorStore(dispatcher, {
sdkDriver: {} });
loop.store.StoreMixin.register({ remoteCursorStore: remoteCursorStore });});
afterEach(function () {
loop.shared.mixins.setRootObject(window);
clock.restore();
sandbox.restore();
LoopMochaUtils.restore();});
describe("#init", function () {
var OTRestore;
beforeEach(function () {
sandbox.stub(ReactDOM, "render");
sandbox.stub(document.mozL10n, "initialize");
sandbox.stub(loop.Dispatcher.prototype, "dispatch");
sandbox.stub(loop.shared.utils,
"locationData").returns({
hash: "#42",
pathname: "/" });
OTRestore = window.OT;
window.OT = {
overrideGuidStorage: sinon.stub() };});
afterEach(function () {
window.OT = OTRestore;});
it("should initialize L10n", function () {
loop.conversation.init();
sinon.assert.calledOnce(document.mozL10n.initialize);
sinon.assert.calledWith(document.mozL10n.initialize, sinon.match({ locale: "en-US" }));});
it("should create the AppControllerView", function () {
loop.conversation.init();
sinon.assert.calledOnce(ReactDOM.render);
sinon.assert.calledWith(ReactDOM.render,
sinon.match(function (value) {
return TestUtils.isCompositeComponentElement(value,
loop.conversation.AppControllerView);}));});
it("should trigger a getWindowData action", function () {
loop.conversation.init();
sinon.assert.calledOnce(loop.Dispatcher.prototype.dispatch);
sinon.assert.calledWithExactly(loop.Dispatcher.prototype.dispatch,
new loop.shared.actions.GetWindowData({
windowId: "42" }));});
it("should log a telemetry event when opening the conversation window", function () {
var constants = requestStubs.GetAllConstants();
loop.conversation.init();
sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"],
"LOOP_ACTIVITY_COUNTER", constants.LOOP_MAU_TYPE.OPEN_CONVERSATION);});});
describe("AppControllerView", function () {
var activeRoomStore,
ccView,
addRemoteCursorStub,
clickRemoteCursorStub;
var conversationAppStore,
roomStore;
var ROOM_STATES = loop.store.ROOM_STATES;
function mountTestComponent() {
return TestUtils.renderIntoDocument(
React.createElement(loop.conversation.AppControllerView, {
cursorStore: remoteCursorStore,
dispatcher: dispatcher,
roomStore: roomStore }));}
beforeEach(function () {
activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, {
mozLoop: {},
sdkDriver: {} });
roomStore = new loop.store.RoomStore(dispatcher, {
activeRoomStore: activeRoomStore,
constants: {} });
remoteCursorStore = new loop.store.RemoteCursorStore(dispatcher, {
sdkDriver: {} });
conversationAppStore = new loop.store.ConversationAppStore(dispatcher, {
activeRoomStore: activeRoomStore,
feedbackPeriod: 42,
feedbackTimestamp: 42,
facebookEnabled: false });
loop.store.StoreMixin.register({
conversationAppStore: conversationAppStore });
addRemoteCursorStub = sandbox.stub();
clickRemoteCursorStub = sandbox.stub();
LoopMochaUtils.stubLoopRequest({
AddRemoteCursorOverlay: addRemoteCursorStub,
ClickRemoteCursor: clickRemoteCursorStub });
loop.config = {
tilesIframeUrl: null,
tilesSupportUrl: null };
sinon.stub(dispatcher, "dispatch");});
afterEach(function () {
ccView = undefined;});
it("should request AddRemoteCursorOverlay when cursor position changes", function () {
mountTestComponent();
remoteCursorStore.setStoreState({
"remoteCursorPosition": {
"ratioX": 10,
"ratioY": 10 } });
sinon.assert.calledOnce(addRemoteCursorStub);});
it("should NOT request AddRemoteCursorOverlay when cursor position DOES NOT changes", function () {
mountTestComponent();
remoteCursorStore.setStoreState({
"realVideoSize": {
"height": 400,
"width": 600 } });
sinon.assert.notCalled(addRemoteCursorStub);});
it("should request ClickRemoteCursor when click event detected", function () {
mountTestComponent();
remoteCursorStore.setStoreState({
"remoteCursorClick": true });
sinon.assert.calledOnce(clickRemoteCursorStub);});
it("should NOT request ClickRemoteCursor when reset click on store", function () {
mountTestComponent();
remoteCursorStore.setStoreState({
"remoteCursorClick": false });
sinon.assert.notCalled(clickRemoteCursorStub);});
it("should display the RoomView for rooms", function () {
conversationAppStore.setStoreState({ windowType: "room" });
activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
ccView = mountTestComponent();
var desktopRoom = TestUtils.findRenderedComponentWithType(ccView,
loop.roomViews.DesktopRoomConversationView);
expect(desktopRoom.props.facebookEnabled).to.eql(false);});
it("should pass the correct value of facebookEnabled to DesktopRoomConversationView",
function () {
conversationAppStore.setStoreState({
windowType: "room",
facebookEnabled: true });
activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
ccView = mountTestComponent();
var desktopRoom = TestUtils.findRenderedComponentWithType(ccView,
loop.roomViews.DesktopRoomConversationView);
expect(desktopRoom.props.facebookEnabled).to.eql(true);});
it("should display the RoomFailureView for failures", function () {
conversationAppStore.setStoreState({
outgoing: false,
windowType: "failed" });
ccView = mountTestComponent();
TestUtils.findRenderedComponentWithType(ccView,
loop.roomViews.RoomFailureView);});
it("should set the correct title when rendering feedback view", function () {
conversationAppStore.setStoreState({ showFeedbackForm: true });
ccView = mountTestComponent();
sinon.assert.calledWithExactly(mozL10nGet, "conversation_has_ended");});
it("should render FeedbackView if showFeedbackForm state is true",
function () {
conversationAppStore.setStoreState({ showFeedbackForm: true });
ccView = mountTestComponent();
TestUtils.findRenderedComponentWithType(ccView, FeedbackView);});
it("should dispatch LeaveConversation when handleCallTerminated is called", function () {
ccView = mountTestComponent();
ccView.handleCallTerminated();
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.LeaveConversation());});});});

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

@ -1,122 +0,0 @@
"use strict"; /* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
describe("loop.copy", function () {
"use strict";
var expect = chai.expect;
var CopyView = loop.copy.CopyView;
var l10n = navigator.mozL10n || document.mozL10n;
var TestUtils = React.addons.TestUtils;
var sandbox;
beforeEach(function () {
sandbox = LoopMochaUtils.createSandbox();
sandbox.stub(l10n, "get");});
afterEach(function () {
loop.shared.mixins.setRootObject(window);
sandbox.restore();
LoopMochaUtils.restore();});
describe("#init", function () {
beforeEach(function () {
sandbox.stub(ReactDOM, "render");
sandbox.stub(document.mozL10n, "initialize");});
it("should initalize L10n", function () {
loop.copy.init();
sinon.assert.calledOnce(document.mozL10n.initialize);});
it("should render the copy view", function () {
loop.copy.init();
sinon.assert.calledOnce(ReactDOM.render);
sinon.assert.calledWith(ReactDOM.render, sinon.match(function (value) {
return value.type === CopyView;}));});});
describe("#render", function () {
var view;
beforeEach(function () {
view = TestUtils.renderIntoDocument(React.createElement(CopyView));});
it("should have an unchecked box", function () {
expect(ReactDOM.findDOMNode(view).querySelector("input[type=checkbox]").checked).eql(false);});
it("should have two buttons", function () {
expect(ReactDOM.findDOMNode(view).querySelectorAll("button").length).eql(2);});});
describe("handleChanges", function () {
var view;
beforeEach(function () {
view = TestUtils.renderIntoDocument(React.createElement(CopyView));});
it("should have default state !checked", function () {
expect(view.state.checked).to.be.false;});
it("should have checked state after change", function () {
TestUtils.Simulate.change(ReactDOM.findDOMNode(view).querySelector("input"), {
target: { checked: true } });
expect(view.state.checked).to.be.true;});});
describe("handleClicks", function () {
var view;
beforeEach(function () {
sandbox.stub(window, "dispatchEvent");
view = TestUtils.renderIntoDocument(React.createElement(CopyView));});
function checkDispatched(detail) {
sinon.assert.calledOnce(window.dispatchEvent);
sinon.assert.calledWith(window.dispatchEvent, sinon.match.has("detail", detail));}
it("should dispatch accept !stop on accept", function () {
TestUtils.Simulate.click(ReactDOM.findDOMNode(view).querySelector("button:last-child"));
checkDispatched({ accept: true, stop: false });});
it("should dispatch !accept !stop on cancel", function () {
TestUtils.Simulate.click(ReactDOM.findDOMNode(view).querySelector("button"));
checkDispatched({ accept: false, stop: false });});
it("should dispatch accept stop on checked accept", function () {
TestUtils.Simulate.change(ReactDOM.findDOMNode(view).querySelector("input"), {
target: { checked: true } });
TestUtils.Simulate.click(ReactDOM.findDOMNode(view).querySelector("button:last-child"));
checkDispatched({ accept: true, stop: true });});
it("should dispatch !accept stop on checked cancel", function () {
TestUtils.Simulate.change(ReactDOM.findDOMNode(view).querySelector("input"), {
target: { checked: true } });
TestUtils.Simulate.click(ReactDOM.findDOMNode(view).querySelector("button"));
checkDispatched({ accept: false, stop: true });});});});

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

@ -1,270 +0,0 @@
"use strict"; /* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
describe("loop.shared.desktopViews", function () {
"use strict";
var expect = chai.expect;
var l10n = navigator.mozL10n || document.mozL10n;
var TestUtils = React.addons.TestUtils;
var sharedActions = loop.shared.actions;
var sharedDesktopViews = loop.shared.desktopViews;
var sandbox, clock, dispatcher;
beforeEach(function () {
sandbox = LoopMochaUtils.createSandbox();
clock = sandbox.useFakeTimers(); // exposes sandbox.clock as a fake timer
sandbox.stub(l10n, "get", function (x) {
return "translated:" + x;});
LoopMochaUtils.stubLoopRequest({
GetLoopPref: function GetLoopPref() {
return true;} });
dispatcher = new loop.Dispatcher();
sandbox.stub(dispatcher, "dispatch");});
afterEach(function () {
sandbox.restore();
LoopMochaUtils.restore();});
describe("CopyLinkButton", function () {
var view;
function mountTestComponent(props) {
props = _.extend({
dispatcher: dispatcher,
locationForMetrics: "conversation",
roomData: {
roomUrl: "http://invalid",
roomContextUrls: [{
location: "fakeLocation",
url: "fakeUrl" }] } },
props || {});
return TestUtils.renderIntoDocument(
React.createElement(sharedDesktopViews.CopyLinkButton, props));}
beforeEach(function () {
view = mountTestComponent();});
it("should dispatch a CopyRoomUrl action when the copy button is pressed", function () {
var copyBtn = ReactDOM.findDOMNode(view);
React.addons.TestUtils.Simulate.click(copyBtn);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWith(dispatcher.dispatch, new sharedActions.CopyRoomUrl({
roomUrl: "http://invalid",
from: "conversation" }));});
it("should change the text when the url has been copied", function () {
var copyBtn = ReactDOM.findDOMNode(view);
React.addons.TestUtils.Simulate.click(copyBtn);
expect(copyBtn.textContent).eql("translated:invite_copied_link_button");});
it("should keep the text for a while after the url has been copied", function () {
var copyBtn = ReactDOM.findDOMNode(view);
React.addons.TestUtils.Simulate.click(copyBtn);
clock.tick(sharedDesktopViews.CopyLinkButton.TRIGGERED_RESET_DELAY / 2);
expect(copyBtn.textContent).eql("translated:invite_copied_link_button");});
it("should reset the text a bit after the url has been copied", function () {
var copyBtn = ReactDOM.findDOMNode(view);
React.addons.TestUtils.Simulate.click(copyBtn);
clock.tick(sharedDesktopViews.CopyLinkButton.TRIGGERED_RESET_DELAY);
expect(copyBtn.textContent).eql("translated:invite_copy_link_button");});
it("should invoke callback if defined", function () {
var callback = sinon.stub();
view = mountTestComponent({
callback: callback });
var copyBtn = ReactDOM.findDOMNode(view);
React.addons.TestUtils.Simulate.click(copyBtn);
clock.tick(sharedDesktopViews.CopyLinkButton.TRIGGERED_RESET_DELAY);
sinon.assert.calledOnce(callback);});});
describe("EmailLinkButton", function () {
var view;
function mountTestComponent(props) {
props = _.extend({
dispatcher: dispatcher,
locationForMetrics: "conversation",
roomData: {
roomUrl: "http://invalid",
roomContextUrls: [] } },
props || {});
return TestUtils.renderIntoDocument(
React.createElement(sharedDesktopViews.EmailLinkButton, props));}
beforeEach(function () {
view = mountTestComponent();});
it("should dispatch an EmailRoomUrl with no description" +
" for rooms without context when the email button is pressed",
function () {
var emailBtn = ReactDOM.findDOMNode(view);
React.addons.TestUtils.Simulate.click(emailBtn);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWith(dispatcher.dispatch,
new sharedActions.EmailRoomUrl({
roomUrl: "http://invalid",
roomDescription: undefined,
from: "conversation" }));});
it("should dispatch an EmailRoomUrl with a domain name description for rooms with context",
function () {
view = mountTestComponent({
roomData: {
roomUrl: "http://invalid",
roomContextUrls: [{ location: "http://www.mozilla.com/" }] } });
var emailBtn = ReactDOM.findDOMNode(view);
React.addons.TestUtils.Simulate.click(emailBtn);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWith(dispatcher.dispatch,
new sharedActions.EmailRoomUrl({
roomUrl: "http://invalid",
roomDescription: "www.mozilla.com",
from: "conversation" }));});
it("should invoke callback if defined", function () {
var callback = sinon.stub();
view = mountTestComponent({
callback: callback });
var emailBtn = ReactDOM.findDOMNode(view);
React.addons.TestUtils.Simulate.click(emailBtn);
sinon.assert.calledOnce(callback);});});
describe("FacebookShareButton", function () {
var view;
function mountTestComponent(props) {
props = _.extend({
dispatcher: dispatcher,
locationForMetrics: "conversation",
roomData: {
roomUrl: "http://invalid",
roomContextUrls: [] } },
props || {});
return TestUtils.renderIntoDocument(
React.createElement(sharedDesktopViews.FacebookShareButton, props));}
it("should dispatch a FacebookShareRoomUrl action when the facebook button is clicked",
function () {
view = mountTestComponent();
var facebookBtn = ReactDOM.findDOMNode(view);
React.addons.TestUtils.Simulate.click(facebookBtn);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWith(dispatcher.dispatch,
new sharedActions.FacebookShareRoomUrl({
from: "conversation",
roomUrl: "http://invalid" }));});
it("should invoke callback if defined", function () {
var callback = sinon.stub();
view = mountTestComponent({
callback: callback });
var facebookBtn = ReactDOM.findDOMNode(view);
React.addons.TestUtils.Simulate.click(facebookBtn);
sinon.assert.calledOnce(callback);});});
describe("SharePanelView", function () {
var view;
function mountTestComponent(props) {
props = _.extend({
dispatcher: dispatcher,
facebookEnabled: false,
locationForMetrics: "conversation",
roomData: { roomUrl: "http://invalid" },
savingContext: false,
show: true,
showEditContext: false },
props);
return TestUtils.renderIntoDocument(
React.createElement(sharedDesktopViews.SharePanelView, props));}
it("should not display the Facebook Share button when it is disabled in prefs",
function () {
view = mountTestComponent({
facebookEnabled: false });
expect(ReactDOM.findDOMNode(view).querySelectorAll(".btn-facebook")).
to.have.length.of(0);});
it("should display the Facebook Share button only when it is enabled in prefs",
function () {
view = mountTestComponent({
facebookEnabled: true });
expect(ReactDOM.findDOMNode(view).querySelectorAll(".btn-facebook")).
to.have.length.of(1);});
it("should not display the panel when show prop is false", function () {
view = mountTestComponent({
show: false });
expect(ReactDOM.findDOMNode(view)).eql(null);});
it("should not display the panel when roomUrl is not defined", function () {
view = mountTestComponent({
roomData: {} });
expect(ReactDOM.findDOMNode(view)).eql(null);});});});

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

@ -1,21 +0,0 @@
"use strict"; /* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
(function () {
"use strict";
// Create fake Components to test chrome-privileged React components.
window.Components = {
utils: {
import: function _import() {
return {
LoopAPI: {
sendMessageToHandler: function sendMessageToHandler(_ref, callback) {var name = _ref.name;
switch (name) {
case "GetLocale":
return callback("en-US");
case "GetPluralRule":
return callback(1);
default:
return callback();}} } };} } };})();

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

@ -1,115 +0,0 @@
"use strict"; /* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
describe("loop.feedbackViews", function () {
"use strict";
var FeedbackView = loop.feedbackViews.FeedbackView;
var l10n = navigator.mozL10n || document.mozL10n;
var TestUtils = React.addons.TestUtils;
var sandbox, mozL10nGet;
beforeEach(function () {
sandbox = LoopMochaUtils.createSandbox();
mozL10nGet = sandbox.stub(l10n, "get", function (x) {
return "translated:" + x;});});
afterEach(function () {
sandbox.restore();});
describe("FeedbackView", function () {
var openURLStub, getLoopPrefStub, feedbackReceivedStub, getAddonVersionStub;
var view;
var fakeURL = "fake.form?version=";
var addonVersion = "1.3.0";
function mountTestComponent(props) {
props = _.extend({
onAfterFeedbackReceived: feedbackReceivedStub },
props);
return TestUtils.renderIntoDocument(
React.createElement(FeedbackView, props));}
beforeEach(function () {
openURLStub = sandbox.stub();
getLoopPrefStub = sandbox.stub();
feedbackReceivedStub = sandbox.stub();
getAddonVersionStub = sandbox.stub();
LoopMochaUtils.stubLoopRequest({
OpenURL: openURLStub,
GetLoopPref: getLoopPrefStub,
GetAddonVersion: getAddonVersionStub });});
afterEach(function () {
view = null;
LoopMochaUtils.restore();});
it("should render a feedback view", function () {
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view, FeedbackView);});
it("should render a button with correct text", function () {
view = mountTestComponent();
sinon.assert.calledWithExactly(mozL10nGet, "feedback_request_button");});
it("should render a header with correct text", function () {
view = mountTestComponent();
sinon.assert.calledWithExactly(mozL10nGet, "feedback_window_heading");});
it("should open a new page to the feedback form", function () {
getLoopPrefStub.withArgs("feedback.formURL").returns(fakeURL);
getAddonVersionStub.returns(addonVersion);
view = mountTestComponent();
TestUtils.Simulate.click(ReactDOM.findDOMNode(view.refs.feedbackFormBtn));
sinon.assert.calledOnce(openURLStub);
sinon.assert.calledWithExactly(openURLStub, fakeURL);});
it("should fetch the feedback form URL from the prefs", function () {
getLoopPrefStub.withArgs("feedback.formURL").returns(fakeURL);
getAddonVersionStub.returns(addonVersion);
view = mountTestComponent();
TestUtils.Simulate.click(ReactDOM.findDOMNode(view.refs.feedbackFormBtn));
sinon.assert.calledOnce(getLoopPrefStub);
sinon.assert.calledWithExactly(getLoopPrefStub, "feedback.formURL");});
it("should fetch the addon version", function () {
getLoopPrefStub.withArgs("feedback.formURL").returns(fakeURL);
getAddonVersionStub.returns(addonVersion);
view = mountTestComponent();
TestUtils.Simulate.click(ReactDOM.findDOMNode(view.refs.feedbackFormBtn));
sinon.assert.calledOnce(getAddonVersionStub);
sinon.assert.calledWithExactly(getAddonVersionStub);});
it("should close the window after opening the form", function () {
getLoopPrefStub.withArgs("feedback.formURL").returns(fakeURL);
getAddonVersionStub.returns(addonVersion);
view = mountTestComponent();
TestUtils.Simulate.click(ReactDOM.findDOMNode(view.refs.feedbackFormBtn));
sinon.assert.calledOnce(feedbackReceivedStub);});});});

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

@ -1,99 +0,0 @@
<!DOCTYPE html>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html>
<head>
<meta charset="utf-8">
<title>Loop desktop-local mocha tests</title>
<link rel="stylesheet" media="all" href="/test/vendor/mocha.css">
</head>
<body>
<div id="mocha">
<p><a href="../">Index</a></p>
</div>
<div id="messages"></div>
<div id="fixtures"></div>
<script src="/shared/vendor/lodash.js"></script>
<script src="/shared/test/loop_mocha_utils.js"></script>
<script>
LoopMochaUtils.trapErrors();
</script>
<!-- libs -->
<!-- test dependencies -->
<script src="/add-on/panels/vendor/l10n.js"></script>
<script src="/shared/vendor/react.js"></script>
<script src="/shared/vendor/react-dom.js"></script>
<script src="/shared/vendor/classnames.js"></script>
<script src="/shared/vendor/backbone.js"></script>
<script src="/add-on/panels/vendor/simpleSlideshow.js"></script>
<!-- test dependencies -->
<script src="/test/vendor/mocha.js"></script>
<script src="/test/vendor/chai.js"></script>
<script src="/test/vendor/chai-as-promised.js"></script>
<script src="/test/vendor/sinon.js"></script>
<script>
/*global chai,mocha */
chai.config.includeStack = true;
mocha.setup({ui: 'bdd', timeout: 10000});
</script>
<!-- App scripts -->
<script src="/add-on/shared/js/loopapi-client.js"></script>
<script src="/add-on/shared/js/utils.js"></script>
<script src="/add-on/panels/js/models.js"></script>
<script src="/add-on/shared/js/mixins.js"></script>
<script src="/add-on/shared/js/actions.js"></script>
<script src="/add-on/shared/js/validate.js"></script>
<script src="/add-on/shared/js/dispatcher.js"></script>
<script src="/add-on/shared/js/otSdkDriver.js"></script>
<!-- Load fake-components after loading utils to avoid chrome detection. -->
<script src="/add-on/panels/test/fake-components.js"></script>
<!-- Stores need to be loaded before the views that uses them -->
<script src="/add-on/shared/js/store.js"></script>
<script src="/add-on/panels/js/roomStore.js"></script>
<script src="/add-on/shared/js/activeRoomStore.js"></script>
<script src="/add-on/panels/js/conversationAppStore.js"></script>
<script src="/add-on/shared/js/textChatStore.js"></script>
<script src="/add-on/shared/js/remoteCursorStore.js"></script>
<!-- Views -->
<script src="/add-on/shared/js/views.js"></script>
<script src="/add-on/shared/js/textChatView.js"></script>
<script src="/add-on/panels/js/desktopViews.js"></script>
<script src="/add-on/panels/js/roomViews.js"></script>
<script src="/add-on/panels/js/feedbackViews.js"></script>
<script src="/add-on/panels/js/conversation.js"></script>
<script src="/add-on/panels/js/copy.js"></script>
<script src="/add-on/panels/js/panel.js"></script>
<script src="/add-on/panels/js/slideshow.js"></script>
<!-- Test scripts -->
<script src="conversationAppStore_test.js"></script>
<script src="conversation_test.js"></script>
<script src="copy_test.js"></script>
<script src="feedbackViews_test.js"></script>
<script src="panel_test.js"></script>
<script src="desktopViews_test.js"></script>
<script src="roomViews_test.js"></script>
<script src="l10n_test.js"></script>
<script src="roomStore_test.js"></script>
<script src="slideshow_test.js"></script>
<script>
// Stop the default init functions running to avoid conflicts in tests
document.removeEventListener('DOMContentLoaded', loop.panel.init);
document.removeEventListener('DOMContentLoaded', loop.conversation.init);
document.removeEventListener('DOMContentLoaded', loop.copy.init);
document.removeEventListener("DOMContentLoaded", loop.slideshow.init);
LoopMochaUtils.addErrorCheckingTests();
LoopMochaUtils.runTests();
</script>
</body>
</html>

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

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

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

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

@ -1,958 +0,0 @@
"use strict"; /* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var expect = chai.expect;
describe("loop.store.Room", function () {
"use strict";
describe("#constructor", function () {
it("should validate room values", function () {
expect(function () {
new loop.store.Room();}).
to.Throw(Error, /missing required/);});});});
describe("loop.store.RoomStore", function () {
"use strict";
var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var sandbox, dispatcher;
var fakeRoomList, requestStubs;
beforeEach(function () {
sandbox = LoopMochaUtils.createSandbox();
LoopMochaUtils.stubLoopRequest(requestStubs = {
GetAllConstants: function GetAllConstants() {
return {
SHARING_ROOM_URL: {
COPY_FROM_PANEL: 0,
COPY_FROM_CONVERSATION: 1,
EMAIL_FROM_CALLFAILED: 2,
EMAIL_FROM_CONVERSATION: 3,
FACEBOOK_FROM_CONVERSATION: 4,
EMAIL_FROM_PANEL: 5 },
ROOM_CREATE: {
CREATE_SUCCESS: 0,
CREATE_FAIL: 1 },
LOOP_MAU_TYPE: {
OPEN_PANEL: 0,
OPEN_CONVERSATION: 1,
ROOM_OPEN: 2,
ROOM_SHARE: 3,
ROOM_DELETE: 4 } };},
CopyString: sinon.stub(),
ComposeEmail: sinon.stub(),
GetLoopPref: function GetLoopPref(prefName) {
if (prefName === "debug.dispatcher") {
return false;}
return true;},
NotifyUITour: function NotifyUITour() {},
OpenURL: sinon.stub(),
"Rooms:Create": sinon.stub().returns({
decryptedContext: [],
roomToken: "fakeToken",
roomUrl: "fakeUrl" }),
"Rooms:Delete": sinon.stub(),
"Rooms:GetAll": sinon.stub(),
"Rooms:Open": sinon.stub(),
"Rooms:Rename": sinon.stub(),
"Rooms:PushSubscription": sinon.stub(),
"SetLoopPref": sinon.stub(),
"SetNameNewRoom": sinon.stub(),
TelemetryAddValue: sinon.stub() });
dispatcher = new loop.Dispatcher();
fakeRoomList = [{
roomToken: "_nxD4V4FflQ",
roomUrl: "http://sample/_nxD4V4FflQ",
roomName: "First Room Name",
maxSize: 2,
participants: [],
ctime: 1405517546 },
{
roomToken: "QzBbvGmIZWU",
roomUrl: "http://sample/QzBbvGmIZWU",
roomName: "Second Room Name",
maxSize: 2,
participants: [],
ctime: 1405517418 },
{
roomToken: "3jKS_Els9IU",
roomUrl: "http://sample/3jKS_Els9IU",
roomName: "Third Room Name",
maxSize: 3,
clientMaxSize: 2,
participants: [],
ctime: 1405518241 }];
document.mozL10n.get = function (str) {
return str;};});
afterEach(function () {
sandbox.restore();
LoopMochaUtils.restore();});
describe("#constructor", function () {
it("should throw an error if constants are missing", function () {
expect(function () {
new loop.store.RoomStore(dispatcher);}).
to.Throw(/constants/);});});
describe("constructed", function () {
var fakeNotifications, store;
var defaultStoreState = {
error: undefined,
pendingCreation: false,
pendingInitialRetrieval: true,
rooms: [],
activeRoom: {} };
beforeEach(function () {
fakeNotifications = {
set: sinon.stub(),
remove: sinon.stub() };
store = new loop.store.RoomStore(dispatcher, {
constants: requestStubs.GetAllConstants(),
notifications: fakeNotifications });
store.setStoreState(defaultStoreState);});
describe("MozLoop rooms event listeners", function () {
beforeEach(function () {
LoopMochaUtils.stubLoopRequest({
"Rooms:GetAll": function RoomsGetAll() {
return fakeRoomList;} });
store.getAllRooms(); // registers event listeners
});
describe("add", function () {
it("should add the room entry to the list", function () {
LoopMochaUtils.publish("Rooms:Add", {
roomToken: "newToken",
roomUrl: "http://sample/newToken",
roomName: "New room",
maxSize: 2,
participants: [],
ctime: 1405517546 });
expect(store.getStoreState().rooms).to.have.length.of(4);});
it("should avoid adding a duplicate room", function () {
var sampleRoom = fakeRoomList[0];
LoopMochaUtils.publish("Rooms:Add", sampleRoom);
expect(store.getStoreState().rooms).to.have.length.of(3);
expect(store.getStoreState().rooms.reduce(function (count, room) {
return count + (room.roomToken === sampleRoom.roomToken ? 1 : 0);},
0)).eql(1);});});
describe("close", function () {
it("should request rename if closing room is last created", function () {
store.setStoreState({
openedRoom: "fakeToken",
lastCreatedRoom: "fakeToken" });
sinon.stub(store, "setStoreState");
LoopMochaUtils.publish("Rooms:Close");
sinon.assert.calledTwice(store.setStoreState);
sinon.assert.calledWithExactly(store.setStoreState.getCall(0),
{ closingNewRoom: true });
sinon.assert.calledOnce(requestStubs.SetNameNewRoom);});
it("should not request rename if last created and opened room are null", function () {
store.setStoreState({
lastCreatedRoom: null,
openedRoom: null });
sinon.stub(store, "setStoreState");
LoopMochaUtils.publish("Rooms:Close");
sinon.assert.notCalled(requestStubs.SetNameNewRoom);});
it("should end up updating closingNewRoom state to false", function () {
store.setStoreState({ closingNewRoom: "true" });
LoopMochaUtils.publish("Rooms:Close");
expect(store.getStoreState().closingNewRoom).to.eql(false);});
it("should update the lastCreatedRoom state to null", function () {
store.setStoreState({ lastCreatedRoom: "fake1234" });
LoopMochaUtils.publish("Rooms:Close");
expect(store.getStoreState().lastCreatedRoom).to.eql(null);});
it("should update the openedRoom state to null", function () {
store.setStoreState({ openedRoom: "fake1234" });
LoopMochaUtils.publish("Rooms:Close");
expect(store.getStoreState().openedRoom).to.eql(null);});});
describe("open", function () {
it("should update the openedRoom state to the room token", function () {
LoopMochaUtils.publish("Rooms:Open", "fake1234");
expect(store.getStoreState().openedRoom).to.eql("fake1234");});});
describe("update", function () {
it("should update a room entry", function () {
LoopMochaUtils.publish("Rooms:Update", {
roomToken: "_nxD4V4FflQ",
roomUrl: "http://sample/_nxD4V4FflQ",
roomName: "Changed First Room Name",
maxSize: 2,
participants: [],
ctime: 1405517546 });
expect(store.getStoreState().rooms).to.have.length.of(3);
expect(store.getStoreState().rooms.some(function (room) {
return room.roomName === "Changed First Room Name";})).
eql(true);});});
describe("delete", function () {
it("should delete a room from the list", function () {
LoopMochaUtils.publish("Rooms:Delete", {
roomToken: "_nxD4V4FflQ" });
expect(store.getStoreState().rooms).to.have.length.of(2);
expect(store.getStoreState().rooms.some(function (room) {
return room.roomToken === "_nxD4V4FflQ";})).
eql(false);});});
describe("refresh", function () {
it("should clear the list of rooms", function () {
LoopMochaUtils.publish("Rooms:Refresh");
expect(store.getStoreState().rooms).to.have.length.of(0);});});});
describe("#createRoom", function () {
var fakeRoomCreationData;
beforeEach(function () {
sandbox.stub(dispatcher, "dispatch");
store.setStoreState({ pendingCreation: false, rooms: [] });
fakeRoomCreationData = {};});
it("should clear any existing room errors", function () {
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
sinon.assert.calledOnce(fakeNotifications.remove);
sinon.assert.calledWithExactly(fakeNotifications.remove,
"create-room-error");});
it("should request creation of a new room", function () {
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
sinon.assert.calledWith(requestStubs["Rooms:Create"], {
decryptedContext: {},
maxSize: store.maxRoomCreationSize });});
it("should request creation of a new room with context", function () {
fakeRoomCreationData.urls = [{
location: "http://invalid.com",
description: "fakeSite",
thumbnail: "fakeimage.png" }];
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
sinon.assert.calledWith(requestStubs["Rooms:Create"], {
decryptedContext: {
urls: [{
location: "http://invalid.com",
description: "fakeSite",
thumbnail: "fakeimage.png" }] },
maxSize: store.maxRoomCreationSize });});
it("should switch the pendingCreation state flag to true", function () {
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
expect(store.getStoreState().pendingCreation).eql(true);});
it("should store the room token as the last created", function () {
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
expect(store.getStoreState().lastCreatedRoom).eql("fakeToken");});
it("should dispatch a CreatedRoom action once the operation is done",
function () {
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.CreatedRoom({
decryptedContext: [],
roomToken: "fakeToken",
roomUrl: "fakeUrl" }));});
it("should dispatch a CreateRoomError action if the operation fails",
function () {
var err = new Error("fake");
err.isError = true;
requestStubs["Rooms:Create"].returns(err);
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.CreateRoomError({
error: err }));});
it("should dispatch a CreateRoomError action if the operation fails with no result",
function () {
requestStubs["Rooms:Create"].returns();
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.CreateRoomError({
error: new Error("no result") }));});
it("should log a telemetry event when the operation is successful", function () {
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
"LOOP_ROOM_CREATE", 0);});
it("should log a telemetry event when the operation fails", function () {
var err = new Error("fake");
err.isError = true;
requestStubs["Rooms:Create"].returns(err);
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
"LOOP_ROOM_CREATE", 1);});
it("should log a telemetry event when the operation fails with no result", function () {
requestStubs["Rooms:Create"].returns();
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
"LOOP_ROOM_CREATE", 1);});});
describe("#createdRoom", function () {
beforeEach(function () {
sandbox.stub(dispatcher, "dispatch");});
it("should switch the pendingCreation state flag to false", function () {
store.setStoreState({ pendingCreation: true });
store.createdRoom(new sharedActions.CreatedRoom({
decryptedContext: [],
roomToken: "fakeToken",
roomUrl: "fakeUrl" }));
expect(store.getStoreState().pendingCreation).eql(false);});
it("should not dispatch an OpenRoom action once the operation is done",
function () {
store.createdRoom(new sharedActions.CreatedRoom({
decryptedContext: [],
roomToken: "fakeToken",
roomUrl: "fakeUrl" }));
sinon.assert.notCalled(dispatcher.dispatch);});
it("should save the state of the active room", function () {
store.createdRoom(new sharedActions.CreatedRoom({
decryptedContext: [],
roomToken: "fakeToken",
roomUrl: "fakeUrl" }));
expect(store.getStoreState().activeRoom).eql({
decryptedContext: [],
roomToken: "fakeToken",
roomUrl: "fakeUrl" });});});
describe("#createRoomError", function () {
it("should switch the pendingCreation state flag to false", function () {
store.setStoreState({ pendingCreation: true });
store.createRoomError({
error: new Error("fake") });
expect(store.getStoreState().pendingCreation).eql(false);});
it("should set a notification", function () {
store.createRoomError({
error: new Error("fake") });
sinon.assert.calledOnce(fakeNotifications.set);
sinon.assert.calledWithMatch(fakeNotifications.set, {
id: "create-room-error",
level: "error" });});});
describe("#deleteRoom", function () {
var fakeRoomToken = "42abc";
beforeEach(function () {
sandbox.stub(dispatcher, "dispatch");});
it("should request deletion of a room", function () {
store.deleteRoom(new sharedActions.DeleteRoom({
roomToken: fakeRoomToken }));
sinon.assert.calledWith(requestStubs["Rooms:Delete"], fakeRoomToken);});
it("should dispatch a DeleteRoomError action if 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.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.DeleteRoomError({
error: err }));});});
describe("#copyRoomUrl", function () {
it("should copy the room URL", function () {
store.copyRoomUrl(new sharedActions.CopyRoomUrl({
roomUrl: "http://invalid",
from: "conversation" }));
sinon.assert.calledOnce(requestStubs.CopyString);
sinon.assert.calledWithExactly(requestStubs.CopyString, "http://invalid");});});
describe("#emailRoomUrl", function () {
it("should call composeCallUrlEmail to email the url", function () {
sandbox.stub(sharedUtils, "composeCallUrlEmail");
store.emailRoomUrl(new sharedActions.EmailRoomUrl({
roomUrl: "http://invalid",
from: "conversation" }));
sinon.assert.calledOnce(sharedUtils.composeCallUrlEmail);
sinon.assert.calledWith(sharedUtils.composeCallUrlEmail,
"http://invalid", null, undefined);});
it("should call composeUrlEmail differently with context", function () {
sandbox.stub(sharedUtils, "composeCallUrlEmail");
var url = "http://invalid";
var description = "Hello, is it me you're looking for?";
store.emailRoomUrl(new sharedActions.EmailRoomUrl({
roomUrl: url,
roomDescription: description,
from: "conversation" }));
sinon.assert.calledOnce(sharedUtils.composeCallUrlEmail);
sinon.assert.calledWithExactly(sharedUtils.composeCallUrlEmail,
url, null, description);});});
describe("#facebookShareRoomUrl", function () {
var getLoopPrefStub;
var sharingSite = "www.sharing-site.com",
shareURL = sharingSite +
"?app_id=%APP_ID%" +
"&link=%ROOM_URL%" +
"&redirect_uri=%REDIRECT_URI%",
appId = "1234567890",
fallback = "www.fallback.com";
beforeEach(function () {
getLoopPrefStub = sinon.stub();
getLoopPrefStub.withArgs("facebook.appId").returns(appId);
getLoopPrefStub.withArgs("facebook.shareUrl").returns(shareURL);
getLoopPrefStub.withArgs("facebook.fallbackUrl").returns(fallback);
LoopMochaUtils.stubLoopRequest({
GetLoopPref: getLoopPrefStub });});
it("should open the facebook share url with correct room and redirection", function () {
var room = "invalid.room",
origin = "origin.url";
store.facebookShareRoomUrl(new sharedActions.FacebookShareRoomUrl({
from: "conversation",
originUrl: origin,
roomUrl: room }));
sinon.assert.calledOnce(requestStubs.OpenURL);
sinon.assert.calledWithMatch(requestStubs.OpenURL, sharingSite);
sinon.assert.calledWithMatch(requestStubs.OpenURL, room);
sinon.assert.calledWithMatch(requestStubs.OpenURL, origin);});
it("if no origin URL, send fallback URL", function () {
var room = "invalid.room";
store.facebookShareRoomUrl(new sharedActions.FacebookShareRoomUrl({
from: "conversation",
roomUrl: room }));
sinon.assert.calledOnce(requestStubs.OpenURL);
sinon.assert.calledWithMatch(requestStubs.OpenURL, sharingSite);
sinon.assert.calledWithMatch(requestStubs.OpenURL, room);
sinon.assert.calledWithMatch(requestStubs.OpenURL, fallback);});});
describe("#setStoreState", function () {
it("should update store state data", function () {
store.setStoreState({ pendingCreation: true });
expect(store.getStoreState().pendingCreation).eql(true);});
it("should trigger a `change` event", function (done) {
store.once("change", function () {
done();});
store.setStoreState({ pendingCreation: true });});
it("should trigger a `change:<prop>` event", function (done) {
store.once("change:pendingCreation", function () {
done();});
store.setStoreState({ pendingCreation: true });});});
describe("#getAllRooms", function () {
it("should fetch the room list from the Loop API", function () {
requestStubs["Rooms:GetAll"].returns(fakeRoomList);
store.getAllRooms(new sharedActions.GetAllRooms());
expect(store.getStoreState().error).to.be.a.undefined;
expect(store.getStoreState().rooms).to.have.length.of(3);});
it("should not fetch the room list if called a second time", function () {
requestStubs["Rooms:GetAll"].returns(fakeRoomList);
store.getAllRooms(new sharedActions.GetAllRooms());
store.getAllRooms(new sharedActions.GetAllRooms());
sinon.assert.calledOnce(requestStubs["Rooms:GetAll"]);});
it("should order the room list using ctime desc", function () {
requestStubs["Rooms:GetAll"].returns(fakeRoomList);
store.getAllRooms(new sharedActions.GetAllRooms());
var storeState = store.getStoreState();
expect(storeState.error).to.be.a.undefined;
expect(storeState.rooms[0].ctime).eql(1405518241);
expect(storeState.rooms[1].ctime).eql(1405517546);
expect(storeState.rooms[2].ctime).eql(1405517418);});
it("should report an error", function () {
var err = new Error("fake");
err.isError = true;
requestStubs["Rooms:GetAll"].returns(err);
dispatcher.dispatch(new sharedActions.GetAllRooms());
expect(store.getStoreState().error).eql(err);});
it("should register event listeners after the list is retrieved",
function () {
sandbox.stub(store, "startListeningToRoomEvents");
requestStubs["Rooms:GetAll"].returns(fakeRoomList);
store.getAllRooms();
sinon.assert.calledOnce(store.startListeningToRoomEvents);});
it("should set the pendingInitialRetrieval flag to true", function () {
expect(store.getStoreState().pendingInitialRetrieval).eql(true);
requestStubs["Rooms:GetAll"].returns([]);
store.getAllRooms();
expect(store.getStoreState().pendingInitialRetrieval).eql(false);});
it("should set pendingInitialRetrieval to false once the action is performed",
function () {
requestStubs["Rooms:GetAll"].returns(fakeRoomList);
store.getAllRooms();
expect(store.getStoreState().pendingInitialRetrieval).eql(false);});});
describe("ActiveRoomStore substore", function () {
var fakeStore, activeRoomStore;
beforeEach(function () {
activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, {
sdkDriver: {} });
fakeStore = new loop.store.RoomStore(dispatcher, {
constants: requestStubs.GetAllConstants(),
activeRoomStore: activeRoomStore });});
it("should subscribe to substore changes", function () {
var fakeServerData = { fake: true };
activeRoomStore.setStoreState({ serverData: fakeServerData });
expect(fakeStore.getStoreState().activeRoom.serverData).
eql(fakeServerData);});
it("should trigger a change event when the substore is updated",
function (done) {
fakeStore.once("change:activeRoom", function () {
done();});
activeRoomStore.setStoreState({ serverData: {} });});});});
describe("#openRoom", function () {
var store;
beforeEach(function () {
store = new loop.store.RoomStore(dispatcher, {
constants: requestStubs.GetAllConstants() });});
it("should open the room via mozLoop", function () {
store.openRoom(new sharedActions.OpenRoom({ roomToken: "42abc" }));
sinon.assert.calledOnce(requestStubs["Rooms:Open"]);
sinon.assert.calledWithExactly(requestStubs["Rooms:Open"], "42abc");});});
describe("#updateRoomContext", function () {
var store, getRoomStub, updateRoomStub, clock;
beforeEach(function () {
clock = sinon.useFakeTimers();
getRoomStub = sinon.stub().returns({
roomToken: "42abc",
decryptedContext: {
roomName: "sillier name" } });
updateRoomStub = sinon.stub();
LoopMochaUtils.stubLoopRequest({
"Rooms:Get": getRoomStub,
"Rooms:Update": updateRoomStub });
store = new loop.store.RoomStore(dispatcher, {
constants: requestStubs.GetAllConstants() });});
afterEach(function () {
clock.restore();});
it("should rename the room via mozLoop", function () {
dispatcher.dispatch(new sharedActions.UpdateRoomContext({
roomToken: "42abc",
newRoomName: "silly name" }));
sinon.assert.calledOnce(getRoomStub);
sinon.assert.calledOnce(updateRoomStub);
sinon.assert.calledWith(updateRoomStub, "42abc", {
roomName: "silly name" });});
it("should flag the the store as saving context", function () {
expect(store.getStoreState().savingContext).to.eql(false);
LoopMochaUtils.stubLoopRequest({
"Rooms:Update": function RoomsUpdate() {
expect(store.getStoreState().savingContext).to.eql(true);} });
dispatcher.dispatch(new sharedActions.UpdateRoomContext({
roomToken: "42abc",
newRoomName: "silly name" }));
expect(store.getStoreState().savingContext).to.eql(false);});
it("should store any update-encountered error", function () {
var err = new Error("fake");
err.isError = true;
LoopMochaUtils.stubLoopRequest({
"Rooms:Update": function RoomsUpdate() {
expect(store.getStoreState().savingContext).to.eql(true);
return err;} });
dispatcher.dispatch(new sharedActions.UpdateRoomContext({
roomToken: "42abc",
newRoomName: "silly name" }));
var state = store.getStoreState();
expect(state.error).eql(err);
expect(state.savingContext).to.eql(false);});
it("should ensure only submitting a non-empty room name", function () {
dispatcher.dispatch(new sharedActions.UpdateRoomContext({
roomToken: "42abc",
newRoomName: " \t \t " }));
clock.tick(1);
sinon.assert.notCalled(updateRoomStub);
expect(store.getStoreState().savingContext).to.eql(false);});
it("should save updated context information", function () {
dispatcher.dispatch(new sharedActions.UpdateRoomContext({
roomToken: "42abc",
// Room name doesn't need to change.
newRoomName: "sillier name",
newRoomDescription: "Hello, is it me you're looking for?",
newRoomThumbnail: "http://example.com/empty.gif",
newRoomURL: "http://example.com" }));
sinon.assert.calledOnce(updateRoomStub);
sinon.assert.calledWith(updateRoomStub, "42abc", {
urls: [{
description: "Hello, is it me you're looking for?",
location: "http://example.com",
thumbnail: "http://example.com/empty.gif" }] });});
it("should not save context information with an invalid URL", function () {
dispatcher.dispatch(new sharedActions.UpdateRoomContext({
roomToken: "42abc",
// Room name doesn't need to change.
newRoomName: "sillier name",
newRoomDescription: "Hello, is it me you're looking for?",
newRoomThumbnail: "http://example.com/empty.gif",
// NOTE: there are many variation we could test here, but the URL object
// constructor also fails on empty strings and is using the Gecko URL
// parser. Therefore we ought to rely on it working properly.
newRoomURL: "http/example.com" }));
sinon.assert.notCalled(updateRoomStub);});
it("should not save context information when no context information is provided",
function () {
dispatcher.dispatch(new sharedActions.UpdateRoomContext({
roomToken: "42abc",
// Room name doesn't need to change.
newRoomName: "sillier name",
newRoomDescription: "",
newRoomThumbnail: "",
newRoomURL: "" }));
clock.tick(1);
sinon.assert.notCalled(updateRoomStub);
expect(store.getStoreState().savingContext).to.eql(false);});});
describe("MAU telemetry events", function () {
var getLoopPrefStub, store;
beforeEach(function () {
getLoopPrefStub = function getLoopPrefStub(pref) {
if (pref === "facebook.shareUrl") {
return "https://shared.site/?u=%ROOM_URL%";}
return 0;};
LoopMochaUtils.stubLoopRequest({
GetLoopPref: getLoopPrefStub });
store = new loop.store.RoomStore(dispatcher, {
constants: requestStubs.GetAllConstants() });});
it("should log telemetry event when opening a room", function () {
store.openRoom(new sharedActions.OpenRoom({ roomToken: "42abc" }));
sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"],
"LOOP_ACTIVITY_COUNTER", store._constants.LOOP_MAU_TYPE.ROOM_OPEN);});
it("should log telemetry event when sharing a room (copy link)", function () {
store.copyRoomUrl(new sharedActions.CopyRoomUrl({
roomUrl: "http://invalid",
from: "conversation" }));
sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"],
"LOOP_ACTIVITY_COUNTER", store._constants.LOOP_MAU_TYPE.ROOM_SHARE);});
it("should log telemetry event when sharing a room (email)", function () {
store.emailRoomUrl(new sharedActions.EmailRoomUrl({
roomUrl: "http://invalid",
from: "conversation" }));
sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"],
"LOOP_ACTIVITY_COUNTER", store._constants.LOOP_MAU_TYPE.ROOM_SHARE);});
it("should log telemetry event when sharing a room (facebook)", function () {
store.facebookShareRoomUrl(new sharedActions.FacebookShareRoomUrl({
roomUrl: "http://invalid",
from: "conversation" }));
sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"],
"LOOP_ACTIVITY_COUNTER", store._constants.LOOP_MAU_TYPE.ROOM_SHARE);});
it("should log telemetry event when deleting a room", function () {
store.deleteRoom(new sharedActions.DeleteRoom({
roomToken: "42abc" }));
sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"],
"LOOP_ACTIVITY_COUNTER", store._constants.LOOP_MAU_TYPE.ROOM_DELETE);});});});

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

@ -1,615 +0,0 @@
"use strict"; /* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
describe("loop.roomViews", function () {
"use strict";
var expect = chai.expect;
var TestUtils = React.addons.TestUtils;
var sharedActions = loop.shared.actions;
var sharedViews = loop.shared.views;
var ROOM_STATES = loop.store.ROOM_STATES;
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
var sandbox,
dispatcher,
roomStore,
activeRoomStore,
remoteCursorStore,
view;
var clock, fakeWindow, requestStubs;
var favicon = "data:image/x-icon;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
beforeEach(function () {
sandbox = LoopMochaUtils.createSandbox();
LoopMochaUtils.stubLoopRequest(requestStubs = {
GetAudioBlob: sinon.spy(function () {
return new Blob([new ArrayBuffer(10)], { type: "audio/ogg" });}),
GetLoopPref: sinon.stub(),
GetSelectedTabMetadata: sinon.stub().returns({
favicon: favicon,
previews: [],
title: "" }),
OpenURL: sinon.stub(),
"Rooms:Get": sinon.stub().returns({
roomToken: "fakeToken",
roomName: "fakeName",
decryptedContext: {
roomName: "fakeName",
urls: [] } }),
"Rooms:Update": sinon.stub().returns(null),
TelemetryAddValue: sinon.stub(),
SetLoopPref: sandbox.stub(),
GetDoNotDisturb: function GetDoNotDisturb() {return false;} });
dispatcher = new loop.Dispatcher();
clock = sandbox.useFakeTimers();
fakeWindow = {
close: sinon.stub(),
document: {},
addEventListener: function addEventListener() {},
removeEventListener: function removeEventListener() {},
setTimeout: function setTimeout(callback) {callback();} };
loop.shared.mixins.setRootObject(fakeWindow);
// XXX These stubs should be hoisted in a common file
// Bug 1040968
sandbox.stub(document.mozL10n, "get", function (x) {
return x;});
activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, {
sdkDriver: {} });
roomStore = new loop.store.RoomStore(dispatcher, {
constants: {},
activeRoomStore: activeRoomStore });
remoteCursorStore = new loop.store.RemoteCursorStore(dispatcher, {
sdkDriver: {} });
var textChatStore = new loop.store.TextChatStore(dispatcher, {
sdkDriver: {} });
loop.store.StoreMixin.register({
textChatStore: textChatStore });
sandbox.stub(dispatcher, "dispatch");});
afterEach(function () {
sandbox.restore();
clock.restore();
loop.shared.mixins.setRootObject(window);
LoopMochaUtils.restore();
view = null;});
describe("ActiveRoomStoreMixin", function () {
it("should merge initial state", function () {
var TestView = React.createClass({ displayName: "TestView",
mixins: [loop.roomViews.ActiveRoomStoreMixin],
getInitialState: function getInitialState() {
return { foo: "bar" };},
render: function render() {return React.DOM.div();} });
var testView = TestUtils.renderIntoDocument(
React.createElement(TestView, {
roomStore: roomStore }));
var expectedState = _.extend({ foo: "bar", savingContext: false },
activeRoomStore.getInitialStoreState());
expect(testView.state).eql(expectedState);});
it("should listen to store changes", function () {
var TestView = React.createClass({ displayName: "TestView",
mixins: [loop.roomViews.ActiveRoomStoreMixin],
render: function render() {return React.DOM.div();} });
var testView = TestUtils.renderIntoDocument(
React.createElement(TestView, {
roomStore: roomStore }));
activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
expect(testView.state.roomState).eql(ROOM_STATES.READY);});});
describe("RoomFailureView", function () {
var fakeAudio;
function mountTestComponent(props) {
props = _.extend({
dispatcher: dispatcher,
failureReason: props && props.failureReason || FAILURE_DETAILS.UNKNOWN });
return TestUtils.renderIntoDocument(
React.createElement(loop.roomViews.RoomFailureView, props));}
beforeEach(function () {
fakeAudio = {
play: sinon.spy(),
pause: sinon.spy(),
removeAttribute: sinon.spy() };
sandbox.stub(window, "Audio").returns(fakeAudio);});
it("should render the FailureInfoView", function () {
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.roomViews.FailureInfoView);});
it("should dispatch a JoinRoom action when the rejoin call button is pressed", function () {
view = mountTestComponent();
var rejoinBtn = ReactDOM.findDOMNode(view).querySelector(".btn-rejoin");
React.addons.TestUtils.Simulate.click(rejoinBtn);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.JoinRoom());});
it("should render retry button when an ice failure is dispatched", function () {
view = mountTestComponent({
failureReason: FAILURE_DETAILS.ICE_FAILED });
var retryBtn = ReactDOM.findDOMNode(view).querySelector(".btn-rejoin");
expect(retryBtn.textContent).eql("retry_call_button");});
it("should play a failure sound, once", function () {
view = mountTestComponent();
sinon.assert.calledOnce(requestStubs.GetAudioBlob);
sinon.assert.calledWithExactly(requestStubs.GetAudioBlob, "failure");
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.equal(false);});});
describe("DesktopRoomConversationView", function () {
var onCallTerminatedStub;
beforeEach(function () {
LoopMochaUtils.stubLoopRequest({
GetLoopPref: function GetLoopPref(prefName) {
if (prefName === "contextInConversations.enabled") {
return true;}
return "test";} });
onCallTerminatedStub = sandbox.stub();
loop.config = {
tilesIframeUrl: null,
tilesSupportUrl: null };
activeRoomStore.setStoreState({ roomUrl: "http://invalid " });});
function mountTestComponent(props) {
props = _.extend({
chatWindowDetached: false,
cursorStore: remoteCursorStore,
dispatcher: dispatcher,
facebookEnabled: false,
roomStore: roomStore,
onCallTerminated: onCallTerminatedStub },
props);
return TestUtils.renderIntoDocument(
React.createElement(loop.roomViews.DesktopRoomConversationView, props));}
it("should NOT show the context menu on right click", function () {
var prevent = sandbox.stub();
view = mountTestComponent();
TestUtils.Simulate.contextMenu(
ReactDOM.findDOMNode(view),
{ preventDefault: prevent });
sinon.assert.calledOnce(prevent);});
it("should dispatch a setMute action when the audio mute button is pressed",
function () {
view = mountTestComponent();
view.setState({ audioMuted: true });
var muteBtn = ReactDOM.findDOMNode(view).querySelector(".btn-mute-audio");
React.addons.TestUtils.Simulate.click(muteBtn);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "setMute"));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("enabled", true));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("type", "audio"));});
it("should dispatch a setMute action when the video mute button is pressed",
function () {
view = mountTestComponent();
view.setState({ videoMuted: false });
var muteBtn = ReactDOM.findDOMNode(view).querySelector(".btn-mute-video");
React.addons.TestUtils.Simulate.click(muteBtn);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "setMute"));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("enabled", false));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("type", "video"));});
it("should set the mute button as mute off", function () {
view = mountTestComponent();
view.setState({ videoMuted: false });
var muteBtn = ReactDOM.findDOMNode(view).querySelector(".btn-mute-video");
expect(muteBtn.classList.contains("muted")).eql(false);});
it("should set the mute button as mute on", function () {
view = mountTestComponent();
view.setState({ audioMuted: true });
var muteBtn = ReactDOM.findDOMNode(view).querySelector(".btn-mute-audio");
expect(muteBtn.classList.contains("muted")).eql(true);});
it("should dispatch a `SetMute` action when the mute button is pressed", function () {
view = mountTestComponent();
var muteBtn = ReactDOM.findDOMNode(view).querySelector(".btn-mute-video");
React.addons.TestUtils.Simulate.click(muteBtn);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "setMute"));});
describe("#leaveRoom", function () {
it("should close the window when leaving a room that hasn't been used", function () {
view = mountTestComponent();
view.setState({ used: false });
view.leaveRoom();
sinon.assert.calledOnce(fakeWindow.close);});
it("should dispatch `LeaveRoom` action when leaving a room that has been used", function () {
view = mountTestComponent();
view.setState({ used: true });
view.leaveRoom();
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.LeaveRoom());});
it("should call onCallTerminated when leaving a room that has been used", function () {
view = mountTestComponent();
view.setState({ used: true });
view.leaveRoom();
sinon.assert.calledOnce(onCallTerminatedStub);});});
describe("#componentWillUpdate", function () {
it("should dispatch a `SetupStreamElements` action when the MEDIA_WAIT state is entered", function () {
activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
view = mountTestComponent();
sandbox.stub(view, "getDefaultPublisherConfig").returns({
fake: "config" });
activeRoomStore.setStoreState({ roomState: ROOM_STATES.MEDIA_WAIT });
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.SetupStreamElements({
publisherConfig: {
fake: "config" } }));});
it("should dispatch a `SetupStreamElements` action on MEDIA_WAIT state is re-entered", function () {
activeRoomStore.setStoreState({ roomState: ROOM_STATES.ENDED });
view = mountTestComponent();
sandbox.stub(view, "getDefaultPublisherConfig").returns({
fake: "config" });
activeRoomStore.setStoreState({ roomState: ROOM_STATES.MEDIA_WAIT });
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.SetupStreamElements({
publisherConfig: {
fake: "config" } }));});});
describe("#render", function () {
it("should set document.title to store.serverData.roomName", function () {
mountTestComponent();
activeRoomStore.setStoreState({ roomName: "fakeName" });
expect(fakeWindow.document.title).to.equal("fakeName");});
it("should render the RoomFailureView if the roomState is `FAILED`",
function () {
activeRoomStore.setStoreState({
failureReason: FAILURE_DETAILS.UNKNOWN,
roomState: ROOM_STATES.FAILED });
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.roomViews.RoomFailureView);});
it("should render the RoomFailureView if the roomState is `FULL`",
function () {
activeRoomStore.setStoreState({
failureReason: FAILURE_DETAILS.UNKNOWN,
roomState: ROOM_STATES.FULL });
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.roomViews.RoomFailureView);});
it("should render the DesktopRoomInvitationView if roomState is `JOINED`",
function () {
activeRoomStore.setStoreState({ roomState: ROOM_STATES.JOINED });
view = mountTestComponent();
expect(ReactDOM.findDOMNode(TestUtils.findRenderedComponentWithType(view,
loop.shared.desktopViews.SharePanelView))).to.not.eql(null);});
it("should render the DesktopRoomInvitationView if roomState is `JOINED` with just owner",
function () {
activeRoomStore.setStoreState({
participants: [{ owner: true }],
roomState: ROOM_STATES.JOINED });
view = mountTestComponent();
expect(ReactDOM.findDOMNode(TestUtils.findRenderedComponentWithType(view,
loop.shared.desktopViews.SharePanelView))).to.not.eql(null);});
it("should render the DesktopRoomConversationView if roomState is `JOINED` with remote participant",
function () {
activeRoomStore.setStoreState({
participants: [{}],
roomState: ROOM_STATES.JOINED });
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.roomViews.DesktopRoomConversationView);
expect(ReactDOM.findDOMNode(TestUtils.findRenderedComponentWithType(view,
loop.shared.desktopViews.SharePanelView))).to.eql(null);});
it("should render the DesktopRoomConversationView if roomState is `JOINED` with participants",
function () {
activeRoomStore.setStoreState({
participants: [{ owner: true }, {}],
roomState: ROOM_STATES.JOINED });
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.roomViews.DesktopRoomConversationView);
expect(ReactDOM.findDOMNode(TestUtils.findRenderedComponentWithType(view,
loop.shared.desktopViews.SharePanelView))).to.eql(null);});
it("should render the DesktopRoomConversationView if roomState is `HAS_PARTICIPANTS`",
function () {
activeRoomStore.setStoreState({ roomState: ROOM_STATES.HAS_PARTICIPANTS });
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.roomViews.DesktopRoomConversationView);
expect(ReactDOM.findDOMNode(TestUtils.findRenderedComponentWithType(view,
loop.shared.desktopViews.SharePanelView))).to.eql(null);});
it("should display loading spinner when localSrcMediaElement is null",
function () {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.MEDIA_WAIT,
localSrcMediaElement: null });
view = mountTestComponent();
expect(ReactDOM.findDOMNode(view).querySelector(".local .loading-stream")).
not.eql(null);});
it("should not display a loading spinner when local stream available",
function () {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.MEDIA_WAIT,
localSrcMediaElement: { fake: "video" } });
view = mountTestComponent();
expect(ReactDOM.findDOMNode(view).querySelector(".local .loading-stream")).
eql(null);});
it("should display loading spinner when remote stream is not available",
function () {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.HAS_PARTICIPANTS,
remoteSrcMediaElement: null });
view = mountTestComponent();
expect(ReactDOM.findDOMNode(view).querySelector(".remote .loading-stream")).
not.eql(null);});
it("should not display a loading spinner when remote stream available",
function () {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.HAS_PARTICIPANTS,
remoteSrcMediaElement: { fake: "video" } });
view = mountTestComponent();
expect(ReactDOM.findDOMNode(view).querySelector(".remote .loading-stream")).
eql(null);});
it("should display an avatar for remote video when the room has participants but video is not enabled",
function () {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.HAS_PARTICIPANTS,
mediaConnected: true,
remoteVideoEnabled: false });
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view, sharedViews.AvatarView);});
it("should display the remote video when there are participants and video is enabled", function () {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.HAS_PARTICIPANTS,
mediaConnected: true,
remoteVideoEnabled: true,
remoteSrcMediaElement: { fake: 1 } });
view = mountTestComponent();
expect(ReactDOM.findDOMNode(view).querySelector(".remote video")).not.eql(null);});
it("should display an avatar for local video when the stream is muted", function () {
activeRoomStore.setStoreState({
videoMuted: true });
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view, sharedViews.AvatarView);});
it("should display the local video when the stream is enabled", function () {
activeRoomStore.setStoreState({
localSrcMediaElement: { fake: 1 },
videoMuted: false });
view = mountTestComponent();
expect(ReactDOM.findDOMNode(view).querySelector(".local video")).not.eql(null);});
describe("Room name priority", function () {
beforeEach(function () {
activeRoomStore.setStoreState({
participants: [{}],
roomState: ROOM_STATES.JOINED,
roomName: "fakeName",
roomContextUrls: [
{
description: "Website title",
location: "https://fakeurl.com" }] });});
it("should use room name by default", function () {
view = mountTestComponent();
expect(fakeWindow.document.title).to.equal("fakeName");});
it("should use context title when there's no room title", function () {
activeRoomStore.setStoreState({ roomName: null });
view = mountTestComponent();
expect(fakeWindow.document.title).to.equal("Website title");});
it("should use website url when there's no room title nor website", function () {
activeRoomStore.setStoreState({
roomName: null,
roomContextUrls: [
{
location: "https://fakeurl.com" }] });
view = mountTestComponent();
expect(fakeWindow.document.title).to.equal("https://fakeurl.com");});});});});});

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

@ -1,100 +0,0 @@
"use strict"; /* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
describe("loop.slideshow", function () {
"use strict";
var expect = chai.expect;
var sharedUtils = loop.shared.utils;
var sandbox;
var data;
beforeEach(function () {
sandbox = LoopMochaUtils.createSandbox();
LoopMochaUtils.stubLoopRequest({
GetAllStrings: function GetAllStrings() {
return JSON.stringify({ textContent: "fakeText" });},
GetLocale: function GetLocale() {
return "en-US";},
GetPluralRule: function GetPluralRule() {
return 1;},
GetPluralForm: function GetPluralForm() {
return "fakeText";} });
data = [
{
id: "slide1",
imageClass: "slide1-image",
title: "fakeString",
text: "fakeString" },
{
id: "slide2",
imageClass: "slide2-image",
title: "fakeString",
text: "fakeString" },
{
id: "slide3",
imageClass: "slide3-image",
title: "fakeString",
text: "fakeString" },
{
id: "slide4",
imageClass: "slide4-image",
title: "fakeString",
text: "fakeString" }];
document.mozL10n.initialize({
getStrings: function getStrings() {
return JSON.stringify({ textContent: "fakeText" });},
locale: "en-US" });
sandbox.stub(document.mozL10n, "get").returns("fakeString");
sandbox.stub(sharedUtils, "getPlatform").returns("other");});
afterEach(function () {
sandbox.restore();
LoopMochaUtils.restore();});
describe("#init", function () {
beforeEach(function () {
sandbox.stub(ReactDOM, "render");
sandbox.stub(document.mozL10n, "initialize");
sandbox.stub(loop.SimpleSlideshow, "init");});
it("should initalize L10n", function () {
loop.slideshow.init();
sinon.assert.calledOnce(document.mozL10n.initialize);
sinon.assert.calledWith(document.mozL10n.initialize, sinon.match({ locale: "en-US" }));});
it("should call the slideshow init with the right arguments", function () {
loop.slideshow.init();
sinon.assert.calledOnce(loop.SimpleSlideshow.init);
sinon.assert.calledWith(loop.SimpleSlideshow.init, sinon.match("#main", data));});
it("should set the document attributes correctly", function () {
loop.slideshow.init();
expect(document.documentElement.getAttribute("lang")).to.eql("en-US");
expect(document.documentElement.getAttribute("dir")).to.eql("ltr");
expect(document.body.getAttribute("platform")).to.eql("other");});});});

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

@ -1,18 +0,0 @@
# need to get this dir in the path so that we make the import work
import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'shared', 'test'))
from frontend_tester import BaseTestFrontendUnits
class TestDesktopUnits(BaseTestFrontendUnits):
def setUp(self):
super(TestDesktopUnits, self).setUp()
# Set the server prefix to the top of the src directory for the mozilla-central
# repository.
self.set_server_prefix("../../../../")
def test_units(self):
self.check_page("chrome/content/panels/test/index.html")

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

@ -1,250 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
// This is a modified version of l10n.js that the pdf.js extension uses.
// It uses an explicitly passed object for the strings/locale functionality,
// and does not automatically translate on DOMContentLoaded, but requires
// initialize to be called. This improves testability and helps to avoid race
// conditions. It has also been updated to be closer to the gaia l10n.js api.
(function(window) {
var gL10nDetails;
var gLanguage = '';
// These are the available plural functions that give the appropriate index
// based on the plural rule number specified. The first element is the number
// of plural forms and the second is the function to figure out the index.
// NOTE: these rule functions are - unfortunately - a copy from the `gFunctions`
// array in intl/locale/PluralForm.jsm. An attempt should be made to keep
// this in sync with that source.
// These need to be copied over, because there's no way to copy Function
// objects over from chrome to content and the PluralForm.jsm module can
// only be loaded in a chrome context.
var kPluralFunctions = [
// 0: Chinese
[1, function(n) {
return 0
}],
// 1: English
[2, function(n) {
return n!=1?1:0
}],
// 2: French
[2, function(n) {
return n>1?1:0
}],
// 3: Latvian
[3, function(n) {
return n%10==1&&n%100!=11?1:n!=0?2:0
}],
// 4: Scottish Gaelic
[4, function(n) {
return n==1||n==11?0:n==2||n==12?1:n>0&&n<20?2:3
}],
// 5: Romanian
[3, function(n) {
return n==1?0:n==0||n%100>0&&n%100<20?1:2
}],
// 6: Lithuanian
[3, function(n) {
return n%10==1&&n%100!=11?0:n%10>=2&&(n%100<10||n%100>=20)?2:1
}],
// 7: Russian
[3, function(n) {
return n%10==1&&n%100!=11?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2
}],
// 8: Slovak
[3, function(n) {
return n==1?0:n>=2&&n<=4?1:2
}],
// 9: Polish
[3, function(n) {
return n==1?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2
}],
// 10: Slovenian
[4, function(n) {
return n%100==1?0:n%100==2?1:n%100==3||n%100==4?2:3
}],
// 11: Irish Gaeilge
[5, function(n) {
return n==1?0:n==2?1:n>=3&&n<=6?2:n>=7&&n<=10?3:4
}],
// 12: Arabic
[6, function(n) {
return n==0?5:n==1?0:n==2?1:n%100>=3&&n%100<=10?2:n%100>=11&&n%100<=99?3:4
}],
// 13: Maltese
[4, function(n) {
return n==1?0:n==0||n%100>0&&n%100<=10?1:n%100>10&&n%100<20?2:3
}],
// 14: Macedonian
[3, function(n) {
return n%10==1?0:n%10==2?1:2
}],
// 15: Icelandic
[2, function(n) {
return n%10==1&&n%100!=11?0:1
}],
// 16: Breton
[5, function(n) {
return n%10==1&&n%100!=11&&n%100!=71&&n%100!=91?0:n%10==2&&n%100!=12&&
n%100!=72&&n%100!=92?1:(n%10==3||n%10==4||n%10==9)&&n%100!=13&&n%100!=14&&
n%100!=19&&n%100!=73&&n%100!=74&&n%100!=79&&n%100!=93&&n%100!=94&&n%100!=99?2:n%1000000==0&&n!=0?3:4
}]
];
var gPluralFunc = null;
// fetch an l10n objects
function getL10nData(key, num) {
var response = gL10nDetails.getStrings(key);
var data = JSON.parse(response);
if (!data)
console.warn('[l10n] #' + key + ' missing for [' + gLanguage + ']');
if (num !== undefined) {
for (var prop in data) {
data[prop] = gL10nDetails.getPluralForm(num, data[prop]);
}
}
return data;
}
function fallbackGetPluralForm(num, str) {
// Figure out which index to use for the semi-colon separated words.
var index = gPluralFunc(num ? Number(num) : 0);
var words = str ? str.split(/;/) : [""];
// Explicitly check bounds to avoid strict warnings.
var ret = index < words.length ? words[index] : undefined;
// Check for array out of bounds or empty strings.
if ((ret == undefined) || (ret == "")) {
// Display a message in the error console
console.error("Index #" + index + " of '" + str + "' for value " + num +
" is invalid -- plural rule #" + ret);
// Default to the first entry (which might be empty, but not undefined).
ret = words[0];
}
return ret;
}
// replace {{arguments}} with their values
function substArguments(text, args) {
if (!args)
return text;
return text.replace(/\{\{\s*(\w+)\s*\}\}/g, function(all, name) {
return name in args ? args[name] : '{{' + name + '}}';
});
}
// translate a string
function translateString(key, args, fallback) {
var num;
if (args && ("num" in args)) {
num = args.num;
}
var data = getL10nData(key, num);
if (!data && fallback)
data = {textContent: fallback};
if (!data)
return '{{' + key + '}}';
return substArguments(data.textContent, args);
}
// translate an HTML element
function translateElement(element) {
if (!element || !element.dataset)
return;
// get the related l10n object
var key = element.dataset.l10nId;
var data = getL10nData(key);
if (!data)
return;
// get arguments (if any)
// TODO: more flexible parser?
var args;
if (element.dataset.l10nArgs) try {
args = JSON.parse(element.dataset.l10nArgs);
} catch (e) {
console.warn('[l10n] could not parse arguments for #' + key + '');
}
// translate element
// TODO: security check?
for (var k in data)
element[k] = substArguments(data[k], args);
}
// translate an HTML subtree
function translateFragment(element) {
element = element || document.querySelector('html');
// check all translatable children (= w/ a `data-l10n-id' attribute)
var children = element.querySelectorAll('*[data-l10n-id]');
var elementCount = children.length;
for (var i = 0; i < elementCount; i++)
translateElement(children[i]);
// translate element itself if necessary
if (element.dataset.l10nId)
translateElement(element);
}
// Public API
document.mozL10n = {
/**
* Called to do the initial translation, this should be called
* when DOMContentLoaded is fired, or the equivalent time.
*
* @param {Object} l10nDetails An object implementing the locale attribute
* and getStrings(key) function.
*/
initialize: function(l10nDetails) {
gL10nDetails = l10nDetails;
gLanguage = gL10nDetails.locale;
// Fallback to a working - synchronous - implementation of retrieving the
// plural form of a string.
if (!gL10nDetails.getPluralForm && ("pluralRule" in gL10nDetails)) {
var ruleNum = gL10nDetails.pluralRule;
// Default to "all plural" if the value is out of bounds or invalid
if (ruleNum < 0 || ruleNum >= kPluralFunctions.length || isNaN(ruleNum)) {
console.error(["Invalid rule number: ", ruleNum, " -- defaulting to 0"]);
ruleNum = 0;
}
gPluralFunc = kPluralFunctions[ruleNum][1];
gL10nDetails.getPluralForm = fallbackGetPluralForm;
}
translateFragment();
},
// get a localized string
get: translateString,
// get the document language
language: {
set code(lang) {
throw new Error("unsupported");
},
get code() {
return gLanguage;
},
get direction() {
// http://www.w3.org/International/questions/qa-scripts
// Arabic, Hebrew, Farsi, Pashto, Urdu
var rtlList = ['ar', 'he', 'fa', 'ps', 'ur'];
return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr';
}
},
// translate an element or document fragment
translate: translateFragment
};
})(this);

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

@ -1,109 +0,0 @@
/* This is derived from PIOTR F's code,
currently available at https://github.com/piotrf/simple-react-slideshow
Simple React Slideshow Example
Original Author: PIOTR F.
License: MIT
Copyright (c) 2015 Piotr
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
html {
font-size: 10px;
font-family: menu;
color: #fff;
}
body {
background: none;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
line-height: 2.4rem; /* or 1.3rem original*/
}
.slides {
display: block;
width: 100%;
height: 390px;
overflow: hidden;
white-space: nowrap;
}
.slide {
display: none;
height: 100%;
width: 100%;
}
.slide.slide--active {
display: block;
}
.control-panel {
height: 60px;
background: #fff;
width: 100%;
}
.toggle {
color: white;
display: block;
padding: 0px;
position: absolute;
bottom: 20px;
background-color: transparent;
background-image: url(../../shared/img/arrow-01.svg);
background-repeat: no-repeat;
background-size: 20px 20px;
border: none;
/*padding: 0;*/
height: 20px;
width: 20px;
}
.toggle-prev {
left: 20px;
transform: scaleX(-1);
}
.toggle-next {
right: 20px;
}
.button-close {
display: block;
position: absolute;
background-color: transparent;
background-image: url(../../shared/img/close-02.svg);
background-repeat: no-repeat;
background-size: 20px 20px;
border: none;
padding: 0;
height: 20px;
width: 20px;
top: 20px;
right: 20px;
}

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

@ -1,195 +0,0 @@
"use strict"; // This is derived from PIOTR F's code,
// currently available at https://github.com/piotrf/simple-react-slideshow
// Simple React Slideshow Example
//
// Original Author: PIOTR F.
// License: MIT
//
// Copyright (c) 2015 Piotr
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
var loop = loop || {};
loop.SimpleSlideshow = function () {
"use strict";
// App state
var state = {
currentSlide: 0,
data: [] };
// State transitions
var actions = {
toggleNext: function toggleNext() {
var current = state.currentSlide;
var next = current + 1;
if (next < state.data.length) {
state.currentSlide = next;}
render();},
togglePrev: function togglePrev() {
var current = state.currentSlide;
var prev = current - 1;
if (prev >= 0) {
state.currentSlide = prev;}
render();},
toggleSlide: function toggleSlide(id) {
var index = state.data.map(function (el) {
return (
el.id);});
var currentIndex = index.indexOf(id);
state.currentSlide = currentIndex;
render();} };
var Slideshow = React.createClass({ displayName: "Slideshow",
propTypes: {
data: React.PropTypes.array.isRequired },
render: function render() {
return (
React.createElement("div", { className: "slideshow" },
React.createElement(Slides, { data: this.props.data }),
React.createElement("div", { className: "control-panel" },
React.createElement(Controls, null))));} });
var Slides = React.createClass({ displayName: "Slides",
propTypes: {
data: React.PropTypes.array.isRequired },
render: function render() {
var slidesNodes = this.props.data.map(function (slideNode, index) {
var isActive = state.currentSlide === index;
return (
React.createElement(Slide, { active: isActive,
imageClass: slideNode.imageClass,
indexClass: slideNode.id,
text: slideNode.text,
title: slideNode.title }));});
return (
React.createElement("div", { className: "slides" },
slidesNodes));} });
var Slide = React.createClass({ displayName: "Slide",
propTypes: {
active: React.PropTypes.bool.isRequired,
imageClass: React.PropTypes.string.isRequired,
indexClass: React.PropTypes.string.isRequired,
text: React.PropTypes.string.isRequired,
title: React.PropTypes.string.isRequired },
render: function render() {
var classes = classNames({
"slide": true,
"slide--active": this.props.active });
return (
React.createElement("div", { className: classes },
React.createElement("div", { className: this.props.indexClass },
React.createElement("div", { className: "slide-layout" },
React.createElement("img", { className: this.props.imageClass }),
React.createElement("h2", null, this.props.title),
React.createElement("div", { className: "slide-text" }, this.props.text)))));} });
var Controls = React.createClass({ displayName: "Controls",
togglePrev: function togglePrev() {
actions.togglePrev();},
toggleNext: function toggleNext() {
actions.toggleNext();},
render: function render() {
var showPrev, showNext;
var current = state.currentSlide;
var last = state.data.length;
if (current > 0) {
showPrev = React.createElement("div", { className: "toggle toggle-prev", onClick: this.togglePrev });}
if (current < last - 1) {
showNext = React.createElement("div", { className: "toggle toggle-next", onClick: this.toggleNext });}
return (
React.createElement("div", { className: "controls" },
showPrev,
showNext));} });
var EmptyMessage = React.createClass({ displayName: "EmptyMessage",
render: function render() {
return (
React.createElement("div", { className: "empty-message" }, "No Data"));} });
function render(renderTo) {
var hasData = state.data.length > 0;
var component;
if (hasData) {
component = React.createElement(Slideshow, { data: state.data });} else
{
component = React.createElement(EmptyMessage, null);}
ReactDOM.render(
component,
document.querySelector(renderTo ? renderTo : "#main"));}
function init(renderTo, data) {
state.data = data;
render(renderTo);}
return {
init: init };}();

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

@ -1,49 +0,0 @@
pref("loop.enabled", true);
pref("loop.remote.autostart", true);
#ifdef LOOP_DEV_XPI
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");
pref("loop.linkClicker.url", "https://hello.firefox.com/");
#endif
pref("loop.gettingStarted.latestFTUVersion", 1);
pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
pref("loop.gettingStarted.resumeOnFirstJoin", false);
pref("loop.legal.ToS_url", "https://www.mozilla.org/about/legal/terms/firefox-hello/");
pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/firefox-hello/");
pref("loop.do_not_disturb", false);
pref("loop.retry_delay.start", 60000);
pref("loop.retry_delay.limit", 300000);
pref("loop.ping.interval", 1800000);
pref("loop.ping.timeout", 10000);
pref("loop.copy.showLimit", 3);
pref("loop.copy.shown", false);
pref("loop.copy.throttler", "copy.loop.services.mozilla.com");
pref("loop.copy.ticket", -1);
pref("loop.debug.loglevel", "Error");
pref("loop.debug.dispatcher", false);
pref("loop.debug.sdk", false);
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%");
pref("loop.feedback.manualFormURL", "https://www.mozilla.org/firefox/hello/feedbacksurvey/");
pref("loop.logDomains", false);
pref("loop.mau.openPanel", 0);
pref("loop.mau.openConversation", 0);
pref("loop.mau.roomOpen", 0);
pref("loop.mau.roomShare", 0);
pref("loop.mau.roomDelete", 0);
#ifdef DEBUG
pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src * data:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*; media-src blob:");
#else
pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src * data:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net; media-src blob:");
#endif
pref("loop.fxa_oauth.tokendata", "");
pref("loop.fxa_oauth.profile", "");
pref("loop.support_url", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/cobrowsing");
pref("loop.facebook.enabled", true);
pref("loop.facebook.appId", "1519239075036718");
pref("loop.facebook.shareUrl", "https://www.facebook.com/dialog/send?app_id=%APP_ID%&link=%ROOM_URL%&redirect_uri=%REDIRECT_URI%");
pref("loop.facebook.fallbackUrl", "https://hello.firefox.com/");
pref("loop.conversationPopOut.enabled", false);

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

@ -1,652 +0,0 @@
/* 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/. */
/* Generic rules */
/**
* "Fixes" the Box Model.
* @see http://www.paulirish.com/2012/box-sizing-border-box-ftw/
*/
*, *:before, *:after {
box-sizing: border-box;
}
body {
font: message-box;
font-size: 12px;
background: #fbfbfb;
overflow: hidden;
}
img {
border: none;
}
h1, h2, h3 {
color: #666;
}
/* choose a sane default for paragraphs, since reset.css' 0px is not what we want */
p {
margin: 1em 0;
}
/* Helpers */
/**
* Clearfix impl. for modern browsers
* 1. The space content is one way to avoid an Opera bug when the
* contenteditable attribute is included anywhere else in the document.
* Otherwise it causes space to appear at the top and bottom of elements
* that are clearfixed.
* 2. The use of `table` rather than `block` is only necessary if using
* `:before` to contain the top-margins of child elements.
*/
.cf:before,
.cf:after {
display: table; /* 1 */
content: " "; /* 2 */
}
.cf:after {
clear: both;
}
.hide {
/**
* Force the display: none as it can conflict with other display.
* You usually want to avoid !important statements as much as
* possible. In this case, it makes sense as it's unlikely we want a
* class to undo the hide feature.
*/
display: none !important;
}
.tc {
text-align: center;
}
.full-width {
width: 100%;
}
/* Buttons */
.button-group {
display: flex;
flex-direction: row;
width: 100%;
padding-top: 6px;
}
.button-group > .button {
flex: 1;
margin: 0 5px;
min-height: 3rem;
font-size: 1.2rem;
line-height: 1rem;
font-weight: 300;
border-radius: 4px;
}
.button-group > .button:first-child {
margin-inline-start: 0;
}
.button-group > .button:last-child {
margin-inline-end: 0;
}
.button {
padding: 2px 5px;
background-color: #fbfbfb;
color: #333;
border-radius: 2px;
min-height: 26px;
font-size: 1.2rem;
line-height: 1.2rem;
border: none;
}
.button:hover {
background-color: #ebebeb;
}
.button:active {
background-color: #ccc;
color: #fff;
}
.button.button-accept {
background-color: #00a9dc;
color: #fff;
}
.button.button-accept:hover,
.button.button-accept:hover:active {
background-color: #5cccee;
color: #fff;
}
.button.button-cancel {
background-color: #ebebeb;
border: 0;
color: #000;
}
.button.button-cancel:hover,
.button.button-cancel:hover:active {
background-color: #dcd6d6;
color: #000;
}
.button.button-cancel:disabled {
background-color: #ebebeb;
color: #c3c3c3;
}
.button.button-accept:active {
background-color: #3aa689;
border-color: #3aa689;
color: #fff;
}
/* A reset for all button-appearing elements, with the lowest-common
* denominator of the needed rules. Intended to be used as a base class
* together with .btn-*
*/
.btn {
display: inline-block;
margin: 0;
padding: 0;
border: none;
background: #a5a;
color: #fff;
text-align: center;
text-decoration: none;
font-size: .9em;
cursor: pointer;
}
.btn.btn-constrained {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.btn-info {
background-color: #00a9dc;
border: 1px solid #00a9dc;
color: #fff;
}
.btn-info:hover {
background-color: #5cccee;
border: 1px solid #5cccee;
}
.btn-info:active {
background-color: #5cccee;
border: 1px solid #5cccee;
}
.btn-accept,
.btn-success {
background-color: #56b397;
border: 1px solid #56b397;
}
.btn-accept:hover,
.btn-success:hover {
background-color: #50e3c2;
border: 1px solid #50e3c2;
}
.btn-accept:active,
.btn-success:active {
background-color: #3aa689;
border: 1px solid #3aa689;
}
.btn-warning {
background-color: #f0ad4e;
}
.btn-error,
.btn-hangup {
background-color: #d74345;
border: 1px solid #d74345;
}
.btn-error:hover,
.btn-hangup:hover {
background-color: #c53436;
border-color: #c53436;
}
.btn-error:active,
.btn-hangup:active {
background-color: #ae2325;
border-color: #ae2325;
}
.disabled, button[disabled] {
cursor: not-allowed;
pointer-events: none;
opacity: 0.65;
}
.btn-group {
display: flex;
align-content: space-between;
justify-content: center;
}
/* Alerts/Notifications */
.notificationContainer {
border-bottom: 2px solid #E9E9E9;
}
.messages > .notificationContainer > .alert {
text-align: center;
}
.notificationContainer > .detailsBar,
.alert {
background: #eee;
padding: .4em 1em;
}
.alert p.message {
padding: 0;
margin: 0;
}
.alert-error {
background: repeating-linear-gradient(-45deg, #D74345, #D74345 10px, #D94B4D 10px, #D94B4D 20px) repeat scroll 0% 0% transparent;
color: #fff;
}
.alert-warning {
background: #fcf8e3;
border: 1px solid #fbeed5;
}
.alert-success {
background: #5BC0A4;
border: 1px solid #5BC0A4;
color: #fff;
}
.notificationContainer > .details-error {
background: #fbebeb;
color: #d74345
}
.notificationContainer > .details-error > .detailsButton {
float: right;
margin-inline-start: 1em; /* Match .detailsBar padding */
}
.alert .close {
position: relative;
top: -.1rem;
right: -1rem;
}
/* Misc */
.call-url,
.standalone-call-btn-text {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.close {
float: right;
font-size: 1rem;
font-weight: bold;
line-height: 1rem;
color: #000;
opacity: .4;
background: none;
border: none;
cursor: pointer;
}
.close:hover {
opacity: .6;
}
.close:before {
/* \2716 is unicode representation of the close button icon */
content: '\2716';
}
/* Transitions */
.fade-out {
transition: opacity 0.5s ease-in;
opacity: 0;
}
.icon,
.icon-small {
background-size: 20px;
background-repeat: no-repeat;
vertical-align: top;
background-position: 80% center;
}
.pseudo-icon:before {
content: "";
display: inline-block;
background-repeat: no-repeat;
width: 14px;
height: 14px;
vertical-align: top;
margin: 0 .7rem;
}
.icon-small {
background-size: 10px;
}
/*
* Platform specific styles
* The UI should match the user OS
* Specific font sizes for text paragraphs to match
* the interface on that platform.
*/
.inverse {
color: #fff;
}
.light {
color: rgba(51, 51, 51, .5);
}
/* Web panel */
.info-panel {
border-radius: 4px;
background: #fff;
padding: 20px 0;
border: 1px solid #e7e7e7;
box-shadow: 0 2px 0 rgba(0, 0, 0, .03);
margin: 2rem 0;
}
.info-panel h1 {
font-size: 1.2em;
font-weight: 700;
padding: 20px 0;
text-align: center;
}
.info-panel h4 {
color: #aaa;
text-align: center;
font-weight: 500;
font-size: 1.2em;
margin: 0;
}
/* Logos */
.firefox-logo {
margin: 0 auto; /* horizontal block centering */
width: 100px;
height: 100px;
background: transparent url(../img/firefox-logo.png) no-repeat center center;
background-size: contain;
}
/* Dropdown menu */
.dropdown {
position: relative;
}
.dropdown-menu {
position: absolute;
bottom: 0;
left: 0;
background-color: #fbfbfb;
box-shadow: 0 1px 3px rgba(0,0,0,.3);
list-style: none;
border-radius: 2px;
}
html[dir="rtl"] .dropdown-menu {
left: auto;
right: 0;
}
.dropdown-menu-item {
width: 100%;
text-align: start;
padding: .3rem .8rem;
cursor: pointer;
border: 1px solid transparent;
font-size: 1.2rem;
line-height: 22px;
white-space: nowrap;
color: #4a4a4a;
}
.dropdown-menu-item:first-child {
padding-top: .5rem;
}
.dropdown-menu-item:last-child {
padding-bottom: .5rem;
}
.dropdown-menu-item:first-child:hover {
border-top-right-radius: 2px;
border-top-left-radius: 2px;
}
.dropdown-menu-item:last-child {
border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px;
}
.dropdown-menu-item:hover {
background-color: #dbf7ff;
}
/* Custom checkbox */
.checkbox-wrapper {
-moz-user-select: none;
user-select: none;
}
.checkbox {
float: left;
width: 2rem;
height: 2rem;
margin-inline-end: .5em;
margin-top: .1em;
border: 1px solid #999;
border-radius: 3px;
cursor: pointer;
background-color: transparent;
background-position: center center;
background-repeat: no-repeat;
background-size: 1em 1em;
}
html[dir="rtl"] .checkbox {
float: right;
}
.checkbox.checked {
background-image: url("../img/check.svg#check");
}
.checkbox.checked:hover,
.checkbox.checked:hover:active {
background-image: url("../img/check.svg#check-active");
}
.checkbox.disabled {
border: 1px solid #909090;
}
.checkbox.checked.disabled {
background-image: url("../img/check.svg#check-disabled");
}
.checkbox-label.ellipsis {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-align: left;
}
html[dir="rtl"] .checkbox-label.ellipsis {
text-align: right;
}
/* ContextUrlView classes */
.context-content {
color: black;
text-align: left;
}
html[dir="rtl"] .context-content {
text-align: right;
}
.context-content > p {
font-weight: bold;
margin-bottom: .8em;
margin-top: 0;
}
.context-wrapper {
/* Use the flex row mode to position the elements next to each other. */
display: flex;
flex-flow: row nowrap;
/* No underline for the text in the context view. */
text-decoration: none;
}
.context-content > .context-wrapper {
border: 2px solid #ebebeb;
border-radius: 4px;
background: #fafafa;
padding: 1.1rem .8rem;
font-size: 1.3rem;
line-height: 1.4rem;
}
.context-wrapper > .context-preview {
float: left;
/* 16px is standard height/width for a favicon */
width: 16px;
max-height: 16px;
margin-right: .8em;
flex: 0 0 auto;
}
html[dir="rtl"] .context-wrapper > .context-preview {
float: left;
margin-left: .8em;
margin-right: 0;
}
.context-wrapper > .context-info {
flex: 0 1 auto;
display: block;
color: black;
/* 16px for the preview, plus its .8em margin */
max-width: calc(100% - 16px - .8em);
word-wrap: break-word;
}
.context-wrapper > .context-info > .context-url {
display: block;
color: #00a9dc;
font-weight: 700;
clear: both;
}
/* Only underline the url, not the associated text */
.clicks-allowed.context-wrapper:hover > .context-info > .context-url {
text-decoration: underline;
}
.remote-video-box {
height: 100%;
width: 100%;
}
.remote-video-box > .remote-cursor-container {
/* Svg cursor has a white outline so we need to offset it to ensure that the
* cursor points at a more precise position with the negative margin. */
margin: -2px;
position: absolute;
z-index: 65534;
}
.remote-video-box > .remote-cursor-container > .remote-cursor {
background: url('../img/cursor.svg#blue') no-repeat;
height: 20px;
width: 15px;
}
.remote-video-box > .remote-cursor-container:after,
.remote-video-box > .remote-cursor-container:before {
background-color: rgba(231, 149, 68, 0.5);
border-radius: 50%;
content: "";
height: 50px;
left: 50%;
margin: -35px 0 0 -35px;
opacity: 0;
pointer-events: none;
position: absolute;
top: 50%;
width: 50px;
z-index: -1;
}
@keyframes double-pulse-1 {
0% {
opacity: 1;
transform: scale3d(0.1, 0.1, 1);
}
100% {
opacity: 0;
transform: scale3d(1.1, 1.1, 1);
}
}
@keyframes double-pulse-2 {
0% {
opacity: 1;
transform: scale3d(0.5, 0.5, 1);
}
50%, 100% {
opacity: 0;
transform: scale3d(1.2, 1.2, 1);
}
}
.remote-video-box > .remote-cursor-clicked:after {
animation: double-pulse-1 800ms ease-out forwards;
}
.remote-video-box > .remote-cursor-clicked:before {
animation: double-pulse-2 800ms ease-out forwards;
}

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

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

@ -1,53 +0,0 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
:focus {
/* from https://people.mozilla.org/~dhenein/labs/loop-link-spec/app.min.css */
/* remove blue outline in chrome */
outline: 0;
}

Двоичный файл не отображается.

До

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

Двоичный файл не отображается.

До

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

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

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g fill="#1BAADA"><path opacity=".15" d="M4.9 11.8c.2-.4.7-.5 1.1-.3.4.2.5.7.3 1.1l-1.2 2.1c-.2.3-.7.5-1.1.2-.4-.2-.5-.7-.3-1.1l1.2-2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite"/></path><path opacity=".2" d="M3.4 9.7c.4-.2.9-.1 1.1.3.2.4.1.9-.3 1.1l-2.1 1.2c-.4.2-.9.1-1.1-.3-.2-.4-.1-.9.3-1.1l2.1-1.2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.08s"/></path><path opacity=".25" d="M3.2 7.2c.4 0 .8.4.8.8s-.4.8-.8.8H.8C.4 8.8 0 8.4 0 8s.4-.8.8-.8h2.4z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.16s"/></path><path opacity=".35" d="M1.4 5.1C1 4.9.8 4.4 1.1 4c.2-.4.7-.5 1.1-.3l2.1 1.2c.3.2.5.7.2 1.1-.2.4-.7.5-1.1.3l-2-1.2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.24s"/></path><path opacity=".45" d="M3.7 2.2c-.2-.4-.1-.9.3-1.1.4-.2.9-.1 1.1.3l1.2 2.1c.2.3.1.8-.3 1-.4.3-.9.1-1.1-.3l-1.2-2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.32s"/></path><path opacity=".5" d="M8.8 3.2c0 .4-.4.8-.8.8s-.8-.4-.8-.8V.8c0-.4.4-.8.8-.8s.8.4.8.8v2.4z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.40s"/></path><path opacity=".55" d="M10.9 1.4c.2-.4.7-.6 1.1-.3.4.2.5.7.3 1.1l-1.2 2.1c-.2.4-.7.5-1.1.3-.4-.3-.5-.8-.3-1.2l1.2-2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.48s"/></path><path opacity=".6" d="M13.8 3.7c.4-.2.9-.1 1.1.3.2.4.1.9-.3 1.1l-2.1 1.2c-.4.2-.9.1-1.1-.3-.2-.4-.1-.9.3-1.1l2.1-1.2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.56s"/></path><path opacity=".65" d="M15.2 7.2c.4 0 .8.4.8.8s-.4.8-.8.8h-2.4c-.4 0-.8-.4-.8-.8s.4-.8.8-.8h2.4z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.64s"/></path><path opacity=".7" d="M11.8 11.1c-.4-.2-.5-.7-.3-1.1.2-.4.7-.5 1.1-.3l2.1 1.2c.4.2.5.7.3 1.1-.2.4-.7.5-1.1.3l-2.1-1.2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.70s"/></path><path opacity=".8" d="M9.7 12.6c-.2-.4-.1-.9.3-1.1.4-.2.9-.1 1.1.3l1.2 2.1c.2.4.1.9-.3 1.1-.4.2-.9.1-1.1-.3l-1.2-2.1z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.78s"/></path><path d="M8.8 15.2c0 .4-.4.8-.8.8s-.8-.4-.8-.8v-2.4c0-.4.4-.8.8-.8s.8.4.8.8v2.4z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.86s"/></path></g><path fill-rule="evenodd" clip-rule="evenodd" fill="#1BAADA" d="M8 8.7c-1.5 0-2.6-.5-2.6-.5S5.9 10 8 10s2.6-1.9 2.6-1.9-1.1.6-2.6.6zM9.3 6c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.3-.7-.7.3-.7.7-.7zM6.7 6c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.3-.7-.7.3-.7.7-.7z"/></svg>

До

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

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

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><path fill="#55565A" d="M47,25c0-1-0.4-2-1.1-2.7L26.4,2.9c-0.7-0.7-1.7-1.1-2.7-1.1c-1,0-2,0.4-2.7,1.1l-2.2,2.2 c-0.7,0.7-1.1,1.7-1.1,2.7s0.4,2,1.1,2.7l8.8,8.8h-21c-2.2,0-3.5,1.8-3.5,3.8v3.8c0,2,1.3,3.8,3.5,3.8h21l-8.8,8.8 c-0.7,0.7-1.1,1.7-1.1,2.7c0,1,0.4,2,1.1,2.7l2.2,2.2c0.7,0.7,1.7,1.1,2.7,1.1c1,0,2-0.4,2.7-1.1l19.5-19.5C46.6,27,47,26,47,25z"/></svg>

До

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

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

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="129 5 252 253" width="21pc" height="253pt">
<rect x="129" y="5" width="252" height="252" fill="#4ba6e7"/>
<circle cx="255" cy="106" r="54" fill="#badbeb"/>
<path fill="#badbeb" d="M 159.01782 257 L 350.98218 257 C 351.46667 236.66908 342.1 216.2136 322.88218 200.69928 C 285.39187 170.43352 224.60813 170.43352 187.11782 200.69928 C 167.9 216.2136 158.53333 236.66909 159.01782 257 Z"/>
</svg>

До

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

Двоичный файл не отображается.

До

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

Двоичный файл не отображается.

До

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

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

@ -1 +0,0 @@
<svg width="31" height="24" viewBox="0 0 31 24" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path d="M19.002 11.5c0-.273-.226-.5-.5-.5-.273 0-.5.226-.5.5v1c0 1.93-1.57 3.5-3.5 3.5s-3.5-1.57-3.5-3.5v-1c0-.273-.226-.5-.5-.5-.273 0-.5.226-.5.5v1c0 2.312 1.75 4.219 4 4.468v1.031h-2c-.273 0-.5.226-.5.5 0 .273.226.5.5.5h5c.273 0 .5-.226.5-.5 0-.273-.226-.5-.5-.5h-2v-1.031c2.25-.25 4-2.156 4-4.468v-1zm-2-2.999c0-1.375-1.125-2.5-2.5-2.5s-2.5 1.125-2.5 2.5v4c0 1.375 1.125 2.5 2.5 2.5s2.5-1.125 2.5-2.5v-4z" fill="#fff"/></g></svg>

До

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

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

@ -1 +0,0 @@
<svg width="31" height="24" viewBox="0 0 31 24" xmlns="http://www.w3.org/2000/svg"><path d="M9.07 17.683l.644.644c.056.042.112.07.182.07.071 0 .126-.027.182-.07l1.975-1.989c.574.35 1.191.56 1.849.63v1.037h-2.003c-.14 0-.252.042-.35.154-.098.098-.154.21-.154.336 0 .14.056.252.154.35.098.098.21.154.35.154h5.001c.14 0 .252-.056.35-.154.098-.098.154-.21.154-.35 0-.126-.056-.238-.154-.336-.098-.112-.21-.154-.35-.154h-2.003v-1.037c1.135-.126 2.088-.602 2.858-1.457.771-.855 1.149-1.864 1.149-3.012v-.995c0-.14-.056-.252-.154-.35-.098-.098-.21-.154-.336-.154-.14 0-.252.056-.35.154-.098.098-.154.21-.154.35v.995c0 .967-.35 1.793-1.037 2.48-.687.687-1.513 1.022-2.465 1.022-.574 0-1.107-.126-1.611-.392l.757-.756c.28.098.56.154.854.154.687 0 1.274-.252 1.765-.743.49-.491.728-1.078.728-1.765v-.995l2.83-2.83c.042-.042.07-.112.07-.182 0-.056-.028-.126-.07-.168l-.644-.644c-.056-.056-.112-.084-.182-.084-.07 0-.126.028-.182.084l-9.652 9.637c-.042.056-.07.112-.07.182 0 .07.028.126.07.182zm1.149-3.502l.784-.798c-.07-.308-.112-.602-.112-.882v-.995c0-.14-.056-.252-.154-.35-.098-.098-.21-.154-.336-.154-.14 0-.252.056-.35.154-.098.098-.154.21-.154.35v.995c0 .574.112 1.135.323 1.681zm6.542-6.527c-.182-.491-.491-.882-.911-1.191-.434-.308-.911-.462-1.443-.462-.701 0-1.289.238-1.779.728-.49.491-.728 1.079-.728 1.765v4.007l4.861-4.847z" fill="#fff"/></svg>

До

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

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

@ -1 +0,0 @@
<svg width="31" height="24" viewBox="0 0 31 24" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path d="M9.07 17.683l.644.644c.056.042.112.07.182.07.071 0 .126-.027.182-.07l1.975-1.989c.574.35 1.191.56 1.849.63v1.037h-2.003c-.14 0-.252.042-.35.154-.098.098-.154.21-.154.336 0 .14.056.252.154.35.098.098.21.154.35.154h5.001c.14 0 .252-.056.35-.154.098-.098.154-.21.154-.35 0-.126-.056-.238-.154-.336-.098-.112-.21-.154-.35-.154h-2.003v-1.037c1.135-.126 2.088-.602 2.858-1.457.771-.855 1.149-1.864 1.149-3.012v-.995c0-.14-.056-.252-.154-.35-.098-.098-.21-.154-.336-.154-.14 0-.252.056-.35.154-.098.098-.154.21-.154.35v.995c0 .967-.35 1.793-1.037 2.48-.687.687-1.513 1.022-2.465 1.022-.574 0-1.107-.126-1.611-.392l.757-.756c.28.098.56.154.854.154.687 0 1.274-.252 1.765-.743.49-.491.728-1.078.728-1.765v-.995l2.83-2.83c.042-.042.07-.112.07-.182 0-.056-.028-.126-.07-.168l-.644-.644c-.056-.056-.112-.084-.182-.084-.07 0-.126.028-.182.084l-9.652 9.637c-.042.056-.07.112-.07.182 0 .07.028.126.07.182zm1.149-3.502l.784-.798c-.07-.308-.112-.602-.112-.882v-.995c0-.14-.056-.252-.154-.35-.098-.098-.21-.154-.336-.154-.14 0-.252.056-.35.154-.098.098-.154.21-.154.35v.995c0 .574.112 1.135.323 1.681zm6.542-6.527c-.182-.491-.491-.882-.911-1.191-.434-.308-.911-.462-1.443-.462-.701 0-1.289.238-1.779.728-.49.491-.728 1.079-.728 1.765v4.007l4.861-4.847z" fill="#00A9DC"/></g></svg>

До

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

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

@ -1 +0,0 @@
<svg width="31" height="24" viewBox="0 0 31 24" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path d="M9.07 17.683l.644.644c.056.042.112.07.182.07.071 0 .126-.027.182-.07l1.975-1.989c.574.35 1.191.56 1.849.63v1.037h-2.003c-.14 0-.252.042-.35.154-.098.098-.154.21-.154.336 0 .14.056.252.154.35.098.098.21.154.35.154h5.001c.14 0 .252-.056.35-.154.098-.098.154-.21.154-.35 0-.126-.056-.238-.154-.336-.098-.112-.21-.154-.35-.154h-2.003v-1.037c1.135-.126 2.088-.602 2.858-1.457.771-.855 1.149-1.864 1.149-3.012v-.995c0-.14-.056-.252-.154-.35-.098-.098-.21-.154-.336-.154-.14 0-.252.056-.35.154-.098.098-.154.21-.154.35v.995c0 .967-.35 1.793-1.037 2.48-.687.687-1.513 1.022-2.465 1.022-.574 0-1.107-.126-1.611-.392l.757-.756c.28.098.56.154.854.154.687 0 1.274-.252 1.765-.743.49-.491.728-1.078.728-1.765v-.995l2.83-2.83c.042-.042.07-.112.07-.182 0-.056-.028-.126-.07-.168l-.644-.644c-.056-.056-.112-.084-.182-.084-.07 0-.126.028-.182.084l-9.652 9.637c-.042.056-.07.112-.07.182 0 .07.028.126.07.182zm1.149-3.502l.784-.798c-.07-.308-.112-.602-.112-.882v-.995c0-.14-.056-.252-.154-.35-.098-.098-.21-.154-.336-.154-.14 0-.252.056-.35.154-.098.098-.154.21-.154.35v.995c0 .574.112 1.135.323 1.681zm6.542-6.527c-.182-.491-.491-.882-.911-1.191-.434-.308-.911-.462-1.443-.462-.701 0-1.289.238-1.779.728-.49.491-.728 1.079-.728 1.765v4.007l4.861-4.847z" fill="#333"/></g></svg>

До

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

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

@ -1 +0,0 @@
<svg width="31" height="24" viewBox="0 0 31 24" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path d="M19.002 11.5c0-.273-.226-.5-.5-.5-.273 0-.5.226-.5.5v1c0 1.93-1.57 3.5-3.5 3.5s-3.5-1.57-3.5-3.5v-1c0-.273-.226-.5-.5-.5-.273 0-.5.226-.5.5v1c0 2.312 1.75 4.219 4 4.468v1.031h-2c-.273 0-.5.226-.5.5 0 .273.226.5.5.5h5c.273 0 .5-.226.5-.5 0-.273-.226-.5-.5-.5h-2v-1.031c2.25-.25 4-2.156 4-4.468v-1zm-2-2.999c0-1.375-1.125-2.5-2.5-2.5s-2.5 1.125-2.5 2.5v4c0 1.375 1.125 2.5 2.5 2.5s2.5-1.125 2.5-2.5v-4z" fill="#00A9DC"/></g></svg>

До

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

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

@ -1,70 +0,0 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 36 36">
<style>
use:not(:target) {
display: none;
}
use {
fill: #ccc;
}
use[id$="-hover"] {
fill: #444;
}
use[id$="-active"] {
fill: #0095dd;
}
use[id$="-white"] {
fill: #fff;
}
</style>
<defs>
<g id="blue" transform="translate(-2588 -413) translate(2588 413)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#4A90E2"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#4A90E2"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="orange" transform="translate(-2638 -317) translate(2638 317)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3A35C"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3A35C"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="mintgreen" transform="translate(-2588 -317) translate(2588 317)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#50E2C2"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#50E2C2"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="lightpink" transform="translate(-2687 -366) translate(2687 366)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#E364A1"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#E364A1"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="grey" transform="translate(-2736 -366) translate(2736 366)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9B9B9B"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9B9B9B"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="yellow" transform="translate(-2732 -317) translate(2732 317)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3E968"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3E968"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="purple" transform="translate(-2588 -366) translate(2588 366)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9C61AF"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9C61AF"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="lightgreen" transform="translate(-2686 -317) translate(2686 317)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9AC967"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9AC967"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="darkblue" transform="translate(-2686 -413) translate(2686 413)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#607CAE"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#607CAE"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="darkpink" transform="translate(-2638 -413) translate(2638 413)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#CE4D6E"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#CE4D6E"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="brown" transform="translate(-2736 -413) translate(2736 413)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#8A572A"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#8A572A"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="green" transform="translate(-2638 -366) translate(2637.857 366)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#56B397"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#56B397"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
</defs>
<use id="blue-avatar" xlink:href="#blue"/>
<use id="orange-avatar" xlink:href="#orange"/>
<use id="mintgreen-avatar" xlink:href="#mintgreen"/>
<use id="lightpink-avatar" xlink:href="#lightpink"/>
<use id="grey-avatar" xlink:href="#grey"/>
<use id="yellow-avatar" xlink:href="#yellow"/>
<use id="purple-avatar" xlink:href="#purple"/>
<use id="lightgreen-avatar" xlink:href="#lightgreen"/>
<use id="darkblue-avatar" xlink:href="#darkblue"/>
<use id="darkpink-avatar" xlink:href="#darkpink"/>
<use id="brown-avatar" xlink:href="#brown"/>
<use id="green-avatar" xlink:href="#green"/>
</svg>

До

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

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

@ -1,16 +0,0 @@
<?xml version="1.0"?>
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path fill="#e6e6e6" d="M0,100 100,0 49.1,0 0,49.2z"/>
<path fill="#fff" d="M0,94.7 94.7,0 46.5,0 0,46.6z"/>
<g fill="#999">
<path d="m25.9,56.7l-4,4-13.7-13.7 3.7-3.7c2.4-2.4 5.7-4.1 8.3-1.4 1.7,1.7 1.4,3.9 .5,5.3l.1,.1c1.6-1.1 4-1.9 6.3,.3 3,3 1.3,6.5-1.2,9.1zm-12.2-12.2l-2.2,2.2 4.3,4.3 2.3-2.3c1.6-1.6 1.8-3.1 .2-4.7-1.4-1.6-2.9-1.2-4.6,.5zm6.1,5.4l-2.5,2.5 4.9,4.9 2.4-2.4c1.3-1.3 2.5-3.3 .6-5.3-2-2-3.9-1.2-5.4,.3z"/>
<path d="m30.7,42.7c2.5,2.2 4.6,2.1 6.2,.5 1-1 1.5-2 1.6-3.6l2,.4c-.2,1.7-.9,3.4-2.2,4.7-3.1,3.1-6.9,2.5-10.2-.7-3.2-3.2-3.8-7.1-1.1-9.9 2.7-2.7 6.2-2.3 9.4,.9 .4,.4 .7,.7 .9,1l-6.6,6.7zm-1.4-1.4l4.9-4.9c-2.2-2.1-4.1-2.5-5.7-.9-1.4,1.5-1.3,3.4 .8,5.8z"/>
<path d="m49.8,31.8c-.2,1.1-.8,2.2-1.6,3.1-1.9,1.9-4,1.6-5.9-.2l-6.3-6.3-1.6,1.6-1.4-1.4 1.7-1.7-2.4-2.4 1.6-2 2.6,2.6 2.6-2.6 1.2,1.6-2.4,2.4 6.3,6.3c1.1,1.1 1.9,1.2 2.8,.3 .5-.5 .7-1 .9-1.8l1.9,.5z"/>
<path d="m57,20.8c.8,.8 1.4,.9 2.1,.6l.8,1.7c-1.1,.8-2.1,1.1-3.4,.5 .3,1.7-.4,3.3-1.6,4.6-2.1,2.1-4.7,2.1-6.6,.2-2.2-2.2-1.6-5.1 1.5-8.3l1.4-1.3-.8-.8c-1.5-1.5-2.8-1.3-4.3,.2-.7,.7-1.5,1.8-2.2,3.3l-1.8-.9c.8-1.8 1.8-3.1 2.8-4.2 2.7-2.7 5.1-2.5 7.2-.4l4.9,4.8zm-2,1.6l-2.6-2.6-1.3,1.3c-2.2,2.2-2.2,3.8-.9,5.1 1.3,1.3 2.5,1.4 3.8,.1 1.1-1.1 1.3-2.4 1-3.9z"/>
<path d="M93.4,0 0,94.1 0,96 95.2,0z"/>
<path d="M45.3,0 0,46 0,47.9 47,0z"/>
</g>
</svg>

До

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

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

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="67px" height="28px" viewBox="0 0 67 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M2,14 C2,7.372583 7.37049212,2 13.9962035,2 L52.9937982,2 C59.6191184,2 64.9900017,7.37112582 64.9900017,14 C64.9900017,20.627417 59.6195096,26 52.9937982,26 L13.9962035,26 C7.37088326,26 2,20.6288742 2,14 Z M32.994984,7.28907739 L32.994984,21.2024677 L33.9950177,21.2024677 L33.9950177,7.28907739 L32.994984,7.28907739 Z" id="Rectangle-1264" fill-opacity="0.95" fill="#FFFFFF"></path>
<path d="M2,14 L2,14 C2,20.6288742 7.37173577,26 13.9981077,26 L53.0018923,26 C59.6286554,26 65,20.627417 65,14 L65,14 C65,7.37112582 59.6282642,2 53.0018923,2 L13.9981077,2 C7.37134457,2 2,7.372583 2,14 L2,14 Z M0,14 L0,14 C0,6.26795905 6.26682948,0 13.9981077,0 L53.0018923,0 C60.73337,0 67,6.26709264 67,14 C67,21.7320409 60.7331705,28 53.0018923,28 L13.9981077,28 C6.26662998,28 0,21.7329074 0,14 L0,14 Z" id="Shape" fill-opacity="0.2" fill="#000000"></path>
<path d="M14.5056409,19.4029285 L18.9833396,19.4029285 C19.5376264,19.4029285 20.0166604,19.2013186 20.4075253,18.8104537 C20.8113066,18.407234 21,17.9405548 21,17.3733515 L21,16.2136735 L23.8360165,19.04969 C23.9247473,19.1384208 24.0252714,19.1760472 24.1516286,19.1760472 C24.2145264,19.1760472 24.2650693,19.1636922 24.3279671,19.1384208 C24.517222,19.0626065 24.6053912,18.9238944 24.6053912,18.7222845 L24.6053912,11.0706559 C24.6053912,10.8819625 24.517222,10.7432504 24.3279671,10.6550812 C24.2650693,10.6421647 24.2145264,10.6298098 24.1516286,10.6298098 C24.0252714,10.6298098 23.9247473,10.6674361 23.8360165,10.7556054 L21,13.5792669 L21,12.9085694 L22.8301248,11.0784446 C22.8719395,11.0366299 22.900024,10.9667307 22.900024,10.8962074 C22.900024,10.8406625 22.8719395,10.7701392 22.8301248,10.7283245 L22.1860538,10.0842535 C22.1298848,10.0280845 22.0737158,10 22.0038166,10 C21.9339174,10 21.8777484,10.0280845 21.8215795,10.0842535 L12.1698752,19.7216035 C12.1280605,19.7777724 12.099976,19.8339414 12.099976,19.9038406 C12.099976,19.9737398 12.1280605,20.0299088 12.1698752,20.0860778 L12.8139462,20.7301488 C12.8701152,20.7725876 12.9262842,20.800048 12.9961834,20.800048 C13.0667067,20.800048 13.1222516,20.7725876 13.1784205,20.7301488 L14.5056409,19.4029285 Z M19.454018,10.4528944 C19.3038986,10.4193577 19.1468952,10.4029285 18.9833396,10.4029285 L14.0295769,10.4029285 C13.4623736,10.4029285 12.9956945,10.5916218 12.5924747,10.9954032 C12.2016099,11.386268 12,11.8647405 12,12.4195889 L12,17.3733515 C12,17.5387368 12.0171407,17.6955757 12.0511158,17.8447868 L19.454018,10.4528944 L19.454018,10.4528944 Z" id="Fill-83-Copy-3" fill="#00A9DC"></path>
<path d="M42.0698992,19.6831493 L42.7139702,20.3272204 C42.7701392,20.3696591 42.8263082,20.3971195 42.8962074,20.3971195 C42.9667307,20.3971195 43.0222756,20.3696591 43.0784446,20.3272204 L45.0537206,18.3382141 C45.6278925,18.6883341 46.2445031,18.8986558 46.9029285,18.968555 L46.9029285,20.0051848 L44.8995679,20.0051848 C44.7597696,20.0051848 44.6474316,20.0469995 44.5494479,20.1593375 C44.4514642,20.2573212 44.3952952,20.3696591 44.3952952,20.4957273 C44.3952952,20.6355257 44.4514642,20.7478637 44.5494479,20.8458473 C44.6474316,20.943831 44.7597696,21 44.8995679,21 L49.9004801,21 C50.0402784,21 50.1526164,20.943831 50.2506001,20.8458473 C50.3485838,20.7478637 50.4047528,20.6355257 50.4047528,20.4957273 C50.4047528,20.3696591 50.3485838,20.2573212 50.2506001,20.1593375 C50.1526164,20.0469995 50.0402784,20.0051848 49.9004801,20.0051848 L47.8971195,20.0051848 L47.8971195,18.968555 C49.0317331,18.8424868 49.9847336,18.3662986 50.7548728,17.5119059 C51.5256361,16.6568891 51.9038406,15.6483437 51.9038406,14.5 L51.9038406,13.5051848 C51.9038406,13.3653865 51.8476716,13.2530485 51.749688,13.1550648 C51.6517043,13.0570811 51.5393663,13.0009121 51.4132981,13.0009121 C51.2734998,13.0009121 51.1611618,13.0570811 51.0631781,13.1550648 C50.9651944,13.2530485 50.9090254,13.3653865 50.9090254,13.5051848 L50.9090254,14.5 C50.9090254,15.4667307 50.5589054,16.2930389 49.8723956,16.9795487 C49.1858857,17.6660586 48.3595775,18.0018243 47.4072012,18.0018243 C46.8330293,18.0018243 46.300048,17.8757561 45.7957753,17.6098896 L46.5528084,16.8534806 C46.8330293,16.9514642 47.112626,17.0076332 47.4072012,17.0076332 C48.093711,17.0076332 48.6816131,16.7554969 49.1721555,16.2649544 C49.6620739,15.7744119 49.9004801,15.1865098 49.9004801,14.5 L49.9004801,13.5051848 L52.7301488,10.6755161 C52.7719635,10.6337014 52.800048,10.5638022 52.800048,10.4932789 C52.800048,10.437734 52.7719635,10.3672108 52.7301488,10.3253961 L52.0860778,9.68132501 C52.0299088,9.62515602 51.9737398,9.59707153 51.9038406,9.59707153 C51.8339414,9.59707153 51.7777724,9.62515602 51.7216035,9.68132501 L42.0698992,19.318675 C42.0280845,19.374844 42,19.431013 42,19.5009121 C42,19.5708113 42.0280845,19.6269803 42.0698992,19.6831493 L42.0698992,19.6831493 Z M43.218867,16.1807009 L44.0027364,15.3824772 C43.9328373,15.0741719 43.8910226,14.7802208 43.8910226,14.5 L43.8910226,13.5051848 C43.8910226,13.3653865 43.8348536,13.2530485 43.7368699,13.1550648 C43.6388862,13.0570811 43.5265482,13.0009121 43.4004801,13.0009121 C43.2606817,13.0009121 43.1483437,13.0570811 43.0503601,13.1550648 C42.9523764,13.2530485 42.8962074,13.3653865 42.8962074,13.5051848 L42.8962074,14.5 C42.8962074,15.0741719 43.0085454,15.6346135 43.218867,16.1807009 L43.218867,16.1807009 Z M49.7606817,9.65324052 C49.5784446,9.16269803 49.2701392,8.77076332 48.85012,8.46245799 C48.4157465,8.15415266 47.9395583,8 47.4072012,8 C46.706337,8 46.1184349,8.23840614 45.6278925,8.72832453 C45.1379741,9.21886702 44.8995679,9.80739318 44.8995679,10.4932789 L44.8995679,14.5 L49.7606817,9.65324052 L49.7606817,9.65324052 Z" id="Fill-90-Copy-2" fill="#00A9DC"></path>
</g>
</svg>

До

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

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

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="67px" height="28px" viewBox="0 0 67 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Rectangle-1264-+-Shape">
<path d="M2,14 C2,7.372583 7.37049212,2 13.9962035,2 L52.9937982,2 C59.6191184,2 64.9900017,7.37112582 64.9900017,14 C64.9900017,20.627417 59.6195096,26 52.9937982,26 L13.9962035,26 C7.37088326,26 2,20.6288742 2,14 Z M32.994984,7.28907739 L32.994984,21.2024677 L33.9950177,21.2024677 L33.9950177,7.28907739 L32.994984,7.28907739 Z" id="Rectangle-1264" opacity="0.95" fill="#FFFFFF"></path>
<path d="M2,14 L2,14 C2,20.6288742 7.37173577,26 13.9981077,26 L53.0018923,26 C59.6286554,26 65,20.627417 65,14 L65,14 C65,7.37112582 59.6282642,2 53.0018923,2 L13.9981077,2 C7.37134457,2 2,7.372583 2,14 L2,14 Z M0,14 L0,14 C0,6.26795905 6.26682948,0 13.9981077,0 L53.0018923,0 C60.73337,0 67,6.26709264 67,14 C67,21.7320409 60.7331705,28 53.0018923,28 L13.9981077,28 C6.26662998,28 0,21.7329074 0,14 L0,14 Z" id="Shape" fill-opacity="0.2" fill="#4A4A4A"></path>
</g>
</g>
</svg>

До

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

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

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="67px" height="28px" viewBox="0 0 67 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M2,14 C2,7.372583 7.37049212,2 13.9962035,2 L52.9937982,2 C59.6191184,2 64.9900017,7.37112582 64.9900017,14 C64.9900017,20.627417 59.6195096,26 52.9937982,26 L13.9962035,26 C7.37088326,26 2,20.6288742 2,14 Z M32.994984,7.28907739 L32.994984,21.2024677 L33.9950177,21.2024677 L33.9950177,7.28907739 L32.994984,7.28907739 Z" id="Rectangle-1264" opacity="0.95" fill="#5CCCEE"></path>
<path d="M2,14 L2,14 C2,20.6288742 7.37173577,26 13.9981077,26 L53.0018923,26 C59.6286554,26 65,20.627417 65,14 L65,14 C65,7.37112582 59.6282642,2 53.0018923,2 L13.9981077,2 C7.37134457,2 2,7.372583 2,14 L2,14 Z M0,14 L0,14 C0,6.26795905 6.26682948,0 13.9981077,0 L53.0018923,0 C60.73337,0 67,6.26709264 67,14 C67,21.7320409 60.7331705,28 53.0018923,28 L13.9981077,28 C6.26662998,28 0,21.7329074 0,14 L0,14 Z" id="Shape" fill-opacity="0.2" fill="#000000"></path>
</g>
</svg>

До

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

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

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<svg width="20" height="8" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<title>chatbubble-arrow</title>
<desc>Created with Sketch.</desc>
<g>
<title>Layer 1</title>
<g transform="rotate(180 6.2844319343566895,3.8364052772521973) " id="svg_1" fill="none">
<path id="svg_2" fill="#5cccee" d="m12.061934,7.656905l-9.299002,0l0,-1l6.088001,0c-2.110002,-0.967001 -4.742001,-2.818 -6.088001,-6.278l0.932,-0.363c2.201999,5.664001 8.377999,6.637 8.439999,6.646c0.259001,0.039 0.444,0.27 0.426001,0.531c-0.019001,0.262 -0.237,0.464 -0.498999,0.464l-12.072001,-0.352001"/>
</g>
<line id="svg_13" y2="0.529488" x2="13.851821" y1="0.529488" x1="17.916953" stroke="#5cccee" fill="none"/>
<line id="svg_26" y2="0.529488" x2="9.79687" y1="0.529488" x1="13.862002" stroke="#5cccee" fill="none"/>
<line id="svg_27" y2="0.529488" x2="15.908413" y1="0.529488" x1="19.973545" stroke="#5cccee" fill="none"/>
</g>
</svg>

До

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

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

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<svg width="20" height="9" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<title>chatbubble-arrow</title>
<desc>Created with Sketch.</desc>
<g>
<title>Layer 1</title>
<g id="svg_1" fill="none">
<path id="svg_2" fill="#d8d8d8" d="m19.505243,8.972466l-9.299002,0l0,-1l6.088001,0c-2.110002,-0.967 -4.742001,-2.818 -6.088001,-6.278l0.932,-0.363c2.202,5.664 8.377999,6.637 8.44,6.646c0.259001,0.039 0.444,0.27 0.426001,0.531c-0.019001,0.262 -0.237,0.464 -0.498999,0.464l-12.072001,-0.352"/>
</g>
<line id="svg_13" y2="8.474788" x2="6.200791" y1="8.474788" x1="10.265923" stroke="#d8d8d8" fill="none"/>
<line id="svg_26" y2="8.474788" x2="2.14584" y1="8.474788" x1="6.210972" stroke="#d8d8d8" fill="none"/>
<line id="svg_27" y2="8.474788" x2="0.000501" y1="8.474788" x1="4.065633" stroke="#d8d8d8" fill="none"/>
</g>
</svg>

До

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

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

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="21" height="21" viewBox="0 0 21 21">
<style>
use:not(:target) {
display: none;
}
use {
fill: #fff;
stroke: #fff;
stroke-width: 0.5;
}
use[id$="-inverted"] {
fill: #0095dd;
stroke: #0095dd;
stroke-width: 0.5;
}
use[id$="-disabled"] {
fill: rgba(255,255,255,.4);
stroke: rgba(255,255,255,.4);
stroke-width: 0.5;
}
use[id$="-blue"] {
fill: #0096dd;
}
</style>
<defs>
<path id="check-shape" d="M 9.39,16.5 16.28,6 14.77,4.5 9.37,12.7 6.28,9.2 4.7,10.7 z"/>
</defs>
<use id="check" xlink:href="#check-shape"/>
<use id="check-active" xlink:href="#check-shape"/>
<use id="check-disabled" xlink:href="#check-shape"/>
<use id="check-blue" xlink:href="#check-shape"/>
</svg>

До

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

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

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><path fill="#ffffff" d="M47.3,38.6c0-0.9-0.4-1.9-1.1-2.6l-11-11l11-11c0.7-0.7,1.1-1.6,1.1-2.6c0-0.9-0.4-1.9-1.1-2.6l-5.1-5.1 c-0.7-0.7-1.6-1.1-2.6-1.1c-0.9,0-1.9,0.4-2.6,1.1l-11,11l-11-11c-0.7-0.7-1.6-1.1-2.6-1.1c-0.9,0-1.9,0.4-2.6,1.1L3.8,8.9 c-0.7,0.7-1.1,1.6-1.1,2.6c0,0.9,0.4,1.9,1.1,2.6l11,11l-11,11c-0.7,0.7-1.1,1.6-1.1,2.6c0,0.9,0.4,1.9,1.1,2.6l5.1,5.1 c0.7,0.7,1.6,1.1,2.6,1.1c0.9,0,1.9-0.4,2.6-1.1l11-11l11,11c0.7,0.7,1.6,1.1,2.6,1.1c0.9,0,1.9-0.4,2.6-1.1l5.1-5.1 C46.9,40.5,47.3,39.5,47.3,38.6z"/></svg>

До

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

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

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:x="http://www.w3.org/1999/xlink" viewBox="0 0 15 20"><style>use,:target~#blue{display:none}#blue,:target{display:inherit}#blue{fill:#00a9dc}#orange{fill:#f7a25e}</style><defs><g id="cursor"><path d="M3.387 12.783l2.833 5.009.469.829.851-.428 1.979-.995h-.001l.938-.472-.517-.914-2.553-4.513h5.244l-1.966-1.747-9-8-1.664-1.479v17.227-.001l1.8-2.4 1.587-2.116z" fill="#fff"/><path fill-rule="evenodd" d="M3.505 10.96l3.586 6.34 1.979-.995-3.397-6.005h4.327l-9-8v12l2.505-3.34z"/></g></defs><use id="orange" x:href="#cursor"/><use id="blue" x:href="#cursor"/></svg>

До

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

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

@ -1 +0,0 @@
<svg width="4" height="20" viewBox="0 0 4 20" xmlns="http://www.w3.org/2000/svg"><g fill="#3A99DA"><ellipse cx="2" cy="2" rx="2" ry="2"/><ellipse cx="2" cy="10" rx="2" ry="2"/><ellipse cx="2" cy="18" rx="2" ry="2"/></g></svg>

До

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

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

@ -1 +0,0 @@
<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><title>Hello_Nomatch@1x</title><g fill="none" fill-rule="evenodd"><path d="M16.2734375,25.1289062 C16.2734375,21.4179502 17.4648318,17.6582222 19.8476562,13.8496094 C22.2304807,10.0409966 25.7070084,6.88673125 30.2773438,4.38671875 C34.8476791,1.88670625 40.179657,0.63671875 46.2734375,0.63671875 C51.9375283,0.63671875 56.9374783,1.68163018 61.2734375,3.77148438 C65.6093967,5.86133857 68.9589726,8.70310703 71.3222656,12.296875 C73.6855587,15.890643 74.8671875,19.7968539 74.8671875,24.015625 C74.8671875,27.3359541 74.1933661,30.2460813 72.8457031,32.7460938 C71.4980401,35.2461062 69.8964937,37.4042878 68.0410156,39.2207031 C66.1855376,41.0371185 62.8554928,44.0937285 58.0507812,48.390625 C56.7226496,49.6015686 55.6582071,50.666011 54.8574219,51.5839844 C54.0566366,52.5019577 53.4609395,53.3417931 53.0703125,54.1035156 C52.6796855,54.8652382 52.3769542,55.6269493 52.1621094,56.3886719 C51.9472646,57.1503944 51.6250021,58.4882717 51.1953125,60.4023438 C50.4531213,64.4648641 48.1289258,66.4960938 44.2226562,66.4960938 C42.1913961,66.4960938 40.4824288,65.8320379 39.0957031,64.5039062 C37.7089774,63.1757746 37.015625,61.2031381 37.015625,58.5859375 C37.015625,55.3046711 37.5234324,52.4629026 38.5390625,50.0605469 C39.5546926,47.6581911 40.9023354,45.5488372 42.5820312,43.7324219 C44.2617271,41.9160065 46.5273295,39.757825 49.3789062,37.2578125 C51.8789188,35.0703016 53.6855413,33.4199274 54.7988281,32.3066406 C55.9121149,31.1933538 56.8496056,29.9531318 57.6113281,28.5859375 C58.3730507,27.2187432 58.7539062,25.734383 58.7539062,24.1328125 C58.7539062,21.0077969 57.5918085,18.3711045 55.2675781,16.2226562 C52.9433478,14.074208 49.9453309,13 46.2734375,13 C41.976541,13 38.8125102,14.0839735 36.78125,16.2519531 C34.7499898,18.4199327 33.031257,21.6132602 31.625,25.8320312 C30.2968684,30.2461158 27.7773623,32.453125 24.0664062,32.453125 C21.8788953,32.453125 20.0332106,31.6816483 18.5292969,30.1386719 C17.0253831,28.5956954 16.2734375,26.9257902 16.2734375,25.1289062 L16.2734375,25.1289062 Z M44.8671875,89.3476562 C42.4843631,89.3476562 40.4043058,88.5761796 38.6269531,87.0332031 C36.8496005,85.4902267 35.9609375,83.3320451 35.9609375,80.5585938 C35.9609375,78.0976439 36.8203039,76.0273521 38.5390625,74.3476562 C40.2578211,72.6679604 42.367175,71.828125 44.8671875,71.828125 C47.3281373,71.828125 49.3984291,72.6679604 51.078125,74.3476562 C52.7578209,76.0273521 53.5976562,78.0976439 53.5976562,80.5585938 C53.5976562,83.2929824 52.7187588,85.4413984 50.9609375,87.0039062 C49.2031162,88.5664141 47.1718865,89.3476562 44.8671875,89.3476562 L44.8671875,89.3476562 Z" fill="#00A9DC" transform="translate(19 15)"/><path d="M30.0307824,90.6694694 C26.2851884,95.1633516 20.7911271,98 14.6681559,98 C9.0246635,98 3.91544008,95.5902225 0.217089748,91.6941393 C0.144175866,91.6173271 0.0718103773,91.5399371 -1.41595069e-13,91.4619766 C3.2164267,86.1597713 8.85313514,82.6443615 15.269241,82.6443615 C21.3600311,82.6443615 26.7484556,85.8123252 30.0307824,90.6694694 L30.0307824,90.6694694 Z M15.269241,80.1856784 C20.5820599,80.1856784 24.8889507,75.6485191 24.8889507,70.0516495 C24.8889507,64.4547799 20.5820599,59.9176207 15.269241,59.9176207 C9.95642199,59.9176207 5.64953124,64.4547799 5.64953124,70.0516495 C5.64953124,75.6485191 9.95642199,80.1856784 15.269241,80.1856784 Z" fill-opacity=".8" fill="#D8D8D8" transform="translate(19 15)"/><path d="M90.7157496,90.6694694 C86.9701557,95.1633516 81.4760943,98 75.3531231,98 C69.7096307,98 64.6004073,95.5902225 60.902057,91.6941393 C60.8291431,91.6173271 60.7567776,91.5399371 60.6849672,91.4619766 C63.9013939,86.1597713 69.5381024,82.6443615 75.9542082,82.6443615 C82.0449983,82.6443615 87.4334229,85.8123252 90.7157496,90.6694694 L90.7157496,90.6694694 Z M75.9542082,80.1856784 C81.2670271,80.1856784 85.5739179,75.6485191 85.5739179,70.0516495 C85.5739179,64.4547799 81.2670271,59.9176207 75.9542082,59.9176207 C70.6413892,59.9176207 66.3344985,64.4547799 66.3344985,70.0516495 C66.3344985,75.6485191 70.6413892,80.1856784 75.9542082,80.1856784 Z" fill-opacity=".8" fill="#D8D8D8" transform="translate(19 15)"/></g></svg>

До

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

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

@ -1,9 +0,0 @@
<svg width="28" height="28" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g>
<title>Layer 1</title>
<rect rx="40" height="24" width="24" y="2.1" x="2" fill="#D74345" opacity="0.95" id="Rectangle-1264-Copy"/>
<path fill="#000000" fill-opacity="0.2" id="Shape" d="m2,14.1l0,0c0,6.62887 5.37258,12 12,12l0,0c6.62887,0 12,-5.37258 12,-12l0,0c0,-6.62887 -5.37258,-12 -12,-12l0,0c-6.62887,0 -12,5.37258 -12,12l0,0zm-2,0l0,0c0,-7.73245 6.26702,-14 14,-14c7.73245,0 14,6.26702 14,14c0,7.73245 -6.267019,14 -14,14c-7.73245,0 -14,-6.267019 -14,-14l0,0z"/>
<path fill="#FFFFFF" id="Shape" d="m14.726974,11.994536l-3.000204,0c-0.556093,0 -0.999815,0.451912 -0.999815,1.009375l0,1.98127c0,0.56602 0.447632,1.00937 0.999815,1.00937l3.000204,0l0,1.00297c0,0.543991 0.34978,0.717209 0.78125,0.37203l3.4375,-2.750002c0.43758,-0.350061 0.431469,-0.904824 0,-1.250006l-3.4375,-2.750013c-0.43758,-0.350061 -0.78125,-0.178611 -0.78125,0.372032l0,1.002975zm-6.000027,-2.00001l5.000022,0l0,1.000005l-4.000019,0l0,6.000019l4.000019,0l0,1.01l-5.000022,0l0,-8.010024z"/>
</g>
</svg>

До

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

Двоичный файл не отображается.

До

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

Двоичный файл не отображается.

До

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

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