This commit is contained in:
Wes Kocher 2015-07-21 16:11:44 -07:00
Родитель 1828b64f06 8dc017722b
Коммит 808edf9e7e
114 изменённых файлов: 1453 добавлений и 2275 удалений

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

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="805cf546729ba742bf23febda52970fcb35c0e8f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="84c3bf622e211046d905803b34de5d331761f22d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="62682dcee5368fe43284efa4e90f22cb1c88b79e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="11b6bfa7620f31b9c2bc2e537b66233daf5e192f"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

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

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="805cf546729ba742bf23febda52970fcb35c0e8f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="84c3bf622e211046d905803b34de5d331761f22d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="62682dcee5368fe43284efa4e90f22cb1c88b79e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="11b6bfa7620f31b9c2bc2e537b66233daf5e192f"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

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

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="805cf546729ba742bf23febda52970fcb35c0e8f"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="84c3bf622e211046d905803b34de5d331761f22d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8bc59310552179f9a8bc6cdd0188e2475df52fb7"/>

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

@ -17,10 +17,10 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="805cf546729ba742bf23febda52970fcb35c0e8f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="84c3bf622e211046d905803b34de5d331761f22d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="62682dcee5368fe43284efa4e90f22cb1c88b79e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="11b6bfa7620f31b9c2bc2e537b66233daf5e192f"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

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

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="805cf546729ba742bf23febda52970fcb35c0e8f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="84c3bf622e211046d905803b34de5d331761f22d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="62682dcee5368fe43284efa4e90f22cb1c88b79e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="11b6bfa7620f31b9c2bc2e537b66233daf5e192f"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>

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

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="805cf546729ba742bf23febda52970fcb35c0e8f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="84c3bf622e211046d905803b34de5d331761f22d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="62682dcee5368fe43284efa4e90f22cb1c88b79e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="11b6bfa7620f31b9c2bc2e537b66233daf5e192f"/>
<!-- Stock Android things -->
<project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>

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

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="805cf546729ba742bf23febda52970fcb35c0e8f"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="84c3bf622e211046d905803b34de5d331761f22d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8bc59310552179f9a8bc6cdd0188e2475df52fb7"/>

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

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="805cf546729ba742bf23febda52970fcb35c0e8f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="84c3bf622e211046d905803b34de5d331761f22d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="62682dcee5368fe43284efa4e90f22cb1c88b79e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="11b6bfa7620f31b9c2bc2e537b66233daf5e192f"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

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

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "805cf546729ba742bf23febda52970fcb35c0e8f",
"git_revision": "84c3bf622e211046d905803b34de5d331761f22d",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "669a688b25adf51474f17cc6febdf2a4ba626b93",
"revision": "86ee685954005405682f78d7b3009dd61ac2e1ec",
"repo_path": "integration/gaia-central"
}

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

@ -17,10 +17,10 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="805cf546729ba742bf23febda52970fcb35c0e8f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="84c3bf622e211046d905803b34de5d331761f22d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="62682dcee5368fe43284efa4e90f22cb1c88b79e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="11b6bfa7620f31b9c2bc2e537b66233daf5e192f"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

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

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="805cf546729ba742bf23febda52970fcb35c0e8f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="84c3bf622e211046d905803b34de5d331761f22d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="62682dcee5368fe43284efa4e90f22cb1c88b79e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="11b6bfa7620f31b9c2bc2e537b66233daf5e192f"/>
<!-- Stock Android things -->
<project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>

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

@ -1758,6 +1758,9 @@ pref("loop.debug.dispatcher", false);
pref("loop.debug.websocket", false);
pref("loop.debug.sdk", false);
pref("loop.debug.twoWayMediaTelemetry", false);
pref("loop.feedback.dateLastSeenSec", 0);
pref("loop.feedback.periodSec", 15770000); // 6 months.
pref("loop.feedback.formURL", "http://www.surveygizmo.com/s3/2227372/Firefox-Hello-Product-Survey");
#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

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

@ -17,7 +17,7 @@ content/js/conversation.js
content/js/conversationViews.js
content/js/panel.js
content/js/roomViews.js
content/shared/js/feedbackViews.js
content/js/feedbackViews.js
content/shared/js/textChatView.js
content/shared/js/views.js
standalone/content/js/fxOSMarketplace.js

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

@ -27,7 +27,6 @@
<script type="text/javascript" src="loop/shared/js/utils.js"></script>
<script type="text/javascript" src="loop/shared/js/mixins.js"></script>
<script type="text/javascript" src="loop/shared/js/feedbackApiClient.js"></script>
<script type="text/javascript" src="loop/shared/js/actions.js"></script>
<script type="text/javascript" src="loop/shared/js/validate.js"></script>
<script type="text/javascript" src="loop/shared/js/dispatcher.js"></script>
@ -37,9 +36,8 @@
<script type="text/javascript" src="loop/shared/js/roomStates.js"></script>
<script type="text/javascript" src="loop/shared/js/fxOSActiveRoomStore.js"></script>
<script type="text/javascript" src="loop/shared/js/activeRoomStore.js"></script>
<script type="text/javascript" src="loop/shared/js/feedbackStore.js"></script>
<script type="text/javascript" src="loop/shared/js/views.js"></script>
<script type="text/javascript" src="loop/shared/js/feedbackViews.js"></script>
<script type="text/javascript" src="loop/js/feedbackViews.js"></script>
<script type="text/javascript" src="loop/shared/js/textChatStore.js"></script>
<script type="text/javascript" src="loop/shared/js/textChatView.js"></script>
<script type="text/javascript" src="loop/js/conversationViews.js"></script>

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

@ -6,14 +6,12 @@ var loop = loop || {};
loop.conversation = (function(mozL10n) {
"use strict";
var sharedViews = loop.shared.views;
var sharedMixins = loop.shared.mixins;
var sharedModels = loop.shared.models;
var sharedActions = loop.shared.actions;
var CallControllerView = loop.conversationViews.CallControllerView;
var CallIdentifierView = loop.conversationViews.CallIdentifierView;
var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
var FeedbackView = loop.feedbackViews.FeedbackView;
var GenericFailureView = loop.conversationViews.GenericFailureView;
/**
@ -24,6 +22,7 @@ loop.conversation = (function(mozL10n) {
mixins: [
Backbone.Events,
loop.store.StoreMixin("conversationAppStore"),
sharedMixins.DocumentTitleMixin,
sharedMixins.WindowCloseMixin
],
@ -37,19 +36,51 @@ loop.conversation = (function(mozL10n) {
return this.getStoreState();
},
_renderFeedbackForm: function() {
this.setTitle(mozL10n.get("conversation_has_ended"));
return (React.createElement(FeedbackView, {
mozLoop: this.props.mozLoop,
onAfterFeedbackReceived: this.closeWindow}));
},
/**
* We only show the feedback for once every 6 months, otherwise close
* the window.
*/
handleCallTerminated: function() {
var delta = new Date() - new Date(this.state.feedbackTimestamp);
// Show timestamp if feedback period (6 months) passed.
// 0 is default value for pref. Always show feedback form on first use.
if (this.state.feedbackTimestamp === 0 ||
delta >= this.state.feedbackPeriod) {
this.props.dispatcher.dispatch(new sharedActions.ShowFeedbackForm());
return;
}
this.closeWindow();
},
render: function() {
if (this.state.showFeedbackForm) {
return this._renderFeedbackForm();
}
switch(this.state.windowType) {
// CallControllerView is used for both.
case "incoming":
case "outgoing": {
return (React.createElement(CallControllerView, {
dispatcher: this.props.dispatcher,
mozLoop: this.props.mozLoop}));
mozLoop: this.props.mozLoop,
onCallTerminated: this.handleCallTerminated}));
}
case "room": {
return (React.createElement(DesktopRoomConversationView, {
dispatcher: this.props.dispatcher,
mozLoop: this.props.mozLoop,
onCallTerminated: this.handleCallTerminated,
roomStore: this.props.roomStore}));
}
case "failed": {
@ -102,15 +133,6 @@ loop.conversation = (function(mozL10n) {
// expose for functional tests
loop.conversation._sdkDriver = sdkDriver;
var appVersionInfo = navigator.mozLoop.appVersionInfo;
var feedbackClient = new loop.FeedbackAPIClient(
navigator.mozLoop.getLoopPref("feedback.baseUrl"), {
product: navigator.mozLoop.getLoopPref("feedback.product"),
platform: appVersionInfo.OS,
channel: appVersionInfo.channel,
version: appVersionInfo.version
});
// Create the stores.
var conversationAppStore = new loop.store.ConversationAppStore({
dispatcher: dispatcher,
@ -131,9 +153,6 @@ loop.conversation = (function(mozL10n) {
mozLoop: navigator.mozLoop,
activeRoomStore: activeRoomStore
});
var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
feedbackClient: feedbackClient
});
var textChatStore = new loop.store.TextChatStore(dispatcher, {
sdkDriver: sdkDriver
});
@ -141,7 +160,6 @@ loop.conversation = (function(mozL10n) {
loop.store.StoreMixin.register({
conversationAppStore: conversationAppStore,
conversationStore: conversationStore,
feedbackStore: feedbackStore,
textChatStore: textChatStore
});

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

@ -6,14 +6,12 @@ var loop = loop || {};
loop.conversation = (function(mozL10n) {
"use strict";
var sharedViews = loop.shared.views;
var sharedMixins = loop.shared.mixins;
var sharedModels = loop.shared.models;
var sharedActions = loop.shared.actions;
var CallControllerView = loop.conversationViews.CallControllerView;
var CallIdentifierView = loop.conversationViews.CallIdentifierView;
var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
var FeedbackView = loop.feedbackViews.FeedbackView;
var GenericFailureView = loop.conversationViews.GenericFailureView;
/**
@ -24,6 +22,7 @@ loop.conversation = (function(mozL10n) {
mixins: [
Backbone.Events,
loop.store.StoreMixin("conversationAppStore"),
sharedMixins.DocumentTitleMixin,
sharedMixins.WindowCloseMixin
],
@ -37,19 +36,51 @@ loop.conversation = (function(mozL10n) {
return this.getStoreState();
},
_renderFeedbackForm: function() {
this.setTitle(mozL10n.get("conversation_has_ended"));
return (<FeedbackView
mozLoop={this.props.mozLoop}
onAfterFeedbackReceived={this.closeWindow} />);
},
/**
* We only show the feedback for once every 6 months, otherwise close
* the window.
*/
handleCallTerminated: function() {
var delta = new Date() - new Date(this.state.feedbackTimestamp);
// Show timestamp if feedback period (6 months) passed.
// 0 is default value for pref. Always show feedback form on first use.
if (this.state.feedbackTimestamp === 0 ||
delta >= this.state.feedbackPeriod) {
this.props.dispatcher.dispatch(new sharedActions.ShowFeedbackForm());
return;
}
this.closeWindow();
},
render: function() {
if (this.state.showFeedbackForm) {
return this._renderFeedbackForm();
}
switch(this.state.windowType) {
// CallControllerView is used for both.
case "incoming":
case "outgoing": {
return (<CallControllerView
dispatcher={this.props.dispatcher}
mozLoop={this.props.mozLoop} />);
mozLoop={this.props.mozLoop}
onCallTerminated={this.handleCallTerminated} />);
}
case "room": {
return (<DesktopRoomConversationView
dispatcher={this.props.dispatcher}
mozLoop={this.props.mozLoop}
onCallTerminated={this.handleCallTerminated}
roomStore={this.props.roomStore} />);
}
case "failed": {
@ -102,15 +133,6 @@ loop.conversation = (function(mozL10n) {
// expose for functional tests
loop.conversation._sdkDriver = sdkDriver;
var appVersionInfo = navigator.mozLoop.appVersionInfo;
var feedbackClient = new loop.FeedbackAPIClient(
navigator.mozLoop.getLoopPref("feedback.baseUrl"), {
product: navigator.mozLoop.getLoopPref("feedback.product"),
platform: appVersionInfo.OS,
channel: appVersionInfo.channel,
version: appVersionInfo.version
});
// Create the stores.
var conversationAppStore = new loop.store.ConversationAppStore({
dispatcher: dispatcher,
@ -131,9 +153,6 @@ loop.conversation = (function(mozL10n) {
mozLoop: navigator.mozLoop,
activeRoomStore: activeRoomStore
});
var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
feedbackClient: feedbackClient
});
var textChatStore = new loop.store.TextChatStore(dispatcher, {
sdkDriver: sdkDriver
});
@ -141,7 +160,6 @@ loop.conversation = (function(mozL10n) {
loop.store.StoreMixin.register({
conversationAppStore: conversationAppStore,
conversationStore: conversationStore,
feedbackStore: feedbackStore,
textChatStore: textChatStore
});

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

@ -27,14 +27,26 @@ loop.store.ConversationAppStore = (function() {
this._dispatcher = options.dispatcher;
this._mozLoop = options.mozLoop;
this._storeState = {};
this._storeState = this.getInitialStoreState();
this._dispatcher.register(this, [
"getWindowData"
"getWindowData",
"showFeedbackForm"
]);
};
ConversationAppStore.prototype = _.extend({
getInitialStoreState: function() {
return {
// How often to display the form. Convert seconds to ms.
feedbackPeriod: this._mozLoop.getLoopPref("feedback.periodSec") * 1000,
// Date when the feedback form was last presented. Convert to ms.
feedbackTimestamp: this._mozLoop
.getLoopPref("feedback.dateLastSeenSec") * 1000,
showFeedbackForm: false
};
},
/**
* Retrieves current store state.
*
@ -54,6 +66,20 @@ loop.store.ConversationAppStore = (function() {
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() {
var timestamp = Math.floor(new Date().getTime() / 1000);
this._mozLoop.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.

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

@ -15,7 +15,6 @@ loop.conversationViews = (function(mozL10n) {
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views;
var sharedMixins = loop.shared.mixins;
var sharedModels = loop.shared.models;
// This duplicates a similar function in contacts.jsx that isn't used in the
// conversation window. If we get too many of these, we might want to consider
@ -701,7 +700,8 @@ loop.conversationViews = (function(mozL10n) {
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
mozLoop: React.PropTypes.object.isRequired
mozLoop: React.PropTypes.object.isRequired,
onCallTerminated: React.PropTypes.func.isRequired
},
getInitialState: function() {
@ -720,19 +720,6 @@ loop.conversationViews = (function(mozL10n) {
this.state.callState !== CALL_STATES.GATHER;
},
/**
* Used to setup and render the feedback view.
*/
_renderFeedbackView: function() {
this.setTitle(mozL10n.get("conversation_has_ended"));
return (
React.createElement(sharedViews.FeedbackView, {
onAfterFeedbackReceived: this._closeWindow.bind(this)}
)
);
},
_renderViewFromCallType: function() {
// For outgoing calls we can display the pending conversation view
// for any state that render() doesn't manage.
@ -760,6 +747,14 @@ loop.conversationViews = (function(mozL10n) {
return null;
},
componentDidUpdate: function(prevProps, prevState) {
// Handle timestamp and window closing only when the call has terminated.
if (prevState.callState === CALL_STATES.ONGOING &&
this.state.callState === CALL_STATES.FINISHED) {
this.props.onCallTerminated();
}
},
render: function() {
// Set the default title to the contact name or the callerId, note
// that views may override this, e.g. the feedback view.
@ -792,7 +787,10 @@ loop.conversationViews = (function(mozL10n) {
}
case CALL_STATES.FINISHED: {
this.play("terminated");
return this._renderFeedbackView();
// When conversation ended we either display a feedback form or
// close the window. This is decided in the AppControllerView.
return null;
}
case CALL_STATES.INIT: {
// We know what we are, but we haven't got the data yet.

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

@ -15,7 +15,6 @@ loop.conversationViews = (function(mozL10n) {
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views;
var sharedMixins = loop.shared.mixins;
var sharedModels = loop.shared.models;
// This duplicates a similar function in contacts.jsx that isn't used in the
// conversation window. If we get too many of these, we might want to consider
@ -701,7 +700,8 @@ loop.conversationViews = (function(mozL10n) {
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
mozLoop: React.PropTypes.object.isRequired
mozLoop: React.PropTypes.object.isRequired,
onCallTerminated: React.PropTypes.func.isRequired
},
getInitialState: function() {
@ -720,19 +720,6 @@ loop.conversationViews = (function(mozL10n) {
this.state.callState !== CALL_STATES.GATHER;
},
/**
* Used to setup and render the feedback view.
*/
_renderFeedbackView: function() {
this.setTitle(mozL10n.get("conversation_has_ended"));
return (
<sharedViews.FeedbackView
onAfterFeedbackReceived={this._closeWindow.bind(this)}
/>
);
},
_renderViewFromCallType: function() {
// For outgoing calls we can display the pending conversation view
// for any state that render() doesn't manage.
@ -760,6 +747,14 @@ loop.conversationViews = (function(mozL10n) {
return null;
},
componentDidUpdate: function(prevProps, prevState) {
// Handle timestamp and window closing only when the call has terminated.
if (prevState.callState === CALL_STATES.ONGOING &&
this.state.callState === CALL_STATES.FINISHED) {
this.props.onCallTerminated();
}
},
render: function() {
// Set the default title to the contact name or the callerId, note
// that views may override this, e.g. the feedback view.
@ -792,7 +787,10 @@ loop.conversationViews = (function(mozL10n) {
}
case CALL_STATES.FINISHED: {
this.play("terminated");
return this._renderFeedbackView();
// When conversation ended we either display a feedback form or
// close the window. This is decided in the AppControllerView.
return null;
}
case CALL_STATES.INIT: {
// We know what we are, but we haven't got the data yet.

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

@ -0,0 +1,47 @@
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: {
mozLoop: React.PropTypes.object.isRequired,
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() {
var url = this.props.mozLoop.getLoopPref("feedback.formURL");
this.props.mozLoop.openURL(url);
this.props.onAfterFeedbackReceived();
},
render: function() {
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);

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

@ -0,0 +1,47 @@
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({
propTypes: {
mozLoop: React.PropTypes.object.isRequired,
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() {
var url = this.props.mozLoop.getLoopPref("feedback.formURL");
this.props.mozLoop.openURL(url);
this.props.onAfterFeedbackReceived();
},
render: function() {
return (
<div className="feedback-view-container">
<h2 className="feedback-heading">
{mozL10n.get("feedback_window_heading")}
</h2>
<div className="feedback-hello-logo" />
<div className="feedback-button-container">
<button onClick={this.onFeedbackButtonClick}
ref="feedbackFormBtn">
{mozL10n.get("feedback_request_button")}
</button>
</div>
</div>
);
}
});
return {
FeedbackView: FeedbackView
};
})(_, navigator.mozL10n || document.mozL10n);

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

@ -558,6 +558,7 @@ loop.roomViews = (function(mozL10n) {
// The poster URLs are for UI-showcase testing and development.
localPosterUrl: React.PropTypes.string,
mozLoop: React.PropTypes.object.isRequired,
onCallTerminated: React.PropTypes.func.isRequired,
remotePosterUrl: React.PropTypes.string,
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired
},
@ -693,6 +694,14 @@ loop.roomViews = (function(mozL10n) {
this.setState({ showEditContext: false });
},
componentDidUpdate: function(prevProps, prevState) {
// Handle timestamp and window closing only when the call has terminated.
if (prevState.roomState === ROOM_STATES.ENDED &&
this.state.roomState === ROOM_STATES.ENDED) {
this.props.onCallTerminated();
}
},
render: function() {
if (this.state.roomName) {
this.setTitle(this.state.roomName);
@ -726,10 +735,9 @@ loop.roomViews = (function(mozL10n) {
);
}
case ROOM_STATES.ENDED: {
return (
React.createElement(sharedViews.FeedbackView, {
onAfterFeedbackReceived: this.closeWindow})
);
// When conversation ended we either display a feedback form or
// close the window. This is decided in the AppControllerView.
return null;
}
default: {

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

@ -558,6 +558,7 @@ loop.roomViews = (function(mozL10n) {
// The poster URLs are for UI-showcase testing and development.
localPosterUrl: React.PropTypes.string,
mozLoop: React.PropTypes.object.isRequired,
onCallTerminated: React.PropTypes.func.isRequired,
remotePosterUrl: React.PropTypes.string,
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired
},
@ -693,6 +694,14 @@ loop.roomViews = (function(mozL10n) {
this.setState({ showEditContext: false });
},
componentDidUpdate: function(prevProps, prevState) {
// Handle timestamp and window closing only when the call has terminated.
if (prevState.roomState === ROOM_STATES.ENDED &&
this.state.roomState === ROOM_STATES.ENDED) {
this.props.onCallTerminated();
}
},
render: function() {
if (this.state.roomName) {
this.setTitle(this.state.roomName);
@ -726,10 +735,9 @@ loop.roomViews = (function(mozL10n) {
);
}
case ROOM_STATES.ENDED: {
return (
<sharedViews.FeedbackView
onAfterFeedbackReceived={this.closeWindow} />
);
// When conversation ended we either display a feedback form or
// close the window. This is decided in the AppControllerView.
return null;
}
default: {

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

@ -536,7 +536,7 @@ html[dir="rtl"] .context-content {
width: 16px;
max-height: 16px;
margin-right: .8em;
flex: 0 1 auto;
flex: 0 0 auto;
}
html[dir="rtl"] .context-wrapper > .context-preview {

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

@ -423,56 +423,47 @@
}
/* Feedback form */
.feedback {
padding: 14px;
}
.feedback p {
margin: 0px;
}
.feedback h3 {
color: #666;
font-size: 12px;
font-weight: 700;
text-align: center;
margin: 0 0 1em 0;
}
.feedback .faces {
.feedback-view-container {
display: flex;
flex-direction: row;
align-items: center;
flex-direction: column;
flex-wrap: nowrap;
justify-content: center;
padding: 20px 0;
align-content: center;
align-items: flex-start;
height: 100%;
}
.feedback .face {
border: 1px solid transparent;
box-shadow: 0 1px 2px #CCC;
cursor: pointer;
border-radius: 4px;
margin: 0 10px;
width: 80px;
height: 80px;
background-color: #fbfbfb;
background-size: 60px auto;
background-position: center center;
.feedback-heading {
margin: 1em 0;
width: 100%;
text-align: center;
font-weight: bold;
font-size: 1.2em;
}
.feedback-hello-logo {
background-image: url("../img/helloicon.svg");
background-position: center;
background-size: contain;
background-repeat: no-repeat;
flex: 2 1 auto;
width: 100%;
margin: 30px 0;
}
.feedback .face:hover {
border: 1px solid #DDD;
background-color: #FEFEFE;
.feedback-button-container {
flex: 0 1 auto;
margin: 30px;
align-self: center;
}
.feedback .face.face-happy {
background-image: url("../img/happy.png");
}
.feedback .face.face-sad {
background-image: url("../img/sad.png");
.feedback-button-container button {
margin: 0 30px;
padding: .5em 2em;
border: none;
background: #4E92DF;
color: #fff;
cursor: pointer;
}
.fx-embedded-btn-back {
@ -1547,7 +1538,6 @@ html[dir="rtl"] .text-chat-entry.received > p {
color: #aaa;
font-style: italic;
font-size: .8em;
order: 0;
flex: 0 1 auto;
align-self: center;
}
@ -1561,10 +1551,6 @@ html[dir="rtl"] .text-chat-entry.received > p {
order: 2;
}
.sent > .text-chat-entry-timestamp {
order: 0;
}
/* Pseudo element used to cover part between chat bubble and chat arrow. */
.text-chat-entry > p:after {
position: absolute;
@ -1631,7 +1617,6 @@ html[dir="rtl"] .text-chat-entry.received > p:after {
margin-right: -9px;
height: 10px;
background-image: url("../img/chatbubble-arrow-left.svg");
order: 0;
align-self: auto;
}

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

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill-rule="evenodd" clip-rule="evenodd" fill="#4E92DF" d="M32 0C14.3 0 0 12.6 0 28.1c0 7.7 3.6 14.7 9.3 19.8-1 3.5-3 8.3-6.9 12.9.7 1.2 11.7-3 19.4-6.1 3.2.9 6.6 1.5 10.2 1.5 17.7 0 32-12.6 32-28.1S49.7 0 32 0zm9.6 16.9c2.3 0 4.2 1.9 4.2 4.2 0 2.3-1.9 4.2-4.2 4.2-2.3 0-4.2-1.9-4.2-4.2-.1-2.3 1.8-4.2 4.2-4.2zm-19.3 0c2.3 0 4.2 1.9 4.2 4.2 0 2.3-1.9 4.2-4.2 4.2-2.3 0-4.2-1.9-4.2-4.2-.1-2.3 1.8-4.2 4.2-4.2zM32 47.7h-.1-.1c-8.6 0-18.1-5.5-20.3-14.9 5.8 2.7 13.8 3.8 20.4 3.8 6.6 0 14.7-1.2 20.4-3.8-2.2 9.3-11.7 14.9-20.3 14.9z"/></svg>

После

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

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

@ -520,19 +520,18 @@ loop.shared.actions = (function() {
expires: Number
}),
/**
* Used to indicate that the feedback cycle is completed and the countdown
* finished.
*/
FeedbackComplete: Action.define("feedbackComplete", {
}),
/**
* Used to indicate the user wishes to leave the room.
*/
LeaveRoom: Action.define("leaveRoom", {
}),
/**
* Signals that the feedback view should be rendered.
*/
ShowFeedbackForm: Action.define("showFeedbackForm", {
}),
/**
* Used to record a link click for metrics purposes.
*/
@ -544,28 +543,6 @@ loop.shared.actions = (function() {
linkInfo: String
}),
/**
* Requires detailed information on sad feedback.
*/
RequireFeedbackDetails: Action.define("requireFeedbackDetails", {
}),
/**
* Send feedback data.
*/
SendFeedback: Action.define("sendFeedback", {
happy: Boolean,
category: String,
description: String
}),
/**
* Reacts on feedback submission error.
*/
SendFeedbackError: Action.define("sendFeedbackError", {
error: Error
}),
/**
* Used to inform of the current session, publisher and connection
* status.

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

@ -1,115 +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/. */
var loop = loop || {};
loop.FeedbackAPIClient = (function($, _) {
"use strict";
/**
* Feedback API client. Sends feedback data to an input.mozilla.com compatible
* API.
*
* @param {String} baseUrl Base API url (required)
* @param {Object} defaults Defaults field values for that client.
*
* Required defaults:
* - {String} product Product name (required)
*
* Optional defaults:
* - {String} platform Platform name, eg. "Windows 8", "Android", "Linux"
* - {String} version Product version, eg. "22b2", "1.1"
* - {String} channel Product channel, eg. "stable", "beta"
* - {String} user_agent eg. Mozilla/5.0 (Mobile; rv:18.0) Gecko/18.0 Firefox/18.0
*
* @link http://fjord.readthedocs.org/en/latest/api.html
*/
function FeedbackAPIClient(baseUrl, defaults) {
this.baseUrl = baseUrl;
if (!this.baseUrl) {
throw new Error("Missing required 'baseUrl' argument.");
}
this.defaults = defaults || {};
// required defaults checks
if (!this.defaults.hasOwnProperty("product")) {
throw new Error("Missing required 'product' default.");
}
}
FeedbackAPIClient.prototype = {
/**
* Supported field names by the feedback API.
* @type {Array}
*/
_supportedFields: ["happy",
"category",
"description",
"product",
"platform",
"version",
"channel",
"user_agent",
"url"],
/**
* Creates a formatted payload object compliant with the Feedback API spec
* against validated field data.
*
* @param {Object} fields Feedback initial values.
* @return {Object} Formatted payload object.
* @throws {Error} If provided values are invalid
*/
_createPayload: function(fields) {
if (typeof fields !== "object") {
throw new Error("Invalid feedback data provided.");
}
Object.keys(fields).forEach(function(name) {
if (this._supportedFields.indexOf(name) === -1) {
throw new Error("Unsupported field " + name);
}
}, this);
// Payload is basically defaults + fields merged in
var payload = _.extend({}, this.defaults, fields);
// Default description field value
if (!fields.description) {
payload.description = (fields.happy ? "Happy" : "Sad") + " User";
}
return payload;
},
/**
* Sends feedback data.
*
* @param {Object} fields Feedback form data.
* @param {Function} cb Callback(err, result)
*/
send: function(fields, cb) {
var req = $.ajax({
url: this.baseUrl,
method: "POST",
contentType: "application/json",
dataType: "json",
data: JSON.stringify(this._createPayload(fields))
});
req.done(function(result) {
console.info("User feedback data have been submitted", result);
cb(null, result);
});
req.fail(function(jqXHR, textStatus, errorThrown) {
var message = "Error posting user feedback data";
var httpError = jqXHR.status + " " + errorThrown;
cb(new Error(message + ": " + httpError + "; " +
(jqXHR.responseJSON && jqXHR.responseJSON.detail || "")));
});
}
};
return FeedbackAPIClient;
})(jQuery, _);

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

@ -1,105 +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/. */
var loop = loop || {};
loop.store = loop.store || {};
loop.store.FeedbackStore = (function() {
"use strict";
var sharedActions = loop.shared.actions;
var FEEDBACK_STATES = loop.store.FEEDBACK_STATES = {
// Initial state (mood selection)
INIT: "feedback-init",
// User detailed feedback form step
DETAILS: "feedback-details",
// Pending feedback data submission
PENDING: "feedback-pending",
// Feedback has been sent
SENT: "feedback-sent",
// There was an issue with the feedback API
FAILED: "feedback-failed"
};
/**
* Feedback store.
*
* @param {loop.Dispatcher} dispatcher The dispatcher for dispatching actions
* and registering to consume actions.
* @param {Object} options Options object:
* - {mozLoop} mozLoop The MozLoop API object.
* - {feedbackClient} loop.FeedbackAPIClient The feedback API client.
*/
var FeedbackStore = loop.store.createStore({
actions: [
"requireFeedbackDetails",
"sendFeedback",
"sendFeedbackError",
"feedbackComplete"
],
initialize: function(options) {
if (!options.feedbackClient) {
throw new Error("Missing option feedbackClient");
}
this._feedbackClient = options.feedbackClient;
},
/**
* Returns initial state data for this active room.
*/
getInitialStoreState: function() {
return {feedbackState: FEEDBACK_STATES.INIT};
},
/**
* Requires user detailed feedback.
*/
requireFeedbackDetails: function() {
this.setStoreState({feedbackState: FEEDBACK_STATES.DETAILS});
},
/**
* Sends feedback data to the feedback server.
*
* @param {sharedActions.SendFeedback} actionData The action data.
*/
sendFeedback: function(actionData) {
delete actionData.name;
this._feedbackClient.send(actionData, function(err) {
if (err) {
this.dispatchAction(new sharedActions.SendFeedbackError({
error: err
}));
return;
}
this.setStoreState({feedbackState: FEEDBACK_STATES.SENT});
}.bind(this));
this.setStoreState({feedbackState: FEEDBACK_STATES.PENDING});
},
/**
* Notifies a store from any error encountered while sending feedback data.
*
* @param {sharedActions.SendFeedback} actionData The action data.
*/
sendFeedbackError: function(actionData) {
this.setStoreState({
feedbackState: FEEDBACK_STATES.FAILED,
error: actionData.error
});
},
/**
* Resets the store to its initial state as feedback has been completed,
* i.e. ready for the next round of feedback.
*/
feedbackComplete: function() {
this.resetStoreState();
}
});
return FeedbackStore;
})();

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

@ -1,323 +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/. */
var loop = loop || {};
loop.shared = loop.shared || {};
loop.shared.views = loop.shared.views || {};
loop.shared.views.FeedbackView = (function(l10n) {
"use strict";
var sharedActions = loop.shared.actions;
var sharedMixins = loop.shared.mixins;
var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS =
loop.shared.views.WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
/**
* Feedback outer layout.
*
* Props:
* -
*/
var FeedbackLayout = React.createClass({displayName: "FeedbackLayout",
propTypes: {
children: React.PropTypes.element,
reset: React.PropTypes.func, // if not specified, no Back btn is shown
title: React.PropTypes.string.isRequired
},
render: function() {
var backButton = React.createElement("div", null);
if (this.props.reset) {
backButton = (
React.createElement("button", {className: "fx-embedded-btn-back",
onClick: this.props.reset,
type: "button"},
"« ", l10n.get("feedback_back_button")
)
);
}
return (
React.createElement("div", {className: "feedback"},
backButton,
React.createElement("h3", null, this.props.title),
this.props.children
)
);
}
});
/**
* Detailed feedback form.
*/
var FeedbackForm = React.createClass({displayName: "FeedbackForm",
propTypes: {
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore),
pending: React.PropTypes.bool,
reset: React.PropTypes.func
},
getInitialState: function() {
return {category: "", description: ""};
},
getDefaultProps: function() {
return {pending: false};
},
_getCategories: function() {
return {
audio_quality: l10n.get("feedback_category_audio_quality"),
video_quality: l10n.get("feedback_category_video_quality"),
disconnected: l10n.get("feedback_category_was_disconnected"),
confusing: l10n.get("feedback_category_confusing2"),
other: l10n.get("feedback_category_other2")
};
},
_getCategoryFields: function() {
var categories = this._getCategories();
return Object.keys(categories).map(function(category, key) {
return (
React.createElement("label", {className: "feedback-category-label", key: key},
React.createElement("input", {
checked: this.state.category === category,
className: "feedback-category-radio",
name: "category",
onChange: this.handleCategoryChange,
ref: "category",
type: "radio",
value: category}),
categories[category]
)
);
}, this);
},
/**
* Checks if the form is ready for submission:
*
* - no feedback submission should be pending.
* - a category (reason) must be chosen;
* - if the "other" category is chosen, a custom description must have been
* entered by the end user;
*
* @return {Boolean}
*/
_isFormReady: function() {
if (this.props.pending || !this.state.category) {
return false;
}
if (this.state.category === "other" && !this.state.description) {
return false;
}
return true;
},
handleCategoryChange: function(event) {
var category = event.target.value;
this.setState({
category: category
});
if (category == "other") {
this.refs.description.getDOMNode().focus();
}
},
handleDescriptionFieldChange: function(event) {
this.setState({description: event.target.value});
},
handleFormSubmit: function(event) {
event.preventDefault();
// XXX this feels ugly, we really want a feedbackActions object here.
this.props.feedbackStore.dispatchAction(new sharedActions.SendFeedback({
happy: false,
category: this.state.category,
description: this.state.description
}));
},
render: function() {
return (
React.createElement(FeedbackLayout, {
reset: this.props.reset,
title: l10n.get("feedback_category_list_heading")},
React.createElement("form", {onSubmit: this.handleFormSubmit},
this._getCategoryFields(),
React.createElement("p", null,
React.createElement("input", {className: "feedback-description",
name: "description",
onChange: this.handleDescriptionFieldChange,
placeholder:
l10n.get("feedback_custom_category_text_placeholder"),
ref: "description",
type: "text",
value: this.state.description})
),
React.createElement("button", {className: "btn btn-success",
disabled: !this._isFormReady(),
type: "submit"},
l10n.get("feedback_submit_button")
)
)
)
);
}
});
/**
* Feedback received view.
*
* Props:
* - {Function} onAfterFeedbackReceived Function to execute after the
* WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS timeout has elapsed
*/
var FeedbackReceived = React.createClass({displayName: "FeedbackReceived",
propTypes: {
noCloseText: React.PropTypes.bool,
onAfterFeedbackReceived: React.PropTypes.func
},
getInitialState: function() {
return {countdown: WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS};
},
componentDidMount: function() {
this._timer = setInterval(function() {
if (this.state.countdown == 1) {
clearInterval(this._timer);
if (this.props.onAfterFeedbackReceived) {
this.props.onAfterFeedbackReceived();
}
return;
}
this.setState({countdown: this.state.countdown - 1});
}.bind(this), 1000);
},
componentWillUnmount: function() {
if (this._timer) {
clearInterval(this._timer);
}
},
_renderCloseText: function() {
if (this.props.noCloseText) {
return null;
}
return (
React.createElement("p", {className: "info thank-you"},
l10n.get("feedback_window_will_close_in2", {
countdown: this.state.countdown,
num: this.state.countdown
}))
);
},
render: function() {
return (
React.createElement(FeedbackLayout, {title: l10n.get("feedback_thank_you_heading")},
this._renderCloseText()
)
);
}
});
/**
* Feedback view.
*/
var FeedbackView = React.createClass({displayName: "FeedbackView",
mixins: [
Backbone.Events,
loop.store.StoreMixin("feedbackStore")
],
propTypes: {
// Used by the UI showcase.
feedbackState: React.PropTypes.string,
noCloseText: React.PropTypes.bool,
onAfterFeedbackReceived: React.PropTypes.func
},
getInitialState: function() {
var storeState = this.getStoreState();
return _.extend({}, storeState, {
feedbackState: this.props.feedbackState || storeState.feedbackState
});
},
reset: function() {
this.setState(this.getStore().getInitialStoreState());
},
handleHappyClick: function() {
// XXX: If the user is happy, we directly send this information to the
// feedback API; this is a behavior we might want to revisit later.
this.getStore().dispatchAction(new sharedActions.SendFeedback({
happy: true,
category: "",
description: ""
}));
},
handleSadClick: function() {
this.getStore().dispatchAction(
new sharedActions.RequireFeedbackDetails());
},
_onFeedbackSent: function(err) {
if (err) {
// XXX better end user error reporting, see bug 1046738
console.error("Unable to send user feedback", err);
}
this.setState({pending: false, step: "finished"});
},
render: function() {
switch(this.state.feedbackState) {
default:
case FEEDBACK_STATES.INIT: {
return (
React.createElement(FeedbackLayout, {title:
l10n.get("feedback_call_experience_heading2")},
React.createElement("div", {className: "faces"},
React.createElement("button", {className: "face face-happy",
onClick: this.handleHappyClick}),
React.createElement("button", {className: "face face-sad",
onClick: this.handleSadClick})
)
)
);
}
case FEEDBACK_STATES.DETAILS: {
return (
React.createElement(FeedbackForm, {
feedbackStore: this.getStore(),
pending: this.state.feedbackState === FEEDBACK_STATES.PENDING,
reset: this.reset})
);
}
case FEEDBACK_STATES.PENDING:
case FEEDBACK_STATES.SENT:
case FEEDBACK_STATES.FAILED: {
if (this.state.error) {
// XXX better end user error reporting, see bug 1046738
console.error("Error encountered while submitting feedback",
this.state.error);
}
return (
React.createElement(FeedbackReceived, {
noCloseText: this.props.noCloseText,
onAfterFeedbackReceived: this.props.onAfterFeedbackReceived})
);
}
}
}
});
return FeedbackView;
})(navigator.mozL10n || document.mozL10n);

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

@ -1,323 +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/. */
var loop = loop || {};
loop.shared = loop.shared || {};
loop.shared.views = loop.shared.views || {};
loop.shared.views.FeedbackView = (function(l10n) {
"use strict";
var sharedActions = loop.shared.actions;
var sharedMixins = loop.shared.mixins;
var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS =
loop.shared.views.WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
/**
* Feedback outer layout.
*
* Props:
* -
*/
var FeedbackLayout = React.createClass({
propTypes: {
children: React.PropTypes.element,
reset: React.PropTypes.func, // if not specified, no Back btn is shown
title: React.PropTypes.string.isRequired
},
render: function() {
var backButton = <div />;
if (this.props.reset) {
backButton = (
<button className="fx-embedded-btn-back"
onClick={this.props.reset}
type="button" >
&laquo;&nbsp;{l10n.get("feedback_back_button")}
</button>
);
}
return (
<div className="feedback">
{backButton}
<h3>{this.props.title}</h3>
{this.props.children}
</div>
);
}
});
/**
* Detailed feedback form.
*/
var FeedbackForm = React.createClass({
propTypes: {
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore),
pending: React.PropTypes.bool,
reset: React.PropTypes.func
},
getInitialState: function() {
return {category: "", description: ""};
},
getDefaultProps: function() {
return {pending: false};
},
_getCategories: function() {
return {
audio_quality: l10n.get("feedback_category_audio_quality"),
video_quality: l10n.get("feedback_category_video_quality"),
disconnected: l10n.get("feedback_category_was_disconnected"),
confusing: l10n.get("feedback_category_confusing2"),
other: l10n.get("feedback_category_other2")
};
},
_getCategoryFields: function() {
var categories = this._getCategories();
return Object.keys(categories).map(function(category, key) {
return (
<label className="feedback-category-label" key={key}>
<input
checked={this.state.category === category}
className="feedback-category-radio"
name="category"
onChange={this.handleCategoryChange}
ref="category"
type="radio"
value={category} />
{categories[category]}
</label>
);
}, this);
},
/**
* Checks if the form is ready for submission:
*
* - no feedback submission should be pending.
* - a category (reason) must be chosen;
* - if the "other" category is chosen, a custom description must have been
* entered by the end user;
*
* @return {Boolean}
*/
_isFormReady: function() {
if (this.props.pending || !this.state.category) {
return false;
}
if (this.state.category === "other" && !this.state.description) {
return false;
}
return true;
},
handleCategoryChange: function(event) {
var category = event.target.value;
this.setState({
category: category
});
if (category == "other") {
this.refs.description.getDOMNode().focus();
}
},
handleDescriptionFieldChange: function(event) {
this.setState({description: event.target.value});
},
handleFormSubmit: function(event) {
event.preventDefault();
// XXX this feels ugly, we really want a feedbackActions object here.
this.props.feedbackStore.dispatchAction(new sharedActions.SendFeedback({
happy: false,
category: this.state.category,
description: this.state.description
}));
},
render: function() {
return (
<FeedbackLayout
reset={this.props.reset}
title={l10n.get("feedback_category_list_heading")}>
<form onSubmit={this.handleFormSubmit}>
{this._getCategoryFields()}
<p>
<input className="feedback-description"
name="description"
onChange={this.handleDescriptionFieldChange}
placeholder={
l10n.get("feedback_custom_category_text_placeholder")}
ref="description"
type="text"
value={this.state.description} />
</p>
<button className="btn btn-success"
disabled={!this._isFormReady()}
type="submit">
{l10n.get("feedback_submit_button")}
</button>
</form>
</FeedbackLayout>
);
}
});
/**
* Feedback received view.
*
* Props:
* - {Function} onAfterFeedbackReceived Function to execute after the
* WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS timeout has elapsed
*/
var FeedbackReceived = React.createClass({
propTypes: {
noCloseText: React.PropTypes.bool,
onAfterFeedbackReceived: React.PropTypes.func
},
getInitialState: function() {
return {countdown: WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS};
},
componentDidMount: function() {
this._timer = setInterval(function() {
if (this.state.countdown == 1) {
clearInterval(this._timer);
if (this.props.onAfterFeedbackReceived) {
this.props.onAfterFeedbackReceived();
}
return;
}
this.setState({countdown: this.state.countdown - 1});
}.bind(this), 1000);
},
componentWillUnmount: function() {
if (this._timer) {
clearInterval(this._timer);
}
},
_renderCloseText: function() {
if (this.props.noCloseText) {
return null;
}
return (
<p className="info thank-you">{
l10n.get("feedback_window_will_close_in2", {
countdown: this.state.countdown,
num: this.state.countdown
})}</p>
);
},
render: function() {
return (
<FeedbackLayout title={l10n.get("feedback_thank_you_heading")}>
{this._renderCloseText()}
</FeedbackLayout>
);
}
});
/**
* Feedback view.
*/
var FeedbackView = React.createClass({
mixins: [
Backbone.Events,
loop.store.StoreMixin("feedbackStore")
],
propTypes: {
// Used by the UI showcase.
feedbackState: React.PropTypes.string,
noCloseText: React.PropTypes.bool,
onAfterFeedbackReceived: React.PropTypes.func
},
getInitialState: function() {
var storeState = this.getStoreState();
return _.extend({}, storeState, {
feedbackState: this.props.feedbackState || storeState.feedbackState
});
},
reset: function() {
this.setState(this.getStore().getInitialStoreState());
},
handleHappyClick: function() {
// XXX: If the user is happy, we directly send this information to the
// feedback API; this is a behavior we might want to revisit later.
this.getStore().dispatchAction(new sharedActions.SendFeedback({
happy: true,
category: "",
description: ""
}));
},
handleSadClick: function() {
this.getStore().dispatchAction(
new sharedActions.RequireFeedbackDetails());
},
_onFeedbackSent: function(err) {
if (err) {
// XXX better end user error reporting, see bug 1046738
console.error("Unable to send user feedback", err);
}
this.setState({pending: false, step: "finished"});
},
render: function() {
switch(this.state.feedbackState) {
default:
case FEEDBACK_STATES.INIT: {
return (
<FeedbackLayout title={
l10n.get("feedback_call_experience_heading2")}>
<div className="faces">
<button className="face face-happy"
onClick={this.handleHappyClick}></button>
<button className="face face-sad"
onClick={this.handleSadClick}></button>
</div>
</FeedbackLayout>
);
}
case FEEDBACK_STATES.DETAILS: {
return (
<FeedbackForm
feedbackStore={this.getStore()}
pending={this.state.feedbackState === FEEDBACK_STATES.PENDING}
reset={this.reset} />
);
}
case FEEDBACK_STATES.PENDING:
case FEEDBACK_STATES.SENT:
case FEEDBACK_STATES.FAILED: {
if (this.state.error) {
// XXX better end user error reporting, see bug 1046738
console.error("Error encountered while submitting feedback",
this.state.error);
}
return (
<FeedbackReceived
noCloseText={this.props.noCloseText}
onAfterFeedbackReceived={this.props.onAfterFeedbackReceived} />
);
}
}
}
});
return FeedbackView;
})(navigator.mozL10n || document.mozL10n);

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

@ -22,7 +22,6 @@ loop.shared.mixins = (function() {
* @param {Object}
*/
function setRootObject(obj) {
// console.log("loop.shared.mixins: rootObject set to " + obj);
rootObject = obj;
}

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

@ -20,6 +20,7 @@ browser.jar:
content/browser/loop/js/conversationViews.js (content/js/conversationViews.js)
content/browser/loop/js/roomStore.js (content/js/roomStore.js)
content/browser/loop/js/roomViews.js (content/js/roomViews.js)
content/browser/loop/js/feedbackViews.js (content/js/feedbackViews.js)
# Desktop styles
content/browser/loop/css/contacts.css (content/css/contacts.css)
@ -31,8 +32,7 @@ browser.jar:
content/browser/loop/shared/css/conversation.css (content/shared/css/conversation.css)
# Shared images
content/browser/loop/shared/img/happy.png (content/shared/img/happy.png)
content/browser/loop/shared/img/sad.png (content/shared/img/sad.png)
content/browser/loop/shared/img/helloicon.svg (content/shared/img/helloicon.svg)
content/browser/loop/shared/img/icon_32.png (content/shared/img/icon_32.png)
content/browser/loop/shared/img/icon_64.png (content/shared/img/icon_64.png)
content/browser/loop/shared/img/spinner.svg (content/shared/img/spinner.svg)
@ -81,14 +81,11 @@ browser.jar:
content/browser/loop/shared/js/roomStates.js (content/shared/js/roomStates.js)
content/browser/loop/shared/js/fxOSActiveRoomStore.js (content/shared/js/fxOSActiveRoomStore.js)
content/browser/loop/shared/js/activeRoomStore.js (content/shared/js/activeRoomStore.js)
content/browser/loop/shared/js/feedbackStore.js (content/shared/js/feedbackStore.js)
content/browser/loop/shared/js/dispatcher.js (content/shared/js/dispatcher.js)
content/browser/loop/shared/js/feedbackApiClient.js (content/shared/js/feedbackApiClient.js)
content/browser/loop/shared/js/models.js (content/shared/js/models.js)
content/browser/loop/shared/js/mixins.js (content/shared/js/mixins.js)
content/browser/loop/shared/js/otSdkDriver.js (content/shared/js/otSdkDriver.js)
content/browser/loop/shared/js/views.js (content/shared/js/views.js)
content/browser/loop/shared/js/feedbackViews.js (content/shared/js/feedbackViews.js)
content/browser/loop/shared/js/textChatStore.js (content/shared/js/textChatStore.js)
content/browser/loop/shared/js/textChatView.js (content/shared/js/textChatView.js)
content/browser/loop/shared/js/utils.js (content/shared/js/utils.js)

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

@ -337,28 +337,6 @@ p.standalone-btn-label {
text-align: start;
}
.standalone .ended-conversation .feedback {
position: absolute;
width: 50%;
max-width: 400px;
margin: 10px auto;
top: 20px;
left: 10%;
right: 10%;
background: #FFF;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.4);
border-radius: 3px;
z-index: 1002; /* ensures the form is always on top of the control bar */
}
.standalone .room-conversation-wrapper .ended-conversation .feedback {
right: 35%;
}
html[dir="rtl"] .standalone .room-conversation-wrapper .ended-conversation .feedback {
right: auto;
left: 35%;
}
.standalone .ended-conversation .local-stream {
/* Hide local media stream when feedback form is shown. */
display: none;

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

@ -134,7 +134,6 @@
<script type="text/javascript" src="shared/js/crypto.js"></script>
<script type="text/javascript" src="shared/js/models.js"></script>
<script type="text/javascript" src="shared/js/mixins.js"></script>
<script type="text/javascript" src="shared/js/feedbackApiClient.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>
@ -144,7 +143,6 @@
<script type="text/javascript" src="shared/js/roomStates.js"></script>
<script type="text/javascript" src="shared/js/fxOSActiveRoomStore.js"></script>
<script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
<script type="text/javascript" src="shared/js/feedbackStore.js"></script>
<script type="text/javascript" src="shared/js/views.js"></script>
<script type="text/javascript" src="shared/js/feedbackViews.js"></script>
<script type="text/javascript" src="shared/js/textChatStore.js"></script>

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

@ -90,13 +90,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
roomUsed: React.PropTypes.bool.isRequired
},
onFeedbackSent: function() {
// We pass a tick to prevent React warnings regarding nested updates.
setTimeout(function() {
this.props.activeRoomStore.dispatchAction(new sharedActions.FeedbackComplete());
}.bind(this));
},
_renderCallToActionLink: function() {
if (this.props.isFirefox) {
return (
@ -118,8 +111,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
render: function() {
switch(this.props.roomState) {
case ROOM_STATES.ENDED:
case ROOM_STATES.READY: {
// XXX: In ENDED state, we should rather display the feedback form.
return (
React.createElement("div", {className: "room-inner-info-area"},
React.createElement("button", {className: "btn btn-join btn-info",
@ -172,22 +165,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
)
);
}
case ROOM_STATES.ENDED: {
if (this.props.roomUsed) {
return (
React.createElement("div", {className: "ended-conversation"},
React.createElement(sharedViews.FeedbackView, {
noCloseText: true,
onAfterFeedbackReceived: this.onFeedbackSent})
)
);
}
// In case the room was not used (no one was here), we
// bypass the feedback form.
this.onFeedbackSent();
return null;
}
case ROOM_STATES.FAILED: {
return (
React.createElement(StandaloneRoomFailureView, {

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

@ -90,13 +90,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
roomUsed: React.PropTypes.bool.isRequired
},
onFeedbackSent: function() {
// We pass a tick to prevent React warnings regarding nested updates.
setTimeout(function() {
this.props.activeRoomStore.dispatchAction(new sharedActions.FeedbackComplete());
}.bind(this));
},
_renderCallToActionLink: function() {
if (this.props.isFirefox) {
return (
@ -118,8 +111,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
render: function() {
switch(this.props.roomState) {
case ROOM_STATES.ENDED:
case ROOM_STATES.READY: {
// XXX: In ENDED state, we should rather display the feedback form.
return (
<div className="room-inner-info-area">
<button className="btn btn-join btn-info"
@ -172,22 +165,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
</div>
);
}
case ROOM_STATES.ENDED: {
if (this.props.roomUsed) {
return (
<div className="ended-conversation">
<sharedViews.FeedbackView
noCloseText={true}
onAfterFeedbackReceived={this.onFeedbackSent} />
</div>
);
}
// In case the room was not used (no one was here), we
// bypass the feedback form.
this.onFeedbackSent();
return null;
}
case ROOM_STATES.FAILED: {
return (
<StandaloneRoomFailureView

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

@ -591,8 +591,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
currentStatus: mozL10n.get("status_conversation_ended")});
return (
React.createElement("div", {className: "ended-conversation"},
React.createElement(sharedViews.FeedbackView, {
onAfterFeedbackReceived: this.props.onAfterFeedbackReceived}),
React.createElement(sharedViews.ConversationView, {
audio: {enabled: false, visible: false},
dispatcher: this.props.dispatcher,
@ -684,7 +682,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
},
resetCallStatus: function() {
this.props.dispatcher.dispatch(new sharedActions.FeedbackComplete());
return function() {
this.setState({callStatus: "start"});
}.bind(this);
@ -1024,13 +1021,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
// Older non-flux based items.
var notifications = new sharedModels.NotificationCollection();
var feedbackApiClient = new loop.FeedbackAPIClient(
loop.config.feedbackApiUrl, {
product: loop.config.feedbackProductName,
user_agent: navigator.userAgent,
url: document.location.origin
});
// New flux items.
var dispatcher = new loop.Dispatcher();
var client = new loop.StandaloneClient({
@ -1067,22 +1057,12 @@ loop.webapp = (function($, _, OT, mozL10n) {
sdkDriver: sdkDriver
});
var feedbackClient = new loop.FeedbackAPIClient(
loop.config.feedbackApiUrl, {
product: loop.config.feedbackProductName,
user_agent: navigator.userAgent,
url: document.location.origin
});
// Stores
var standaloneAppStore = new loop.store.StandaloneAppStore({
conversation: conversation,
dispatcher: dispatcher,
sdk: OT
});
var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
feedbackClient: feedbackClient
});
var standaloneMetricsStore = new loop.store.StandaloneMetricsStore(dispatcher, {
activeRoomStore: activeRoomStore
});
@ -1092,7 +1072,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
loop.store.StoreMixin.register({
activeRoomStore: activeRoomStore,
feedbackStore: feedbackStore,
// This isn't used in any views, but is saved here to ensure it
// is kept alive.
standaloneMetricsStore: standaloneMetricsStore,

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

@ -591,8 +591,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
currentStatus: mozL10n.get("status_conversation_ended")});
return (
<div className="ended-conversation">
<sharedViews.FeedbackView
onAfterFeedbackReceived={this.props.onAfterFeedbackReceived} />
<sharedViews.ConversationView
audio={{enabled: false, visible: false}}
dispatcher={this.props.dispatcher}
@ -684,7 +682,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
},
resetCallStatus: function() {
this.props.dispatcher.dispatch(new sharedActions.FeedbackComplete());
return function() {
this.setState({callStatus: "start"});
}.bind(this);
@ -1024,13 +1021,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
// Older non-flux based items.
var notifications = new sharedModels.NotificationCollection();
var feedbackApiClient = new loop.FeedbackAPIClient(
loop.config.feedbackApiUrl, {
product: loop.config.feedbackProductName,
user_agent: navigator.userAgent,
url: document.location.origin
});
// New flux items.
var dispatcher = new loop.Dispatcher();
var client = new loop.StandaloneClient({
@ -1067,22 +1057,12 @@ loop.webapp = (function($, _, OT, mozL10n) {
sdkDriver: sdkDriver
});
var feedbackClient = new loop.FeedbackAPIClient(
loop.config.feedbackApiUrl, {
product: loop.config.feedbackProductName,
user_agent: navigator.userAgent,
url: document.location.origin
});
// Stores
var standaloneAppStore = new loop.store.StandaloneAppStore({
conversation: conversation,
dispatcher: dispatcher,
sdk: OT
});
var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
feedbackClient: feedbackClient
});
var standaloneMetricsStore = new loop.store.StandaloneMetricsStore(dispatcher, {
activeRoomStore: activeRoomStore
});
@ -1092,7 +1072,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
loop.store.StoreMixin.register({
activeRoomStore: activeRoomStore,
feedbackStore: feedbackStore,
// This isn't used in any views, but is saved here to ensure it
// is kept alive.
standaloneMetricsStore: standaloneMetricsStore,

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

@ -73,27 +73,6 @@ call_progress_connecting_description=Connecting…
call_progress_ringing_description=Ringing…
fxos_app_needed=Please install the {{fxosAppName}} app from the Firefox Marketplace.
feedback_call_experience_heading2=How was your conversation?
feedback_thank_you_heading=Thank you for your feedback!
feedback_category_list_heading=What made you sad?
feedback_category_audio_quality=Audio quality
feedback_category_video_quality=Video quality
feedback_category_was_disconnected=Was disconnected
feedback_category_confusing2=Confusing controls
feedback_category_other2=Other
feedback_custom_category_text_placeholder=What went wrong?
feedback_submit_button=Submit
feedback_back_button=Back
## LOCALIZATION NOTE (feedback_window_will_close_in2):
## Gaia l10n format; see https://github.com/mozilla-b2g/gaia/blob/f108c706fae43cd61628babdd9463e7695b2496e/apps/email/locales/email.en-US.properties#L387
## In this item, don't translate the part between {{..}}
feedback_window_will_close_in2={[ plural(countdown) ]}
feedback_window_will_close_in2[one] = This window will close in {{countdown}} second
feedback_window_will_close_in2[two] = This window will close in {{countdown}} seconds
feedback_window_will_close_in2[few] = This window will close in {{countdown}} seconds
feedback_window_will_close_in2[many] = This window will close in {{countdown}} seconds
feedback_window_will_close_in2[other] = This window will close in {{countdown}} seconds
## LOCALIZATION_NOTE (feedback_rejoin_button): Displayed on the feedback form after
## a signed-in to signed-in user call.
## https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#feedback

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

@ -32,7 +32,8 @@ describe("loop.store.ConversationAppStore", function () {
});
describe("#getWindowData", function() {
var fakeWindowData, fakeGetWindowData, fakeMozLoop, store;
var fakeWindowData, fakeGetWindowData, fakeMozLoop, store, getLoopPrefStub;
var setLoopPrefStub;
beforeEach(function() {
fakeWindowData = {
@ -44,13 +45,18 @@ describe("loop.store.ConversationAppStore", function () {
windowId: "42"
};
getLoopPrefStub = sandbox.stub();
setLoopPrefStub = sandbox.stub();
fakeMozLoop = {
getConversationWindowData: function(windowId) {
if (windowId === "42") {
return fakeWindowData;
}
return null;
}
},
getLoopPref: getLoopPrefStub,
setLoopPref: setLoopPrefStub
};
store = new loop.store.ConversationAppStore({
@ -59,6 +65,10 @@ describe("loop.store.ConversationAppStore", function () {
});
});
afterEach(function() {
sandbox.restore();
});
it("should fetch the window type from the mozLoop API", function() {
dispatcher.dispatch(new sharedActions.GetWindowData(fakeGetWindowData));
@ -67,6 +77,52 @@ describe("loop.store.ConversationAppStore", function () {
});
});
it("should have the feedback period in initial state", function() {
getLoopPrefStub.returns(42);
// Expect ms.
expect(store.getInitialStoreState().feedbackPeriod).to.eql(42 * 1000);
});
it("should have the dateLastSeen in initial state", function() {
getLoopPrefStub.returns(42);
// Expect ms.
expect(store.getInitialStoreState().feedbackTimestamp).to.eql(42 * 1000);
});
it("should fetch the correct pref for feedback period", function() {
store.getInitialStoreState();
sinon.assert.calledWithExactly(getLoopPrefStub, "feedback.periodSec");
});
it("should fetch the correct pref for feedback period", function() {
store.getInitialStoreState();
sinon.assert.calledWithExactly(getLoopPrefStub,
"feedback.dateLastSeenSec");
});
it("should set showFeedbackForm to true when action is triggered", function() {
var showFeedbackFormStub = sandbox.stub(store, "showFeedbackForm");
dispatcher.dispatch(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);
dispatcher.dispatch(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 mozLoop API",
function() {
sandbox.stub(dispatcher, "dispatch");
@ -80,5 +136,4 @@ describe("loop.store.ConversationAppStore", function () {
}, fakeWindowData)));
});
});
});

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

@ -10,7 +10,7 @@ describe("loop.conversationViews", function () {
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views;
var sandbox, view, dispatcher, contact, fakeAudioXHR, conversationStore;
var fakeMozLoop, fakeWindow;
var fakeMozLoop, fakeWindow, fakeClock;
var CALL_STATES = loop.store.CALL_STATES;
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
@ -20,7 +20,7 @@ describe("loop.conversationViews", function () {
beforeEach(function() {
sandbox = sinon.sandbox.create();
sandbox.useFakeTimers();
fakeClock = sandbox.useFakeTimers();
sandbox.stub(document.mozL10n, "get", function(x) {
return x;
@ -58,6 +58,7 @@ describe("loop.conversationViews", function () {
},
// Dummy function, stubbed below.
getLoopPref: function() {},
setLoopPref: sandbox.stub(),
calls: {
clearCallInProgress: sinon.stub()
},
@ -95,9 +96,6 @@ describe("loop.conversationViews", function () {
};
loop.shared.mixins.setRootObject(fakeWindow);
var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
feedbackClient: {}
});
conversationStore = new loop.store.ConversationStore(dispatcher, {
client: {},
mozLoop: fakeMozLoop,
@ -105,8 +103,7 @@ describe("loop.conversationViews", function () {
});
loop.store.StoreMixin.register({
conversationStore: conversationStore,
feedbackStore: feedbackStore
conversationStore: conversationStore
});
});
@ -608,20 +605,23 @@ describe("loop.conversationViews", function () {
});
describe("CallControllerView", function() {
var feedbackStore;
var onCallTerminatedStub;
function mountTestComponent() {
return TestUtils.renderIntoDocument(
React.createElement(loop.conversationViews.CallControllerView, {
dispatcher: dispatcher,
mozLoop: fakeMozLoop
mozLoop: fakeMozLoop,
onCallTerminated: onCallTerminatedStub
}));
}
beforeEach(function() {
feedbackStore = new loop.store.FeedbackStore(dispatcher, {
feedbackClient: {}
});
onCallTerminatedStub = sandbox.stub();
});
afterEach(function() {
sandbox.restore();
});
it("should set the document title to the callerId", function() {
@ -704,24 +704,6 @@ describe("loop.conversationViews", function () {
loop.conversationViews.OngoingConversationView);
});
it("should render the FeedbackView when the call state is 'finished'",
function() {
conversationStore.setStoreState({callState: CALL_STATES.FINISHED});
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.shared.views.FeedbackView);
});
it("should set the document title to conversation_has_ended when displaying the feedback view", function() {
conversationStore.setStoreState({callState: CALL_STATES.FINISHED});
mountTestComponent();
expect(fakeWindow.document.title).eql("conversation_has_ended");
});
it("should play the terminated sound when the call state is 'finished'",
function() {
var fakeAudio = {
@ -756,6 +738,21 @@ describe("loop.conversationViews", function () {
TestUtils.findRenderedComponentWithType(view,
loop.conversationViews.CallFailedView);
});
it("should call onCallTerminated when the call is finished", function() {
conversationStore.setStoreState({
callState: CALL_STATES.ONGOING
});
view = mountTestComponent({
callState: CALL_STATES.FINISHED
});
// Force a state change so that it triggers componentDidUpdate.
view.setState({ callState: CALL_STATES.FINISHED });
sinon.assert.calledOnce(onCallTerminatedStub);
sinon.assert.calledWithExactly(onCallTerminatedStub);
});
});
describe("AcceptCallView", function() {

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

@ -6,13 +6,15 @@ describe("loop.conversation", function() {
"use strict";
var expect = chai.expect;
var FeedbackView = loop.feedbackViews.FeedbackView;
var TestUtils = React.addons.TestUtils;
var sharedModels = loop.shared.models,
fakeWindow,
sandbox;
var sharedActions = loop.shared.actions;
var sharedModels = loop.shared.models;
var fakeWindow, sandbox, getLoopPrefStub, setLoopPrefStub, mozL10nGet;
beforeEach(function() {
sandbox = sinon.sandbox.create();
setLoopPrefStub = sandbox.stub();
navigator.mozLoop = {
doNotDisturb: true,
@ -22,7 +24,7 @@ describe("loop.conversation", function() {
get locale() {
return "en-US";
},
setLoopPref: sinon.stub(),
setLoopPref: setLoopPrefStub,
getLoopPref: function(prefName) {
if (prefName == "debug.sdk") {
return false;
@ -63,7 +65,7 @@ describe("loop.conversation", function() {
// XXX These stubs should be hoisted in a common file
// Bug 1040968
sandbox.stub(document.mozL10n, "get", function(x) {
mozL10nGet = sandbox.stub(document.mozL10n, "get", function(x) {
return x;
});
document.mozL10n.initialize(navigator.mozLoop);
@ -132,7 +134,7 @@ describe("loop.conversation", function() {
describe("AppControllerView", function() {
var conversationStore, client, ccView, dispatcher;
var conversationAppStore, roomStore;
var conversationAppStore, roomStore, feedbackPeriodMs = 15770000000;
function mountTestComponent() {
return TestUtils.renderIntoDocument(
@ -215,5 +217,97 @@ describe("loop.conversation", function() {
TestUtils.findRenderedComponentWithType(ccView,
loop.conversationViews.GenericFailureView);
});
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 a ShowFeedbackForm action if timestamp is 0",
function() {
conversationAppStore.setStoreState({feedbackTimestamp: 0});
sandbox.stub(dispatcher, "dispatch");
ccView = mountTestComponent();
ccView.handleCallTerminated();
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ShowFeedbackForm());
});
it("should set feedback timestamp if delta is > feedback period",
function() {
var feedbackTimestamp = new Date() - feedbackPeriodMs;
conversationAppStore.setStoreState({
feedbackTimestamp: feedbackTimestamp,
feedbackPeriod: feedbackPeriodMs
});
ccView = mountTestComponent();
ccView.handleCallTerminated();
sinon.assert.calledOnce(setLoopPrefStub);
});
it("should dispatch a ShowFeedbackForm action if delta > feedback period",
function() {
var feedbackTimestamp = new Date() - feedbackPeriodMs;
conversationAppStore.setStoreState({
feedbackTimestamp: feedbackTimestamp,
feedbackPeriod: feedbackPeriodMs
});
sandbox.stub(dispatcher, "dispatch");
ccView = mountTestComponent();
ccView.handleCallTerminated();
sinon.assert.calledOnce(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();
conversationAppStore.setStoreState({
feedbackTimestamp: feedbackTimestamp,
feedbackPeriod: feedbackPeriodMs
});
ccView = mountTestComponent();
var closeWindowStub = sandbox.stub(ccView, "closeWindow");
ccView.handleCallTerminated();
sinon.assert.calledOnce(closeWindowStub);
});
it("should set the correct timestamp for dateLastSeenSec", function() {
var feedbackTimestamp = new Date().getTime();
conversationAppStore.setStoreState({
feedbackTimestamp: feedbackTimestamp,
feedbackPeriod: feedbackPeriodMs
});
ccView = mountTestComponent();
var closeWindowStub = sandbox.stub(ccView, "closeWindow");
ccView.handleCallTerminated();
sinon.assert.calledOnce(closeWindowStub);
});
});
});

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

@ -0,0 +1,100 @@
/* 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 = sinon.sandbox.create();
mozL10nGet = sandbox.stub(l10n, "get", function(x) {
return "translated:" + x;
});
});
afterEach(function() {
sandbox.restore();
});
describe("FeedbackView", function() {
var openURLStub, getLoopPrefStub, feedbackReceivedStub;
var fakeURL = "fake.form", mozLoop, view;
function mountTestComponent(props) {
props = _.extend({
mozLoop: mozLoop,
onAfterFeedbackReceived: feedbackReceivedStub
}, props);
return TestUtils.renderIntoDocument(
React.createElement(FeedbackView, props));
}
beforeEach(function() {
openURLStub = sandbox.stub();
getLoopPrefStub = sandbox.stub();
feedbackReceivedStub = sandbox.stub();
mozLoop = {
openURL: openURLStub,
getLoopPref: getLoopPrefStub
};
});
afterEach(function() {
view = null;
});
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() {
mozLoop.getLoopPref = sinon.stub().withArgs("feedback.formURL")
.returns(fakeURL);
view = mountTestComponent();
TestUtils.Simulate.click(view.refs.feedbackFormBtn.getDOMNode());
sinon.assert.calledOnce(openURLStub);
sinon.assert.calledWithExactly(openURLStub, fakeURL);
});
it("should fetch the feedback form URL from the prefs", function() {
mozLoop.getLoopPref = sinon.stub().withArgs("feedback.formURL")
.returns(fakeURL);
view = mountTestComponent();
TestUtils.Simulate.click(view.refs.feedbackFormBtn.getDOMNode());
sinon.assert.calledOnce(mozLoop.getLoopPref);
sinon.assert.calledWithExactly(mozLoop.getLoopPref, "feedback.formURL");
});
it("should close the window after opening the form", function() {
view = mountTestComponent();
TestUtils.Simulate.click(view.refs.feedbackFormBtn.getDOMNode());
sinon.assert.calledOnce(feedbackReceivedStub);
});
});
});

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

@ -47,7 +47,6 @@
<!-- App scripts -->
<script src="../../content/shared/js/utils.js"></script>
<script src="../../content/shared/js/feedbackApiClient.js"></script>
<script src="../../content/shared/js/models.js"></script>
<script src="../../content/shared/js/mixins.js"></script>
<script src="../../content/shared/js/websocket.js"></script>
@ -60,16 +59,15 @@
<script src="../../content/shared/js/roomStates.js"></script>
<script src="../../content/shared/js/fxOSActiveRoomStore.js"></script>
<script src="../../content/shared/js/activeRoomStore.js"></script>
<script src="../../content/shared/js/feedbackStore.js"></script>
<script src="../../content/shared/js/views.js"></script>
<script src="../../content/shared/js/textChatStore.js"></script>
<script src="../../content/shared/js/textChatView.js"></script>
<script src="../../content/shared/js/feedbackViews.js"></script>
<script src="../../content/js/client.js"></script>
<script src="../../content/js/conversationAppStore.js"></script>
<script src="../../content/js/roomStore.js"></script>
<script src="../../content/js/roomViews.js"></script>
<script src="../../content/js/conversationViews.js"></script>
<script src="../../content/js/feedbackViews.js"></script>
<script src="../../content/js/conversation.js"></script>
<script type="text/javascript;version=1.8" src="../../content/js/contacts.js"></script>
<script src="../../content/js/panel.js"></script>
@ -78,6 +76,7 @@
<script src="conversationAppStore_test.js"></script>
<script src="client_test.js"></script>
<script src="conversation_test.js"></script>
<script src="feedbackViews_test.js"></script>
<script src="panel_test.js"></script>
<script src="roomViews_test.js"></script>
<script src="conversationViews_test.js"></script>
@ -97,7 +96,7 @@
describe("Unexpected Warnings Check", function() {
it("should long only the warnings we expect", function() {
chai.expect(caughtWarnings.length).to.eql(30);
chai.expect(caughtWarnings.length).to.eql(27);
});
});

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

@ -41,7 +41,8 @@ describe("loop.roomViews", function () {
}),
update: sinon.stub().callsArgWith(2, null)
},
telemetryAddValue: sinon.stub()
telemetryAddValue: sinon.stub(),
setLoopPref: sandbox.stub()
};
fakeWindow = {
@ -201,8 +202,7 @@ describe("loop.roomViews", function () {
});
});
it("should dispatch a CopyRoomUrl action when the copy button is " +
"pressed", function() {
it("should dispatch a CopyRoomUrl action when the copy button is pressed", function() {
var copyBtn = view.getDOMNode().querySelector(".btn-copy");
React.addons.TestUtils.Simulate.click(copyBtn);
@ -290,14 +290,9 @@ describe("loop.roomViews", function () {
});
describe("DesktopRoomConversationView", function() {
var view;
var view, onCallTerminatedStub;
beforeEach(function() {
loop.store.StoreMixin.register({
feedbackStore: new loop.store.FeedbackStore(dispatcher, {
feedbackClient: {}
})
});
sandbox.stub(dispatcher, "dispatch");
fakeMozLoop.getLoopPref = function(prefName) {
if (prefName == "contextInConversations.enabled") {
@ -305,15 +300,18 @@ describe("loop.roomViews", function () {
}
return "test";
};
onCallTerminatedStub = sandbox.stub();
});
function mountTestComponent() {
function mountTestComponent(props) {
props = _.extend({
dispatcher: dispatcher,
roomStore: roomStore,
mozLoop: fakeMozLoop,
onCallTerminated: onCallTerminatedStub
}, props);
return TestUtils.renderIntoDocument(
React.createElement(loop.roomViews.DesktopRoomConversationView, {
dispatcher: dispatcher,
roomStore: roomStore,
mozLoop: fakeMozLoop
}));
React.createElement(loop.roomViews.DesktopRoomConversationView, props));
}
it("should dispatch a setMute action when the audio mute button is pressed",
@ -372,8 +370,7 @@ describe("loop.roomViews", function () {
expect(muteBtn.classList.contains("muted")).eql(true);
});
it("should dispatch a `StartScreenShare` action when sharing is not active " +
"and the screen share button is pressed", function() {
it("should dispatch a `StartScreenShare` action when sharing is not active and the screen share button is pressed", function() {
view = mountTestComponent();
view.setState({screenSharingState: SCREEN_SHARE_STATES.INACTIVE});
@ -419,8 +416,7 @@ describe("loop.roomViews", function () {
sinon.match.instanceOf(sharedActions.SetupStreamElements));
}
it("should dispatch a `SetupStreamElements` action when the MEDIA_WAIT state " +
"is entered", function() {
it("should dispatch a `SetupStreamElements` action when the MEDIA_WAIT state is entered", function() {
activeRoomStore.setStoreState({roomState: ROOM_STATES.READY});
var component = mountTestComponent();
@ -429,8 +425,7 @@ describe("loop.roomViews", function () {
expectActionDispatched(component);
});
it("should dispatch a `SetupStreamElements` action on MEDIA_WAIT state is " +
"re-entered", function() {
it("should dispatch a `SetupStreamElements` action on MEDIA_WAIT state is re-entered", function() {
activeRoomStore.setStoreState({roomState: ROOM_STATES.ENDED});
var component = mountTestComponent();
@ -489,19 +484,19 @@ describe("loop.roomViews", function () {
loop.roomViews.DesktopRoomConversationView);
});
it("should render the FeedbackView if roomState is `ENDED`",
function() {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.ENDED,
used: true
});
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.shared.views.FeedbackView);
it("should call onCallTerminated when the call ended", function() {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.ENDED,
used: true
});
view = mountTestComponent();
// Force a state change so that it triggers componentDidUpdate
view.setState({ foo: "bar" });
sinon.assert.calledOnce(onCallTerminatedStub);
});
it("should display loading spinner when localSrcVideoObject is null",
function() {
activeRoomStore.setStoreState({

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

@ -152,16 +152,12 @@ class Test1BrowserCall(MarionetteTestCase):
self.switch_to_standalone()
self.check_video(".screen-share-video")
def remote_leave_room_and_verify_feedback(self):
def remote_leave_room(self):
self.switch_to_standalone()
button = self.marionette.find_element(By.CLASS_NAME, "btn-hangup")
button.click()
# check that the feedback form is displayed
feedback_form = self.wait_for_element_displayed(By.CLASS_NAME, "faces")
self.assertEqual(feedback_form.tag_name, "div", "expect feedback form")
self.switch_to_chatbox()
# check that the local view reverts to the preview mode
self.wait_for_element_displayed(By.CLASS_NAME, "room-invitation-content")
@ -260,7 +256,7 @@ class Test1BrowserCall(MarionetteTestCase):
# which means that the local_check_connection_length below
# verifies that the connection is noted at the time the remote media
# drops, rather than waiting until the window closes.
self.remote_leave_room_and_verify_feedback()
self.remote_leave_room()
self.local_check_connection_length_noted()

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

@ -3,6 +3,7 @@
"globals": {
// General test items.
"add_task": false,
"BrowserTestUtils": true,
"Cc": true,
"Ci": true,
"Cr": true,
@ -24,7 +25,6 @@
"promiseOAuthGetRegistration": false,
"promiseOAuthParamsSetup": false,
"promiseObserverNotified": false,
"promiseTabLoadEvent": false,
"promiseWaitForCondition": false,
"resetFxA": true,
// Loop specific items

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

@ -23,8 +23,7 @@ add_task(function* test_mozLoop_getSelectedTabMetadata() {
Assert.strictEqual(metadata.title, "", "Title should be empty for about:blank");
Assert.deepEqual(metadata.previews, [], "No previews available for about:blank");
let tab = gBrowser.selectedTab = gBrowser.addTab();
yield promiseTabLoadEvent(tab, "about:home");
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home");
metadata = yield promiseGetMetadata();
Assert.strictEqual(metadata.url, null, "URL should be empty for about:home");
@ -34,12 +33,11 @@ add_task(function* test_mozLoop_getSelectedTabMetadata() {
// elements with chrome:// srcs, which show up as null in metadata.previews.
Assert.deepEqual(metadata.previews.filter(e => e), [], "No previews available for about:home");
gBrowser.removeTab(tab);
yield BrowserTestUtils.removeTab(tab);
});
add_task(function* test_mozLoop_getSelectedTabMetadata_defaultIcon() {
let tab = gBrowser.selectedTab = gBrowser.addTab();
yield promiseTabLoadEvent(tab, "http://example.com/");
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
let metadata = yield promiseGetMetadata();
Assert.strictEqual(metadata.url, "http://example.com/", "URL should match");
@ -47,5 +45,5 @@ add_task(function* test_mozLoop_getSelectedTabMetadata_defaultIcon() {
Assert.ok(metadata.title, "Title should be set");
Assert.deepEqual(metadata.previews, [], "No previews available");
gBrowser.removeTab(tab);
yield BrowserTestUtils.removeTab(tab);
});

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

@ -15,9 +15,9 @@ add_task(loadLoopPanel);
add_task(function* test_mozLoop_pluralStrings() {
Assert.ok(gMozLoopAPI, "mozLoop should exist");
var strings = JSON.parse(gMozLoopAPI.getStrings("feedback_window_will_close_in2"));
Assert.equal(gMozLoopAPI.getPluralForm(0, strings.textContent),
"This window will close in {{countdown}} seconds");
var strings = JSON.parse(gMozLoopAPI.getStrings("import_contacts_success_message"));
Assert.equal(gMozLoopAPI.getPluralForm(1, strings.textContent),
"This window will close in {{countdown}} second");
"{{total}} contact was successfully imported.");
Assert.equal(gMozLoopAPI.getPluralForm(3, strings.textContent),
"{{total}} contacts were successfully imported.");
});

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

@ -46,10 +46,10 @@ function promiseWindowIdReceivedNewTab(handlersParam = []) {
}));
});
let createdTab = gBrowser.selectedTab = gBrowser.addTab();
let createdTab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla");
createdTabs.push(createdTab);
promiseHandlers.push(promiseTabLoadEvent(createdTab, "about:mozilla"));
promiseHandlers.push(BrowserTestUtils.browserLoaded(createdTab.linkedBrowser));
return Promise.all(promiseHandlers);
}

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

@ -207,52 +207,6 @@ function promiseOAuthGetRegistration(baseURL) {
});
}
/**
* Waits for a load (or custom) event to finish in a given tab. If provided
* load an uri into the tab.
*
* @param tab
* The tab to load into.
* @param [optional] url
* The url to load, or the current url.
* @param [optional] event
* The load event type to wait for. Defaults to "load".
* @return {Promise} resolved when the event is handled.
* @resolves to the received event
* @rejects if a valid load event is not received within a meaningful interval
*/
function promiseTabLoadEvent(tab, url, eventType="load") {
return new Promise((resolve, reject) => {
info("Wait tab event: " + eventType);
function handle(event) {
if (event.originalTarget != tab.linkedBrowser.contentDocument ||
event.target.location.href == "about:blank" ||
(url && event.target.location.href != url)) {
info("Skipping spurious '" + eventType + "'' event" +
" for " + event.target.location.href);
return;
}
clearTimeout(timeout);
tab.linkedBrowser.removeEventListener(eventType, handle, true);
info("Tab event received: " + eventType);
resolve(event);
}
let timeout = setTimeout(() => {
if (tab.linkedBrowser) {
tab.linkedBrowser.removeEventListener(eventType, handle, true);
}
reject(new Error("Timed out while waiting for a '" + eventType + "'' event"));
}, 30000);
tab.linkedBrowser.addEventListener(eventType, handle, true, true);
if (url) {
tab.linkedBrowser.loadURI(url);
}
});
}
function getLoopString(stringID) {
return MozLoopServiceInternal.localizedStrings.get(stringID);
}

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

@ -566,30 +566,6 @@ describe("loop.store.ActiveRoomStore", function () {
});
});
describe("#feedbackComplete", function() {
it("should set the room state to READY", function() {
store.setStoreState({
roomState: ROOM_STATES.ENDED,
used: true
});
store.feedbackComplete(new sharedActions.FeedbackComplete());
expect(store.getStoreState().roomState).eql(ROOM_STATES.READY);
});
it("should reset the 'used' state", function() {
store.setStoreState({
roomState: ROOM_STATES.ENDED,
used: true
});
store.feedbackComplete(new sharedActions.FeedbackComplete());
expect(store.getStoreState().used).eql(false);
});
});
describe("#videoDimensionsChanged", function() {
it("should not contain any video dimensions at the very start", function() {
expect(store.getStoreState()).eql(store.getInitialStoreState());

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

@ -1,184 +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/. */
describe("loop.FeedbackAPIClient", function() {
"use strict";
var expect = chai.expect;
var sandbox,
fakeXHR,
requests = [];
beforeEach(function() {
sandbox = sinon.sandbox.create();
fakeXHR = sandbox.useFakeXMLHttpRequest();
requests = [];
// https://github.com/cjohansen/Sinon.JS/issues/393
fakeXHR.xhr.onCreate = function (xhr) {
requests.push(xhr);
};
});
afterEach(function() {
sandbox.restore();
});
describe("#constructor", function() {
it("should require a baseUrl setting", function() {
expect(function() {
return new loop.FeedbackAPIClient();
}).to.Throw(/required 'baseUrl'/);
});
it("should require a product setting", function() {
expect(function() {
return new loop.FeedbackAPIClient("http://fake", {});
}).to.Throw(/required 'product'/);
});
});
describe("constructed", function() {
var client;
beforeEach(function() {
client = new loop.FeedbackAPIClient("http://fake/feedback", {
product: "Hello",
version: "42b1"
});
});
describe("#send", function() {
it("should send happy feedback data", function() {
var feedbackData = {
happy: true,
description: "Happy User"
};
client.send(feedbackData, function(){});
expect(requests).to.have.length.of(1);
expect(requests[0].url).to.be.equal("http://fake/feedback");
expect(requests[0].method).to.be.equal("POST");
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.happy).eql(true);
expect(parsed.description).eql("Happy User");
});
it("should send sad feedback data", function() {
var feedbackData = {
happy: false,
category: "confusing"
};
client.send(feedbackData, function(){});
expect(requests).to.have.length.of(1);
expect(requests[0].url).to.be.equal("http://fake/feedback");
expect(requests[0].method).to.be.equal("POST");
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.happy).eql(false);
expect(parsed.product).eql("Hello");
expect(parsed.category).eql("confusing");
expect(parsed.description).eql("Sad User");
});
it("should send formatted feedback data", function() {
client.send({
happy: false,
category: "other",
description: "it's far too awesome!"
}, function(){});
expect(requests).to.have.length.of(1);
expect(requests[0].url).eql("http://fake/feedback");
expect(requests[0].method).eql("POST");
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.happy).eql(false);
expect(parsed.product).eql("Hello");
expect(parsed.category).eql("other");
expect(parsed.description).eql("it's far too awesome!");
});
it("should send product information", function() {
client.send({product: "Hello"}, function(){});
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.product).eql("Hello");
});
it("should send platform information when provided", function() {
client.send({platform: "Windows 8"}, function(){});
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.platform).eql("Windows 8");
});
it("should send channel information when provided", function() {
client.send({channel: "beta"}, function(){});
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.channel).eql("beta");
});
it("should send version information when provided", function() {
client.send({version: "42b1"}, function(){});
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.version).eql("42b1");
});
it("should send user_agent information when provided", function() {
client.send({user_agent: "MOZAGENT"}, function(){});
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.user_agent).eql("MOZAGENT");
});
it("should send url information when provided", function() {
client.send({url: "http://fake.invalid"}, function(){});
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.url).eql("http://fake.invalid");
});
it("should throw on invalid feedback data", function() {
expect(function() {
client.send("invalid data", function(){});
}).to.Throw(/Invalid/);
});
it("should throw on unsupported field name", function() {
expect(function() {
client.send({bleh: "bah"}, function(){});
}).to.Throw(/Unsupported/);
});
it("should call passed callback on success", function() {
var cb = sandbox.spy();
var fakeResponseData = {description: "confusing"};
client.send({category: "confusing"}, cb);
requests[0].respond(200, {"Content-Type": "application/json"},
JSON.stringify(fakeResponseData));
sinon.assert.calledOnce(cb);
sinon.assert.calledWithExactly(cb, null, fakeResponseData);
});
it("should call passed callback on error", function() {
var cb = sandbox.spy();
var fakeErrorData = {error: true};
client.send({category: "confusing"}, cb);
requests[0].respond(400, {"Content-Type": "application/json"},
JSON.stringify(fakeErrorData));
sinon.assert.calledOnce(cb);
sinon.assert.calledWithExactly(cb, sinon.match(function(err) {
return /Bad Request/.test(err);
}));
});
});
});
});

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

@ -1,121 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
describe("loop.store.FeedbackStore", function () {
"use strict";
var expect = chai.expect;
var sharedActions = loop.shared.actions;
var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
var sandbox, dispatcher, store, feedbackClient;
beforeEach(function() {
sandbox = sinon.sandbox.create();
dispatcher = new loop.Dispatcher();
feedbackClient = new loop.FeedbackAPIClient("http://invalid", {
product: "Loop"
});
store = new loop.store.FeedbackStore(dispatcher, {
feedbackClient: feedbackClient
});
});
afterEach(function() {
sandbox.restore();
});
describe("#constructor", function() {
it("should throw an error if feedbackClient is missing", function() {
expect(function() {
new loop.store.FeedbackStore(dispatcher);
}).to.Throw(/feedbackClient/);
});
it("should set the store to the INIT feedback state", function() {
var fakeStore = new loop.store.FeedbackStore(dispatcher, {
feedbackClient: feedbackClient
});
expect(fakeStore.getStoreState("feedbackState"))
.eql(FEEDBACK_STATES.INIT);
});
});
describe("#requireFeedbackDetails", function() {
it("should transition to DETAILS state", function() {
store.requireFeedbackDetails(new sharedActions.RequireFeedbackDetails());
expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.DETAILS);
});
});
describe("#sendFeedback", function() {
var sadFeedbackData = {
happy: false,
category: "fakeCategory",
description: "fakeDescription"
};
beforeEach(function() {
store.requireFeedbackDetails();
});
it("should send feedback data over the feedback client", function() {
sandbox.stub(feedbackClient, "send");
store.sendFeedback(new sharedActions.SendFeedback(sadFeedbackData));
sinon.assert.calledOnce(feedbackClient.send);
sinon.assert.calledWithMatch(feedbackClient.send, sadFeedbackData);
});
it("should transition to PENDING state", function() {
sandbox.stub(feedbackClient, "send");
store.sendFeedback(new sharedActions.SendFeedback(sadFeedbackData));
expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.PENDING);
});
it("should transition to SENT state on successful submission", function(done) {
sandbox.stub(feedbackClient, "send", function(data, cb) {
cb(null);
});
store.once("change:feedbackState", function() {
expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.SENT);
done();
});
store.sendFeedback(new sharedActions.SendFeedback(sadFeedbackData));
});
it("should transition to FAILED state on failed submission", function(done) {
sandbox.stub(feedbackClient, "send", function(data, cb) {
cb(new Error("failed"));
});
store.once("change:feedbackState", function() {
expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.FAILED);
done();
});
store.sendFeedback(new sharedActions.SendFeedback(sadFeedbackData));
});
});
describe("feedbackComplete", function() {
it("should reset the store state", function() {
store.setStoreState({feedbackState: FEEDBACK_STATES.SENT});
store.feedbackComplete();
expect(store.getStoreState()).eql({
feedbackState: FEEDBACK_STATES.INIT
});
});
});
});

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

@ -1,191 +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/. */
describe("loop.shared.views.FeedbackView", function() {
"use strict";
var expect = chai.expect;
var l10n = navigator.mozL10n || document.mozL10n;
var TestUtils = React.addons.TestUtils;
var sharedActions = loop.shared.actions;
var sharedViews = loop.shared.views;
var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
var sandbox, comp, dispatcher, fakeFeedbackClient, feedbackStore;
beforeEach(function() {
sandbox = sinon.sandbox.create();
dispatcher = new loop.Dispatcher();
fakeFeedbackClient = {send: sandbox.stub()};
feedbackStore = new loop.store.FeedbackStore(dispatcher, {
feedbackClient: fakeFeedbackClient
});
loop.store.StoreMixin.register({feedbackStore: feedbackStore});
comp = TestUtils.renderIntoDocument(
React.createElement(sharedViews.FeedbackView));
});
afterEach(function() {
sandbox.restore();
});
// local test helpers
function clickHappyFace(component) {
var happyFace = component.getDOMNode().querySelector(".face-happy");
TestUtils.Simulate.click(happyFace);
}
function clickSadFace(component) {
var sadFace = component.getDOMNode().querySelector(".face-sad");
TestUtils.Simulate.click(sadFace);
}
function fillSadFeedbackForm(component, category, text) {
TestUtils.Simulate.change(
component.getDOMNode().querySelector("[value='" + category + "']"));
if (text) {
TestUtils.Simulate.change(
component.getDOMNode().querySelector("[name='description']"), {
target: {value: "fake reason"}
});
}
}
function submitSadFeedbackForm(component, category, text) {
TestUtils.Simulate.submit(component.getDOMNode().querySelector("form"));
}
describe("Happy feedback", function() {
it("should dispatch a SendFeedback action", function() {
var dispatch = sandbox.stub(dispatcher, "dispatch");
clickHappyFace(comp);
sinon.assert.calledWithMatch(dispatch, new sharedActions.SendFeedback({
happy: true,
category: "",
description: ""
}));
});
it("should thank the user once feedback data is sent", function() {
feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.SENT});
expect(comp.getDOMNode().querySelectorAll(".thank-you")).not.eql(null);
expect(comp.getDOMNode().querySelector("button.fx-embedded-btn-back"))
.eql(null);
});
it("should not display the countdown text if noCloseText is true", function() {
comp = TestUtils.renderIntoDocument(
React.createElement(sharedViews.FeedbackView, {
noCloseText: true
}));
expect(comp.getDOMNode().querySelector(".info.thank-you")).eql(null);
});
});
describe("Sad feedback", function() {
it("should bring the user to feedback form when clicking on the sad face",
function() {
clickSadFace(comp);
expect(comp.getDOMNode().querySelectorAll("form")).not.eql(null);
});
it("should render a back button", function() {
feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.DETAILS});
expect(comp.getDOMNode().querySelector("button.fx-embedded-btn-back"))
.not.eql(null);
});
it("should reset the view when clicking the back button", function() {
feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.DETAILS});
TestUtils.Simulate.click(
comp.getDOMNode().querySelector("button.fx-embedded-btn-back"));
expect(comp.getDOMNode().querySelector(".faces")).not.eql(null);
});
it("should disable the form submit button when no category is chosen",
function() {
clickSadFace(comp);
expect(comp.getDOMNode().querySelector("form button").disabled).eql(true);
});
it("should disable the form submit button when the 'other' category is " +
"chosen but no description has been entered yet",
function() {
clickSadFace(comp);
fillSadFeedbackForm(comp, "other");
expect(comp.getDOMNode().querySelector("form button").disabled).eql(true);
});
it("should enable the form submit button when the 'other' category is " +
"chosen and a description is entered",
function() {
clickSadFace(comp);
fillSadFeedbackForm(comp, "other", "fake");
expect(comp.getDOMNode().querySelector("form button").disabled).eql(false);
});
it("should enable the form submit button once a predefined category is " +
"chosen",
function() {
clickSadFace(comp);
fillSadFeedbackForm(comp, "confusing");
expect(comp.getDOMNode().querySelector("form button").disabled).eql(false);
});
it("should send feedback data when the form is submitted", function() {
var dispatch = sandbox.stub(dispatcher, "dispatch");
feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.DETAILS});
fillSadFeedbackForm(comp, "confusing");
submitSadFeedbackForm(comp);
sinon.assert.calledOnce(dispatch);
sinon.assert.calledWithMatch(dispatch, new sharedActions.SendFeedback({
happy: false,
category: "confusing",
description: ""
}));
});
it("should send feedback data when user has entered a custom description",
function() {
clickSadFace(comp);
fillSadFeedbackForm(comp, "other", "fake reason");
submitSadFeedbackForm(comp);
sinon.assert.calledOnce(fakeFeedbackClient.send);
sinon.assert.calledWith(fakeFeedbackClient.send, {
happy: false,
category: "other",
description: "fake reason"
});
});
it("should thank the user when feedback data has been sent", function() {
fakeFeedbackClient.send = function(data, cb) {
cb();
};
clickSadFace(comp);
fillSadFeedbackForm(comp, "confusing");
submitSadFeedbackForm(comp);
expect(comp.getDOMNode().querySelectorAll(".thank-you")).not.eql(null);
});
});
});

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

@ -52,7 +52,6 @@
<script src="../../content/shared/js/mixins.js"></script>
<script src="../../content/shared/js/crypto.js"></script>
<script src="../../content/shared/js/websocket.js"></script>
<script src="../../content/shared/js/feedbackApiClient.js"></script>
<script src="../../content/shared/js/validate.js"></script>
<script src="../../content/shared/js/actions.js"></script>
<script src="../../content/shared/js/dispatcher.js"></script>
@ -62,11 +61,9 @@
<script src="../../content/shared/js/fxOSActiveRoomStore.js"></script>
<script src="../../content/shared/js/activeRoomStore.js"></script>
<script src="../../content/shared/js/conversationStore.js"></script>
<script src="../../content/shared/js/feedbackStore.js"></script>
<script src="../../content/shared/js/views.js"></script>
<script src="../../content/shared/js/textChatStore.js"></script>
<script src="../../content/shared/js/textChatView.js"></script>
<script src="../../content/shared/js/feedbackViews.js"></script>
<!-- Test scripts -->
<script src="models_test.js"></script>
@ -75,14 +72,11 @@
<script src="crypto_test.js"></script>
<script src="views_test.js"></script>
<script src="websocket_test.js"></script>
<script src="feedbackApiClient_test.js"></script>
<script src="feedbackViews_test.js"></script>
<script src="validate_test.js"></script>
<script src="dispatcher_test.js"></script>
<script src="activeRoomStore_test.js"></script>
<script src="fxOSActiveRoomStore_test.js"></script>
<script src="conversationStore_test.js"></script>
<script src="feedbackStore_test.js"></script>
<script src="otSdkDriver_test.js"></script>
<script src="store_test.js"></script>
<script src="textChatStore_test.js"></script>

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

@ -49,7 +49,6 @@
<script src="../../content/shared/js/models.js"></script>
<script src="../../content/shared/js/mixins.js"></script>
<script src="../../content/shared/js/websocket.js"></script>
<script src="../../content/shared/js/feedbackApiClient.js"></script>
<script src="../../content/shared/js/actions.js"></script>
<script src="../../content/shared/js/validate.js"></script>
<script src="../../content/shared/js/dispatcher.js"></script>
@ -57,11 +56,9 @@
<script src="../../content/shared/js/roomStates.js"></script>
<script src="../../content/shared/js/fxOSActiveRoomStore.js"></script>
<script src="../../content/shared/js/activeRoomStore.js"></script>
<script src="../../content/shared/js/feedbackStore.js"></script>
<script src="../../content/shared/js/views.js"></script>
<script src="../../content/shared/js/textChatStore.js"></script>
<script src="../../content/shared/js/textChatView.js"></script>
<script src="../../content/shared/js/feedbackViews.js"></script>
<script src="../../content/shared/js/otSdkDriver.js"></script>
<script src="../../standalone/content/js/multiplexGum.js"></script>
<script src="../../standalone/content/js/standaloneAppStore.js"></script>
@ -88,7 +85,7 @@
describe("Unexpected Warnings Check", function() {
it("should long only the warnings we expect", function() {
chai.expect(caughtWarnings.length).to.eql(15);
chai.expect(caughtWarnings.length).to.eql(11);
});
});

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

@ -15,7 +15,7 @@ describe("loop.standaloneRoomViews", function() {
var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var sandbox, dispatcher, activeRoomStore, feedbackStore, dispatch;
var sandbox, dispatcher, activeRoomStore, dispatch;
beforeEach(function() {
sandbox = sinon.sandbox.create();
@ -28,12 +28,8 @@ describe("loop.standaloneRoomViews", function() {
var textChatStore = new loop.store.TextChatStore(dispatcher, {
sdkDriver: {}
});
feedbackStore = new loop.store.FeedbackStore(dispatcher, {
feedbackClient: {}
});
loop.store.StoreMixin.register({
activeRoomStore: activeRoomStore,
feedbackStore: feedbackStore,
textChatStore: textChatStore
});
@ -501,38 +497,6 @@ describe("loop.standaloneRoomViews", function() {
});
});
describe("Feedback", function() {
beforeEach(function() {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.ENDED,
used: true
});
});
it("should display a feedback form when the user leaves the room",
function() {
expect(view.getDOMNode().querySelector(".faces")).not.eql(null);
});
it("should dispatch a `FeedbackComplete` action after feedback is sent",
function() {
feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.SENT});
sandbox.clock.tick(
loop.shared.views.WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS * 1000 + 1000);
sinon.assert.calledOnce(dispatch);
sinon.assert.calledWithExactly(dispatch, new sharedActions.FeedbackComplete());
});
it("should NOT display a feedback form if the room has not been used",
function() {
activeRoomStore.setStoreState({used: false});
expect(view.getDOMNode().querySelector(".faces")).eql(null);
});
});
describe("Mute", function() {
it("should render a local avatar if video is muted",
function() {

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

@ -23,11 +23,6 @@ describe("loop.webapp", function() {
sandbox = sinon.sandbox.create();
dispatcher = new loop.Dispatcher();
notifications = new sharedModels.NotificationCollection();
loop.store.StoreMixin.register({
feedbackStore: new loop.store.FeedbackStore(dispatcher, {
feedbackClient: {}
})
});
stubGetPermsAndCacheMedia = sandbox.stub(
loop.standaloneMedia._MultiplexGum.prototype, "getPermsAndCacheMedia");
@ -54,7 +49,6 @@ describe("loop.webapp", function() {
describe("#init", function() {
beforeEach(function() {
sandbox.stub(React, "render");
loop.config.feedbackApiUrl = "http://fake.invalid";
sandbox.stub(loop.Dispatcher.prototype, "dispatch");
});
@ -1077,10 +1071,6 @@ describe("loop.webapp", function() {
it("should render a ConversationView", function() {
TestUtils.findRenderedComponentWithType(view, sharedViews.ConversationView);
});
it("should render a FeedbackView", function() {
TestUtils.findRenderedComponentWithType(view, sharedViews.FeedbackView);
});
});
describe("PromoteFirefoxView", function() {

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

@ -42,7 +42,6 @@
<script src="../content/shared/libs/jquery-2.1.4.js"></script>
<script src="../content/shared/libs/lodash-3.9.3.js"></script>
<script src="../content/shared/libs/backbone-1.2.1.js"></script>
<script src="../content/shared/js/feedbackApiClient.js"></script>
<script src="../content/shared/js/actions.js"></script>
<script src="../content/shared/js/utils.js"></script>
<script src="../content/shared/js/models.js"></script>
@ -55,10 +54,9 @@
<script src="../content/shared/js/roomStates.js"></script>
<script src="../content/shared/js/fxOSActiveRoomStore.js"></script>
<script src="../content/shared/js/activeRoomStore.js"></script>
<script src="../content/shared/js/feedbackStore.js"></script>
<script src="../content/shared/js/views.js"></script>
<script src="../content/shared/js/feedbackViews.js"></script>
<script src="../content/shared/js/textChatStore.js"></script>
<script src="../content/js/feedbackViews.js"></script>
<script src="../content/shared/js/textChatView.js"></script>
<script src="../content/js/roomStore.js"></script>
<script src="../content/js/roomViews.js"></script>

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

@ -32,13 +32,12 @@
// 3. Shared components
var ConversationToolbar = loop.shared.views.ConversationToolbar;
var FeedbackView = loop.shared.views.FeedbackView;
var FeedbackView = loop.feedbackViews.FeedbackView;
var Checkbox = loop.shared.views.Checkbox;
var TextChatView = loop.shared.views.chat.TextChatView;
// Store constants
var ROOM_STATES = loop.store.ROOM_STATES;
var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
// Local helpers
@ -76,14 +75,6 @@
var dispatcher = new loop.Dispatcher();
// Feedback API client configured to send data to the stage input server,
// which is available at https://input.allizom.org
var stageFeedbackApiClient = new loop.FeedbackAPIClient(
"https://input.allizom.org/api/v1/feedback", {
product: "Loop"
}
);
var mockSDK = _.extend({
sendTextChatMessage: function(message) {
dispatcher.dispatch(new loop.shared.actions.ReceivedTextChatMessage({
@ -281,9 +272,6 @@
activeRoomStore: desktopRemoteFaceMuteActiveRoomStore
});
var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
feedbackClient: stageFeedbackApiClient
});
var conversationStore = new loop.store.ConversationStore(dispatcher, {
client: {},
mozLoop: navigator.mozLoop,
@ -354,7 +342,6 @@
loop.store.StoreMixin.register({
activeRoomStore: activeRoomStore,
conversationStore: conversationStore,
feedbackStore: feedbackStore,
textChatStore: textChatStore
});
@ -861,24 +848,13 @@
),
React.createElement(Section, {name: "FeedbackView"},
React.createElement("p", {className: "note"},
React.createElement("strong", null, "Note:"), " For the useable demo, you can access submitted data at ",
React.createElement("a", {href: "https://input.allizom.org/"}, "input.allizom.org"), "."
React.createElement("p", {className: "note"}
),
React.createElement(Example, {dashed: true,
style: {width: "300px", height: "272px"},
summary: "Default (useable demo)"},
React.createElement(FeedbackView, {feedbackStore: feedbackStore})
),
React.createElement(Example, {dashed: true,
style: {width: "300px", height: "272px"},
summary: "Detailed form"},
React.createElement(FeedbackView, {feedbackState: FEEDBACK_STATES.DETAILS, feedbackStore: feedbackStore})
),
React.createElement(Example, {dashed: true,
style: {width: "300px", height: "272px"},
summary: "Thank you!"},
React.createElement(FeedbackView, {feedbackState: FEEDBACK_STATES.SENT, feedbackStore: feedbackStore})
React.createElement(FeedbackView, {mozLoop: {},
onAfterFeedbackReceived: function() {}})
)
),
@ -926,6 +902,7 @@
dispatcher: dispatcher,
localPosterUrl: "sample-img/video-screen-local.png",
mozLoop: navigator.mozLoop,
onCallTerminated: function(){},
roomState: ROOM_STATES.INIT,
roomStore: invitationRoomStore})
)
@ -943,6 +920,7 @@
dispatcher: dispatcher,
localPosterUrl: "sample-img/video-screen-local.png",
mozLoop: navigator.mozLoop,
onCallTerminated: function(){},
remotePosterUrl: "sample-img/video-screen-remote.png",
roomState: ROOM_STATES.HAS_PARTICIPANTS,
roomStore: desktopRoomStoreLoading})
@ -956,6 +934,7 @@
dispatcher: dispatcher,
localPosterUrl: "sample-img/video-screen-local.png",
mozLoop: navigator.mozLoop,
onCallTerminated: function(){},
remotePosterUrl: "sample-img/video-screen-remote.png",
roomState: ROOM_STATES.HAS_PARTICIPANTS,
roomStore: roomStore})
@ -970,6 +949,7 @@
React.createElement(DesktopRoomConversationView, {
dispatcher: dispatcher,
mozLoop: navigator.mozLoop,
onCallTerminated: function(){},
remotePosterUrl: "sample-img/video-screen-remote.png",
roomStore: desktopLocalFaceMuteRoomStore})
)
@ -983,6 +963,7 @@
dispatcher: dispatcher,
localPosterUrl: "sample-img/video-screen-local.png",
mozLoop: navigator.mozLoop,
onCallTerminated: function(){},
roomStore: desktopRemoteFaceMuteRoomStore})
)
)
@ -1171,20 +1152,6 @@
)
),
React.createElement(FramedExample, {cssClass: "standalone",
dashed: true,
height: 483,
summary: "Standalone room conversation (feedback)",
width: 644},
React.createElement("div", {className: "standalone"},
React.createElement(StandaloneRoomView, {
activeRoomStore: endedRoomStore,
dispatcher: dispatcher,
feedbackStore: feedbackStore,
isFirefox: false})
)
),
React.createElement(FramedExample, {cssClass: "standalone",
dashed: true,
height: 483,
@ -1315,7 +1282,7 @@
// This simulates the mocha layout for errors which means we can run
// this alongside our other unit tests but use the same harness.
var expectedWarningsCount = 24;
var expectedWarningsCount = 23;
var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
if (uncaughtError || warningsMismatch) {
$("#results").append("<div class='failures'><em>" +

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

@ -32,13 +32,12 @@
// 3. Shared components
var ConversationToolbar = loop.shared.views.ConversationToolbar;
var FeedbackView = loop.shared.views.FeedbackView;
var FeedbackView = loop.feedbackViews.FeedbackView;
var Checkbox = loop.shared.views.Checkbox;
var TextChatView = loop.shared.views.chat.TextChatView;
// Store constants
var ROOM_STATES = loop.store.ROOM_STATES;
var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
// Local helpers
@ -76,14 +75,6 @@
var dispatcher = new loop.Dispatcher();
// Feedback API client configured to send data to the stage input server,
// which is available at https://input.allizom.org
var stageFeedbackApiClient = new loop.FeedbackAPIClient(
"https://input.allizom.org/api/v1/feedback", {
product: "Loop"
}
);
var mockSDK = _.extend({
sendTextChatMessage: function(message) {
dispatcher.dispatch(new loop.shared.actions.ReceivedTextChatMessage({
@ -281,9 +272,6 @@
activeRoomStore: desktopRemoteFaceMuteActiveRoomStore
});
var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
feedbackClient: stageFeedbackApiClient
});
var conversationStore = new loop.store.ConversationStore(dispatcher, {
client: {},
mozLoop: navigator.mozLoop,
@ -354,7 +342,6 @@
loop.store.StoreMixin.register({
activeRoomStore: activeRoomStore,
conversationStore: conversationStore,
feedbackStore: feedbackStore,
textChatStore: textChatStore
});
@ -862,23 +849,12 @@
<Section name="FeedbackView">
<p className="note">
<strong>Note:</strong> For the useable demo, you can access submitted data at&nbsp;
<a href="https://input.allizom.org/">input.allizom.org</a>.
</p>
<Example dashed={true}
style={{width: "300px", height: "272px"}}
summary="Default (useable demo)">
<FeedbackView feedbackStore={feedbackStore} />
</Example>
<Example dashed={true}
style={{width: "300px", height: "272px"}}
summary="Detailed form">
<FeedbackView feedbackState={FEEDBACK_STATES.DETAILS} feedbackStore={feedbackStore} />
</Example>
<Example dashed={true}
style={{width: "300px", height: "272px"}}
summary="Thank you!">
<FeedbackView feedbackState={FEEDBACK_STATES.SENT} feedbackStore={feedbackStore}/>
<FeedbackView mozLoop={{}}
onAfterFeedbackReceived={function() {}} />
</Example>
</Section>
@ -926,6 +902,7 @@
dispatcher={dispatcher}
localPosterUrl="sample-img/video-screen-local.png"
mozLoop={navigator.mozLoop}
onCallTerminated={function(){}}
roomState={ROOM_STATES.INIT}
roomStore={invitationRoomStore} />
</div>
@ -943,6 +920,7 @@
dispatcher={dispatcher}
localPosterUrl="sample-img/video-screen-local.png"
mozLoop={navigator.mozLoop}
onCallTerminated={function(){}}
remotePosterUrl="sample-img/video-screen-remote.png"
roomState={ROOM_STATES.HAS_PARTICIPANTS}
roomStore={desktopRoomStoreLoading} />
@ -956,6 +934,7 @@
dispatcher={dispatcher}
localPosterUrl="sample-img/video-screen-local.png"
mozLoop={navigator.mozLoop}
onCallTerminated={function(){}}
remotePosterUrl="sample-img/video-screen-remote.png"
roomState={ROOM_STATES.HAS_PARTICIPANTS}
roomStore={roomStore} />
@ -970,6 +949,7 @@
<DesktopRoomConversationView
dispatcher={dispatcher}
mozLoop={navigator.mozLoop}
onCallTerminated={function(){}}
remotePosterUrl="sample-img/video-screen-remote.png"
roomStore={desktopLocalFaceMuteRoomStore} />
</div>
@ -983,6 +963,7 @@
dispatcher={dispatcher}
localPosterUrl="sample-img/video-screen-local.png"
mozLoop={navigator.mozLoop}
onCallTerminated={function(){}}
roomStore={desktopRemoteFaceMuteRoomStore} />
</div>
</FramedExample>
@ -1171,20 +1152,6 @@
</div>
</FramedExample>
<FramedExample cssClass="standalone"
dashed={true}
height={483}
summary="Standalone room conversation (feedback)"
width={644}>
<div className="standalone">
<StandaloneRoomView
activeRoomStore={endedRoomStore}
dispatcher={dispatcher}
feedbackStore={feedbackStore}
isFirefox={false} />
</div>
</FramedExample>
<FramedExample cssClass="standalone"
dashed={true}
height={483}
@ -1315,7 +1282,7 @@
// This simulates the mocha layout for errors which means we can run
// this alongside our other unit tests but use the same harness.
var expectedWarningsCount = 24;
var expectedWarningsCount = 23;
var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
if (uncaughtError || warningsMismatch) {
$("#results").append("<div class='failures'><em>" +

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

@ -89,8 +89,8 @@
<vbox>
<hbox align="center">
<checkbox id="privacyDoNotTrackCheckbox"
label="&dntTrackingNotOkay.label2;"
accesskey="&dntTrackingNotOkay.accesskey;"
label="&dntTrackingNotOkay2.label;"
accesskey="&dntTrackingNotOkay2.accesskey;"
preference="privacy.donottrackheader.enabled"/>
<label id="doNotTrackInfo"
class="text-link"
@ -103,8 +103,8 @@
<hbox align="center">
<checkbox id="trackingProtection"
preference="privacy.trackingprotection.enabled"
accesskey="&trackingProtection.accesskey;"
label="&trackingProtection.label;" />
accesskey="&trackingProtection2.accesskey;"
label="&trackingProtection2.label;" />
<label id="trackingProtectionLearnMore"
class="text-link"
value="&trackingProtectionLearnMore.label;"/>
@ -114,8 +114,8 @@
<hbox align="center">
<checkbox id="trackingProtectionPBM"
preference="privacy.trackingprotection.pbmode.enabled"
accesskey="&trackingProtectionPBM.accesskey;"
label="&trackingProtectionPBM.label;" />
accesskey="&trackingProtectionPBM2.accesskey;"
label="&trackingProtectionPBM2.label;" />
<label id="trackingProtectionPBMLearnMore"
class="text-link"
value="&trackingProtectionPBMLearnMore.label;"/>

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

@ -131,7 +131,6 @@ let gSyncPane = {
_toggleComputerNameControls: function(editMode) {
let textbox = document.getElementById("fxaSyncComputerName");
textbox.className = editMode ? "" : "plain";
textbox.disabled = !editMode;
document.getElementById("fxaChangeDeviceName").hidden = editMode;
document.getElementById("fxaCancelChangeDeviceName").hidden = !editMode;

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

@ -322,30 +322,27 @@
</hbox>
</groupbox>
<groupbox>
<vbox>
<caption>
<label accesskey="&syncDeviceName.accesskey;"
control="fxaSyncComputerName">
&fxaSyncDeviceName.label;
</label>
</caption>
<hbox id="fxaDeviceName">
<hbox flex="1">
<textbox id="fxaSyncComputerName" class="plain"
disabled="true" flex="1"/>
</hbox>
<hbox>
<button id="fxaChangeDeviceName"
label="&changeSyncDeviceName.label;"/>
<button id="fxaCancelChangeDeviceName"
label="&cancelChangeSyncDeviceName.label;"
hidden="true"/>
<button id="fxaSaveChangeDeviceName"
label="&saveChangeSyncDeviceName.label;"
hidden="true"/>
</hbox>
<caption>
<label accesskey="&syncDeviceName.accesskey;"
control="fxaSyncComputerName">
&fxaSyncDeviceName.label;
</label>
</caption>
<hbox id="fxaDeviceName">
<hbox flex="1">
<textbox id="fxaSyncComputerName" disabled="true" flex="1"/>
</hbox>
</vbox>
<hbox>
<button id="fxaChangeDeviceName"
label="&changeSyncDeviceName.label;"/>
<button id="fxaCancelChangeDeviceName"
label="&cancelChangeSyncDeviceName.label;"
hidden="true"/>
<button id="fxaSaveChangeDeviceName"
label="&saveChangeSyncDeviceName.label;"
hidden="true"/>
</hbox>
</hbox>
</groupbox>
<spacer flex="1"/>
<vbox id="tosPP-small">

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

@ -96,8 +96,8 @@
<hbox align="center">
<checkbox id="trackingProtection"
preference="privacy.trackingprotection.enabled"
accesskey="&trackingProtection.accesskey;"
label="&trackingProtection.label;" />
accesskey="&trackingProtection2.accesskey;"
label="&trackingProtection2.label;" />
<image id="trackingProtectionImage"
src="chrome://browser/skin/bad-content-blocked-16.png"/>
</hbox>
@ -111,8 +111,8 @@
<vbox>
<hbox align="center">
<checkbox id="privacyDoNotTrackCheckbox"
label="&dntTrackingNotOkay.label2;"
accesskey="&dntTrackingNotOkay.accesskey;"
label="&dntTrackingNotOkay2.label;"
accesskey="&dntTrackingNotOkay2.accesskey;"
preference="privacy.donottrackheader.enabled"/>
</hbox>
<hbox align="center"

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

@ -16,6 +16,7 @@ function test() {
status: 'VALID',
args: {
nocache: { value: false },
safemode: { value: false },
}
},
},
@ -27,6 +28,31 @@ function test() {
status: 'VALID',
args: {
nocache: { value: true },
safemode: { value: false },
}
},
},
{
setup: 'restart --safemode',
check: {
input: 'restart --safemode',
markup: 'VVVVVVVVVVVVVVVVVV',
status: 'VALID',
args: {
nocache: { value: false },
safemode: { value: true },
}
},
},
{
setup: 'restart --safemode --nocache',
check: {
input: 'restart --safemode --nocache',
markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVV',
status: 'VALID',
args: {
nocache: { value: true },
safemode: { value: true },
}
},
},

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

@ -140,6 +140,7 @@ skip-if = os == 'linux' # Bug 1172120
[browser_profiler_tree-view-10.js]
[browser_profiler_tree-view-11.js]
[browser_timeline-filters-01.js]
skip-if = true # Bug 1176370
[browser_timeline-filters-02.js]
[browser_timeline-waterfall-background.js]
[browser_timeline-waterfall-generic.js]

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

@ -11,6 +11,7 @@ support-files =
doc_content_stylesheet_xul.css
doc_copystyles.css
doc_copystyles.html
doc_custom.html
doc_filter.html
doc_frame_script.js
doc_keyframeanimation.html
@ -83,6 +84,7 @@ skip-if = e10s # Bug 1039528: "inspect element" contextual-menu doesn't work wit
[browser_ruleview_cubicbezier-appears-on-swatch-click.js]
[browser_ruleview_cubicbezier-commit-on-ENTER.js]
[browser_ruleview_cubicbezier-revert-on-ESC.js]
[browser_ruleview_custom.js]
[browser_ruleview_edit-property-commit.js]
[browser_ruleview_edit-property-computed.js]
[browser_ruleview_edit-property-increments.js]

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

@ -0,0 +1,80 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const TEST_URI = TEST_URL_ROOT + "doc_custom.html";
// Test the display of custom declarations in the rule-view
add_task(function*() {
yield addTab(TEST_URI);
let {inspector, view} = yield openRuleView();
yield simpleCustomOverride(inspector, view);
yield importantCustomOverride(inspector, view);
yield disableCustomOverride(inspector, view);
});
function* simpleCustomOverride(inspector, view) {
yield selectNode("#testidSimple", inspector);
let elementStyle = view._elementStyle;
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
is(idProp.name, "--background-color",
"First ID prop should be --background-color");
ok(!idProp.overridden, "ID prop should not be overridden.");
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
is(classProp.name, "--background-color",
"First class prop should be --background-color");
ok(classProp.overridden, "Class property should be overridden.");
// Override --background-color by changing the element style.
let elementRule = elementStyle.rules[0];
elementRule.createProperty("--background-color", "purple", "");
yield elementRule._applyingModifications;
let elementProp = elementRule.textProps[0];
is(classProp.name, "--background-color",
"First element prop should now be --background-color");
ok(!elementProp.overridden,
"Element style property should not be overridden");
ok(idProp.overridden, "ID property should be overridden");
ok(classProp.overridden, "Class property should be overridden");
}
function* importantCustomOverride(inspector, view) {
yield selectNode("#testidImportant", inspector);
let elementStyle = view._elementStyle;
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
ok(idProp.overridden, "Not-important rule should be overridden.");
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
ok(!classProp.overridden, "Important rule should not be overridden.");
}
function* disableCustomOverride(inspector, view) {
yield selectNode("#testidDisable", inspector);
let elementStyle = view._elementStyle;
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
idProp.setEnabled(false);
yield idRule._applyingModifications;
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
ok(!classProp.overridden,
"Class prop should not be overridden after id prop was disabled.");
}

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

@ -0,0 +1,33 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<style>
#testidSimple {
--background-color: blue;
}
.testclassSimple {
--background-color: green;
}
.testclassImportant {
--background-color: green !important;
}
#testidImportant {
--background-color: blue;
}
#testidDisable {
--background-color: blue;
}
.testclassDisable {
--background-color: green;
}
</style>
</head>
<body>
<div id="testidSimple" class="testclassSimple">Styled Node</div>
<div id="testidImportant" class="testclassImportant">Styled Node</div>
<div id="testidDisable" class="testclassDisable">Styled Node</div>
</body>
</html>

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

@ -274,28 +274,13 @@ legal_text_privacy = Privacy Notice
powered_by_beforeLogo=Powered by
powered_by_afterLogo=
feedback_call_experience_heading2=How was your conversation?
feedback_thank_you_heading=Thank you for your feedback!
feedback_category_list_heading=What made you sad?
feedback_category_audio_quality=Audio quality
feedback_category_video_quality=Video quality
feedback_category_was_disconnected=Was disconnected
feedback_category_confusing2=Confusing controls
feedback_category_other2=Other
feedback_custom_category_text_placeholder=What went wrong?
feedback_submit_button=Submit
feedback_back_button=Back
## LOCALIZATION NOTE (feedback_window_will_close_in2):
## Semicolon-separated list of plural forms. See:
## http://developer.mozilla.org/en/docs/Localization_and_Plurals
## In this item, don't translate the part between {{..}}
feedback_window_will_close_in2=This window will close in {{countdown}} second;This window will close in {{countdown}} seconds
## LOCALIZATION_NOTE (feedback_rejoin_button): Displayed on the feedback form after
## a signed-in to signed-in user call.
feedback_rejoin_button=Rejoin
## LOCALIZATION NOTE (feedback_report_user_button): Used to report a user in the case of
## an abusive user.
feedback_report_user_button=Report User
feedback_window_heading=How was your conversation?
feedback_request_button=Leave Feedback
help_label=Help

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

@ -4,15 +4,15 @@
<!ENTITY tracking.label "Tracking">
<!ENTITY dntTrackingNotOkay.label2 "Tell sites that I do not want to be tracked">
<!ENTITY dntTrackingNotOkay.accesskey "n">
<!ENTITY trackingProtection.label "Prevent sites from tracking me">
<!ENTITY trackingProtection.accesskey "m">
<!ENTITY dntTrackingNotOkay2.label "Tell sites that I do not want to be tracked.">
<!ENTITY dntTrackingNotOkay2.accesskey "n">
<!ENTITY doNotTrackInfo.label "Learn More">
<!ENTITY trackingProtection2.label "Prevent sites from tracking me.">
<!ENTITY trackingProtection2.accesskey "m">
<!ENTITY trackingProtectionLearnMore.label "Learn more">
<!ENTITY trackingProtectionPBM.label "Prevent sites from tracking my online activity in Private Windows">
<!ENTITY trackingProtectionPBM.accesskey "y">
<!ENTITY trackingProtectionPBM2.label "Prevent sites from tracking my online activity in Private Windows.">
<!ENTITY trackingProtectionPBM2.accesskey "y">
<!ENTITY trackingProtectionPBMLearnMore.label "Learn more">
<!ENTITY doNotTrackInfo.label "Learn More">
<!ENTITY history.label "History">

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

@ -396,11 +396,6 @@ description > html|a {
margin-left: 0px;
}
#fxaSyncComputerName.plain {
background-color: transparent;
opacity: 1;
}
#tosPP-small-ToS {
margin-bottom: 1em;
}

Двоичные данные
browser/themes/windows/actionicon-tab-XPVista7.png Normal file

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

После

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

Двоичные данные
browser/themes/windows/actionicon-tab.png

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

До

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

После

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

Двоичные данные
browser/themes/windows/actionicon-tab@2x.png Normal file

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

После

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

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

@ -144,7 +144,7 @@
list-style-image: url(chrome://browser/skin/caption-buttons.svg#close);
}
#titlebar-close:hover {
list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-highlight);
list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-white);
}
/* the 12px image renders a 10px icon, and the 10px upscaled gets rounded to 12.5, which

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

@ -1630,6 +1630,15 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
list-style-image: url("chrome://browser/skin/actionicon-tab.png");
-moz-image-region: rect(0, 16px, 11px, 0);
padding: 0 3px;
width: 22px;
height: 11px;
}
@media (min-resolution: 1.1dppx) {
richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-icon {
list-style-image: url("chrome://browser/skin/actionicon-tab@2x.png");
-moz-image-region: rect(0, 32px, 22px, 0);
}
}
@media not all and (-moz-os-version: windows-vista),
@ -1640,6 +1649,12 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
-moz-image-region: rect(11px, 16px, 22px, 0);
}
@media (min-resolution: 1.1dppx) {
richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-box > .ac-action-icon {
-moz-image-region: rect(22px, 32px, 44px, 0);
}
}
.ac-comment[selected="true"],
.ac-url-text[selected="true"],
.ac-action-text[selected="true"] {

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

@ -1,7 +1,7 @@
<svg width="12" height="12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- 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 width="12" height="12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
g {
stroke: ButtonText;
@ -21,9 +21,13 @@
display: initial;
}
.highlight > g {
[id$="-highlight"] > g {
stroke: HighlightText;
}
[id$="-white"] > g {
stroke: #fff;
}
</style>
<g id="close">
<line x1="1" y1="1" x2="11" y2="11"/>
@ -39,8 +43,13 @@
<rect x="1.5" y="3.5" width="7" height="7"/>
<polyline points="3.5,3.5 3.5,1.5 10.5,1.5 10.5,8.5 8.5,8.5"/>
</g>
<use id="close-highlight" class="highlight" xlink:href="#close"/>
<use id="maximize-highlight" class="highlight" xlink:href="#maximize"/>
<use id="minimize-highlight" class="highlight" xlink:href="#minimize"/>
<use id="restore-highlight" class="highlight" xlink:href="#restore"/>
<use id="close-highlight" xlink:href="#close"/>
<use id="maximize-highlight" xlink:href="#maximize"/>
<use id="minimize-highlight" xlink:href="#minimize"/>
<use id="restore-highlight" xlink:href="#restore"/>
<use id="close-white" xlink:href="#close"/>
<use id="maximize-white" xlink:href="#maximize"/>
<use id="minimize-white" xlink:href="#minimize"/>
<use id="restore-white" xlink:href="#restore"/>
</svg>

До

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

После

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

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

@ -240,4 +240,18 @@
padding-left: 15px;
padding-right: 15px;
}
/* Force white caption buttons for the dark theme on Windows 10 */
:root[devtoolstheme="dark"] #titlebar-min {
list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-white);
}
:root[devtoolstheme="dark"] #titlebar-max {
list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize-white);
}
#main-window[devtoolstheme="dark"][sizemode="maximized"] #titlebar-max {
list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-white);
}
:root[devtoolstheme="dark"] #titlebar-close {
list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-white);
}
}

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

@ -22,6 +22,8 @@ browser.jar:
#endif
skin/classic/browser/aboutTabCrashed.css (../shared/aboutTabCrashed.css)
skin/classic/browser/actionicon-tab.png
skin/classic/browser/actionicon-tab@2x.png
skin/classic/browser/actionicon-tab-XPVista7.png
skin/classic/browser/addons/addon-install-blocked.svg (../shared/addons/addon-install-blocked.svg)
skin/classic/browser/addons/addon-install-confirm.svg (../shared/addons/addon-install-confirm.svg)
skin/classic/browser/addons/addon-install-downloading.svg (../shared/addons/addon-install-downloading.svg)
@ -645,6 +647,7 @@ browser.jar:
% override chrome://browser/skin/preferences/saveFile.png chrome://browser/skin/preferences/saveFile-XP.png os=WINNT osversion<6
% override chrome://browser/skin/tabbrowser/tab-separator.png chrome://browser/skin/tabbrowser/tab-separator-XP.png os=WINNT osversion<6
% override chrome://browser/skin/actionicon-tab.png chrome://browser/skin/actionicon-tab-XPVista7.png os=WINNT osversion<=6.1
% override chrome://browser/skin/sync-horizontalbar.png chrome://browser/skin/sync-horizontalbar-XPVista7.png os=WINNT osversion<=6.1
% override chrome://browser/skin/sync-horizontalbar@2x.png chrome://browser/skin/sync-horizontalbar-XPVista7@2x.png os=WINNT osversion<=6.1
% override chrome://browser/skin/syncProgress-horizontalbar.png chrome://browser/skin/syncProgress-horizontalbar-XPVista7.png os=WINNT osversion<=6.1

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

@ -33,6 +33,9 @@ to date and optimally configured for a better, more productive experience
when working on Mozilla projects.
Please run `mach mercurial-setup` now.
Note: `mach mercurial-setup` does not make any changes without prompting
you first.
'''.strip()
OLD_MERCURIAL_TOOLS = '''
@ -46,6 +49,9 @@ more productive experience when working on Mozilla projects.
Please run `mach mercurial-setup` now.
Reminder: `mach mercurial-setup` does not make any changes without
prompting you first.
To avoid this message in the future, run `mach mercurial-setup` once a month.
Or, schedule `mach mercurial-setup --update-only` to run automatically in
the background at least once a month.

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

@ -600,7 +600,7 @@ nsPermissionManager::AppClearDataObserverInit()
// nsPermissionManager Implementation
#define PERMISSIONS_FILE_NAME "permissions.sqlite"
#define HOSTS_SCHEMA_VERSION 5
#define HOSTS_SCHEMA_VERSION 6
#define HOSTPERM_FILE_NAME "hostperm.1"
@ -774,11 +774,14 @@ nsPermissionManager::InitDB(bool aRemoveFile)
LogToConsole(NS_LITERAL_STRING("Get a connection to permissions.sqlite."));
bool tableExists = false;
mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists);
mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &tableExists);
if (!tableExists) {
mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists);
}
if (!tableExists) {
rv = CreateTable();
NS_ENSURE_SUCCESS(rv, rv);
LogToConsole(NS_LITERAL_STRING("DB table(moz_hosts) is created!"));
LogToConsole(NS_LITERAL_STRING("DB table(moz_perms) is created!"));
} else {
// table already exists; check the schema version before reading
int32_t dbSchemaVersion;
@ -921,8 +924,17 @@ nsPermissionManager::InitDB(bool aRemoveFile)
// We rename the old table to moz_hosts_v4 instead of dropping it, such that if
// we discover that there was a problem with our migration code in the future, we have information
// to roll-back with.
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ALTER TABLE moz_hosts RENAME TO moz_hosts_v4"));
NS_ENSURE_SUCCESS(rv, rv);
bool v4TableExists = false;
mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_v4"), &v4TableExists);
if (!v4TableExists) {
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ALTER TABLE moz_hosts RENAME TO moz_hosts_v4"));
NS_ENSURE_SUCCESS(rv, rv);
} else {
NS_WARNING("moz_hosts was not renamed to moz_hosts_v4, as a moz_hosts_v4 table already exists");
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts"));
NS_ENSURE_SUCCESS(rv, rv);
}
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ALTER TABLE moz_hosts_new RENAME TO moz_hosts"));
NS_ENSURE_SUCCESS(rv, rv);
@ -931,6 +943,63 @@ nsPermissionManager::InitDB(bool aRemoveFile)
NS_ENSURE_SUCCESS(rv, rv);
}
// fall through to the next upgrade
// Version 5->6 is the renaming of moz_hosts to moz_perms, and moz_hosts_v4 to moz_hosts
//
// In version 5, we performed the modifications to the permissions database
// in place, this meant that if you upgraded to a version which used V5, and
// then downgraded to a version which used v4 or earlier, the fallback path
// would drop the table, and your permissions data would be lost.
// This migration undoes that mistake, by restoring the old moz_hosts table
// (if it was present), and instead using the new table moz_perms for the
// new permissions schema.
// NOTE: If you downgrade, store new permissions, and then upgrade again,
// these new permissions won't be migrated or reflected in the updated
// database. This migration only occurs once, as if moz_perms exists, it
// will skip creating it. In addition, permissions added after the migration
// will not be visible in previous versions of firefox.
case 5:
{
bool permsTableExists = false;
mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &permsTableExists);
if (!permsTableExists) {
// Move the upgraded database to moz_perms
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ALTER TABLE moz_hosts RENAME TO moz_perms"));
NS_ENSURE_SUCCESS(rv, rv);
} else {
NS_WARNING("moz_hosts was not renamed to moz_perms, as a moz_perms table already exists");
// In the situation where a moz_perms table already exists, but the schema is lower than 6,
// a migration has already previously occured to V6, but a downgrade has caused the moz_hosts
// table to be dropped. This should only occur in the case of a downgrade to a V5 database,
// which was only present in a few day's nightlies. As that version was likely used only on
// a very temporary basis, we assume that the database from the previous V6 has the permissions
// which the user actually wants to use.
// We have to get rid of moz_hosts such that moz_hosts_v4 can be moved into its place if it exists.
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts"));
NS_ENSURE_SUCCESS(rv, rv);
}
#ifdef DEBUG
// The moz_hosts table shouldn't exist anymore
bool hostsTableExists = false;
mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &hostsTableExists);
MOZ_ASSERT(!hostsTableExists);
#endif
// Rename moz_hosts_v4 back to it's original location, if it exists
bool v4TableExists = false;
mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_v4"), &v4TableExists);
if (v4TableExists) {
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ALTER TABLE moz_hosts_v4 RENAME TO moz_hosts"));
NS_ENSURE_SUCCESS(rv, rv);
}
rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
NS_ENSURE_SUCCESS(rv, rv);
}
// current version.
case HOSTS_SCHEMA_VERSION:
break;
@ -946,13 +1015,13 @@ nsPermissionManager::InitDB(bool aRemoveFile)
// check if all the expected columns exist
nsCOMPtr<mozIStorageStatement> stmt;
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT origin, type, permission, expireType, expireTime, modificationTime FROM moz_hosts"),
"SELECT origin, type, permission, expireType, expireTime, modificationTime FROM moz_perms"),
getter_AddRefs(stmt));
if (NS_SUCCEEDED(rv))
break;
// our columns aren't there - drop the table!
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts"));
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_perms"));
NS_ENSURE_SUCCESS(rv, rv);
rv = CreateTable();
@ -964,18 +1033,18 @@ nsPermissionManager::InitDB(bool aRemoveFile)
// cache frequently used statements (for insertion, deletion, and updating)
rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
"INSERT INTO moz_hosts "
"INSERT INTO moz_perms "
"(id, origin, type, permission, expireType, expireTime, modificationTime) "
"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"), getter_AddRefs(mStmtInsert));
NS_ENSURE_SUCCESS(rv, rv);
rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_hosts "
"DELETE FROM moz_perms "
"WHERE id = ?1"), getter_AddRefs(mStmtDelete));
NS_ENSURE_SUCCESS(rv, rv);
rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
"UPDATE moz_hosts "
"UPDATE moz_perms "
"SET permission = ?2, expireType= ?3, expireTime = ?4, modificationTime = ?5 WHERE id = ?1"),
getter_AddRefs(mStmtUpdate));
NS_ENSURE_SUCCESS(rv, rv);
@ -989,7 +1058,7 @@ nsPermissionManager::InitDB(bool aRemoveFile)
return Import();
}
// sets the schema version and creates the moz_hosts table.
// sets the schema version and creates the moz_perms table.
nsresult
nsPermissionManager::CreateTable()
{
@ -999,9 +1068,9 @@ nsPermissionManager::CreateTable()
// create the table
// SQL also lives in automation.py.in. If you change this SQL change that
// one too.
// one too
return mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE moz_hosts ("
"CREATE TABLE moz_perms ("
" id INTEGER PRIMARY KEY"
",origin TEXT"
",type TEXT"
@ -1460,7 +1529,7 @@ nsPermissionManager::RemoveAllInternal(bool aNotifyObservers)
nsCOMPtr<mozIStorageAsyncStatement> removeStmt;
nsresult rv = mDBConn->
CreateAsyncStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_hosts"
"DELETE FROM moz_perms"
), getter_AddRefs(removeStmt));
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (!removeStmt) {
@ -1865,7 +1934,7 @@ nsPermissionManager::RemovePermissionsForApp(uint32_t aAppId, bool aBrowserOnly)
// it is dangerous.
nsAutoCString sql;
sql.AppendLiteral("DELETE FROM moz_hosts WHERE appId=");
sql.AppendLiteral("DELETE FROM moz_perms WHERE appId=");
sql.AppendInt(aAppId);
if (aBrowserOnly) {
@ -2076,7 +2145,7 @@ nsPermissionManager::Read()
// this deletion has its own scope so the write lock is released when done.
nsCOMPtr<mozIStorageStatement> stmtDeleteExpired;
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_hosts WHERE expireType = ?1 AND expireTime <= ?2"),
"DELETE FROM moz_perms WHERE expireType = ?1 AND expireTime <= ?2"),
getter_AddRefs(stmtDeleteExpired));
NS_ENSURE_SUCCESS(rv, rv);
@ -2094,7 +2163,7 @@ nsPermissionManager::Read()
nsCOMPtr<mozIStorageStatement> stmt;
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT id, origin, type, permission, expireType, expireTime, modificationTime "
"FROM moz_hosts"), getter_AddRefs(stmt));
"FROM moz_perms"), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
int64_t id;

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

@ -234,7 +234,7 @@ function findCapabilityViaDB(origin = TEST_ORIGIN, type = TEST_PERMISSION) {
let connection = storage.openDatabase(file);
let query = connection.createStatement(
"SELECT permission FROM moz_hosts WHERE origin = :origin AND type = :type");
"SELECT permission FROM moz_perms WHERE origin = :origin AND type = :type");
query.bindByName("origin", originStr);
query.bindByName("type", type);

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

@ -119,11 +119,11 @@ function run_test() {
.getService(Ci.nsIPermissionManager);
let latestNow = Number(Date.now());
// The schema should be upgraded to 4, and a 'modificationTime' column should
// The schema should be upgraded to 6, and a 'modificationTime' column should
// exist with all records having a value of 0.
do_check_eq(connection.schemaVersion, 5);
do_check_eq(connection.schemaVersion, 6);
let select = connection.createStatement("SELECT modificationTime FROM moz_hosts")
let select = connection.createStatement("SELECT modificationTime FROM moz_perms")
let numMigrated = 0;
while (select.executeStep()) {
let thisModTime = select.getInt64(0);

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

@ -0,0 +1,175 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let PERMISSIONS_FILE_NAME = "permissions.sqlite";
function GetPermissionsFile(profile)
{
let file = profile.clone();
file.append(PERMISSIONS_FILE_NAME);
return file;
}
function run_test() {
run_next_test();
}
add_task(function test() {
/* Create and set up the permissions database */
let profile = do_get_profile();
let perms = [];
let db = Services.storage.openDatabase(GetPermissionsFile(profile));
db.schemaVersion = 5;
db.executeSimpleSQL(
"CREATE TABLE moz_hosts (" +
" id INTEGER PRIMARY KEY" +
",origin TEXT" +
",type TEXT" +
",permission INTEGER" +
",expireType INTEGER" +
",expireTime INTEGER" +
",modificationTime INTEGER" +
")");
db.executeSimpleSQL(
"CREATE TABLE moz_hosts_v4 (" +
" id INTEGER PRIMARY KEY" +
",host TEXT" +
",type TEXT" +
",permission INTEGER" +
",expireType INTEGER" +
",expireTime INTEGER" +
",modificationTime INTEGER" +
",appId INTEGER" +
",isInBrowserElement INTEGER" +
")");
let stmtInsert = db.createStatement(
"INSERT INTO moz_hosts (" +
"id, origin, type, permission, expireType, expireTime, modificationTime" +
") VALUES (" +
":id, :origin, :type, :permission, :expireType, :expireTime, :modificationTime" +
")");
let stmt4Insert = db.createStatement(
"INSERT INTO moz_hosts_v4 (" +
"id, host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement" +
") VALUES (" +
":id, :host, :type, :permission, :expireType, :expireTime, :modificationTime, :appId, :isInBrowserElement" +
")");
let id = 0;
// Insert an origin into the database, and return its principal, type, and permission values
function insertV5(origin, type, permission, expireType, expireTime, modificationTime) {
let thisId = id++;
stmtInsert.bindByName("id", thisId);
stmtInsert.bindByName("origin", origin);
stmtInsert.bindByName("type", type);
stmtInsert.bindByName("permission", permission);
stmtInsert.bindByName("expireType", expireType);
stmtInsert.bindByName("expireTime", expireTime);
stmtInsert.bindByName("modificationTime", modificationTime);
stmtInsert.execute();
return function(stmtLookup, stmt4Lookup) {
stmtLookup.bindByName("id", thisId);
do_check_true(stmtLookup.executeStep());
do_check_eq(stmtLookup.getUTF8String(0), origin);
do_check_eq(stmtLookup.getUTF8String(1), type);
do_check_eq(stmtLookup.getInt64(2), permission);
do_check_eq(stmtLookup.getInt64(3), expireType);
do_check_eq(stmtLookup.getInt64(4), expireTime);
do_check_eq(stmtLookup.getInt64(5), modificationTime);
do_check_false(stmtLookup.executeStep());
stmtLookup.reset();
};
}
function insertV4(host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement) {
let thisId = id++;
stmt4Insert.bindByName("id", thisId);
stmt4Insert.bindByName("host", host);
stmt4Insert.bindByName("type", type);
stmt4Insert.bindByName("permission", permission);
stmt4Insert.bindByName("expireType", expireType);
stmt4Insert.bindByName("expireTime", expireTime);
stmt4Insert.bindByName("modificationTime", modificationTime);
stmt4Insert.bindByName("appId", appId);
stmt4Insert.bindByName("isInBrowserElement", isInBrowserElement);
stmt4Insert.execute();
return function(stmtLookup, stmt4Lookup) {
stmt4Lookup.bindByName("id", thisId);
do_check_true(stmt4Lookup.executeStep());
do_check_eq(stmt4Lookup.getUTF8String(0), host);
do_check_eq(stmt4Lookup.getUTF8String(1), type);
do_check_eq(stmt4Lookup.getInt64(2), permission);
do_check_eq(stmt4Lookup.getInt64(3), expireType);
do_check_eq(stmt4Lookup.getInt64(4), expireTime);
do_check_eq(stmt4Lookup.getInt64(5), modificationTime);
do_check_eq(stmt4Lookup.getInt64(6), appId);
do_check_eq(!!stmt4Lookup.getInt64(7), isInBrowserElement);
do_check_false(stmt4Lookup.executeStep());
stmt4Lookup.reset();
};
}
// Add some rows to the database
perms = [
insertV5("http://foo.com", "A", 1, 0, 0, 0),
insertV5("https://foo.com", "A", 1, 0, 0, 0),
insertV5("https://foo.com^appId=1000", "A", 1, 0, 0, 0),
insertV5("http://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0, 0),
insertV5("https://sub.foo.com", "B", 1, 0, 0, 0),
insertV5("http://subber.sub.foo.com", "B", 1, 0, 0, 0),
insertV5("http://bar.ca", "B", 1, 0, 0, 0),
insertV5("https://bar.ca", "B", 1, 0, 0, 0),
insertV5("ftp://bar.ca", "A", 2, 0, 0, 0),
insertV5("http://bar.ca^appId=1000", "B", 1, 0, 0, 0),
insertV5("http://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0, 0),
insertV5("file:///some/path/to/file.html", "A", 1, 0, 0, 0),
insertV5("file:///another/file.html", "A", 1, 0, 0, 0),
insertV5("about:home", "A", 1, 0, 0, 0),
insertV4("https://foo.com", "A", 1, 0, 0, 0, 1000, false),
insertV4("http://foo.com", "A", 1, 0, 0, 0, 2000, true),
insertV4("https://sub.foo.com", "B", 1, 0, 0, 0, 0, false),
insertV4("http://subber.sub.foo.com", "B", 1, 0, 0, 0, 0, false),
insertV4("http://bar.ca", "B", 1, 0, 0, 0, 0, false),
insertV4("https://bar.ca", "B", 1, 0, 0, 0, 0, false),
];
// Force the permission manager to initialize
let enumerator = Services.perms.enumerator;
do_check_true(enumerator.hasMoreElements());
stmtInsert.finalize();
stmt4Insert.finalize();
db.close();
db = Services.storage.openDatabase(GetPermissionsFile(profile));
do_check_eq(db.schemaVersion, 6);
let stmtLookup = db.createStatement(
"SELECT origin, type, permission, expireType, expireTime, modificationTime FROM moz_perms WHERE id = :id;");
let stmt4Lookup = db.createStatement(
"SELECT host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement FROM moz_hosts WHERE id = :id;");
// Call each of the validation callbacks
perms.forEach((cb) => cb(stmtLookup, stmt4Lookup));
// Close the db connection
stmtLookup.finalize();
stmt4Lookup.finalize();
db.close();
});

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

@ -37,3 +37,4 @@ skip-if = debug == true
[test_permmanager_matchesuri.js]
[test_permmanager_matches.js]
[test_permmanager_migrate_4-5.js]
[test_permmanager_migrate_5-6.js]

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

@ -623,12 +623,17 @@ inDOMUtils::GetSubpropertiesForCSSProperty(const nsAString& aProperty,
nsCSSProperty propertyID =
nsCSSProps::LookupProperty(aProperty, nsCSSProps::eEnabledForAllContent);
if (propertyID == eCSSProperty_UNKNOWN ||
propertyID == eCSSPropertyExtra_variable) {
if (propertyID == eCSSProperty_UNKNOWN) {
return NS_ERROR_FAILURE;
}
nsTArray<nsString> array;
if (propertyID == eCSSPropertyExtra_variable) {
*aValues = static_cast<char16_t**>(moz_xmalloc(sizeof(char16_t*)));
(*aValues)[0] = ToNewUnicode(aProperty);
*aLength = 1;
return NS_OK;
}
if (!nsCSSProps::IsShorthand(propertyID)) {
*aValues = static_cast<char16_t**>(moz_xmalloc(sizeof(char16_t*)));
(*aValues)[0] = ToNewUnicode(nsCSSProps::GetStringValue(propertyID));

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

@ -14,7 +14,6 @@ support-files =
[test_bug856317.html]
[test_bug877690.html]
[test_bug1006595.html]
[test_bug1046140.html]
[test_color_to_rgba.html]
[test_css_property_is_valid.html]
[test_get_all_style_sheets.html]

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

@ -32,6 +32,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1006595
arraysEqual(displaySubProps, [ "color" ],
"'color' subproperties");
var varProps = utils.getSubpropertiesForCSSProperty("--foo");
arraysEqual(varProps, ["--foo"], "'--foo' subproperties");
ok(utils.cssPropertyIsShorthand("padding"), "'padding' is a shorthand")
ok(!utils.cssPropertyIsShorthand("color"), "'color' is not a shorthand")

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

@ -1,34 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1046140
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1046140</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
var utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]
.getService(SpecialPowers.Ci.inIDOMUtils);
try {
utils.getSubpropertiesForCSSProperty("--foo");
ok(false, "expected an exception");
} catch(e) {
ok(true, "getSubpropertiesForCSSProperty throws when passed a CSS variable");
}
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1046140">Mozilla Bug 1046140</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

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

@ -86,20 +86,10 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
remoteTabsEntry = null;
}
// On tablets, we go [...|History|Recent Tabs|Synced Tabs].
// On phones, we go [Synced Tabs|Recent Tabs|History|...].
if (HardwareUtils.isTablet()) {
panelConfigs.add(historyEntry);
panelConfigs.add(recentTabsEntry);
if (remoteTabsEntry != null) {
panelConfigs.add(remoteTabsEntry);
}
} else {
panelConfigs.add(0, historyEntry);
panelConfigs.add(0, recentTabsEntry);
if (remoteTabsEntry != null) {
panelConfigs.add(0, remoteTabsEntry);
}
panelConfigs.add(historyEntry);
panelConfigs.add(recentTabsEntry);
if (remoteTabsEntry != null) {
panelConfigs.add(remoteTabsEntry);
}
return new State(panelConfigs, true);

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

@ -191,8 +191,6 @@ public class HomePager extends ViewPager {
setCurrentItem(index, true);
}
});
} else if (child instanceof HomePagerTabStrip) {
mTabStrip = child;
}
super.addView(child, index, params);

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

@ -102,15 +102,24 @@ public class SearchEngineBar extends TwoWayView
final int searchEngineCount = adapter.getCount() - 1;
if (searchEngineCount > 0) {
final float availableWidthPerContainer = (getMeasuredWidth() - labelContainerWidth) / searchEngineCount;
final int availableWidth = getMeasuredWidth() - labelContainerWidth;
final double searchEnginesToDisplay;
final int desiredIconContainerSize = (int) Math.max(
availableWidthPerContainer,
minIconContainerWidth
);
if (searchEngineCount * minIconContainerWidth <= availableWidth) {
// All search engines fit int: So let's just display all.
searchEnginesToDisplay = searchEngineCount;
} else {
// If only (n) search engines fit into the available space then display (n - 0.5): The last search
// engine will be cut-off to show ability to scroll this view
if (desiredIconContainerSize != iconContainerWidth) {
iconContainerWidth = desiredIconContainerSize;
searchEnginesToDisplay = Math.floor(availableWidth / minIconContainerWidth) - 0.5;
}
// Use all available width and spread search engine icons
final int availableWidthPerContainer = (int) (availableWidth / searchEnginesToDisplay);
if (availableWidthPerContainer != iconContainerWidth) {
iconContainerWidth = availableWidthPerContainer;
adapter.notifyDataSetChanged();
}
}

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

@ -28,21 +28,25 @@ class TabMenuStripLayout extends LinearLayout
private HomePager.OnTitleClickListener onTitleClickListener;
private Drawable strip;
private View selectedView;
private TextView selectedView;
// Data associated with the scrolling of the strip drawable.
private View toTab;
private View fromTab;
private int fromPosition;
private int toPosition;
private float progress;
// This variable is used to predict the direction of scroll.
private float prevProgress;
private int tabContentStart;
TabMenuStripLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabMenuStrip);
final int stripResId = a.getResourceId(R.styleable.TabMenuStrip_strip, -1);
tabContentStart = a.getDimensionPixelSize(R.styleable.TabMenuStrip_tabContentStart, 0);
a.recycle();
if (stripResId != -1) {
@ -55,6 +59,14 @@ class TabMenuStripLayout extends LinearLayout
void onAddPagerView(String title) {
final TextView button = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.tab_menu_strip, this, false);
button.setText(title.toUpperCase());
button.setTextColor(getResources().getColorStateList(R.color.tab_text_color));
if (getChildCount() == 0) {
button.setPadding(button.getPaddingLeft() + tabContentStart,
button.getPaddingTop(),
button.getPaddingRight(),
button.getPaddingBottom());
}
addView(button);
button.setOnClickListener(new ViewClickListener(getChildCount() - 1));
@ -62,7 +74,12 @@ class TabMenuStripLayout extends LinearLayout
}
void onPageSelected(final int position) {
selectedView = getChildAt(position);
if (selectedView != null) {
selectedView.setTextColor(getResources().getColorStateList(R.color.tab_text_color));
}
selectedView = (TextView) getChildAt(position);
selectedView.setTextColor(getResources().getColor(R.color.placeholder_grey));
// Callback to measure and draw the strip after the view is visible.
ViewTreeObserver vto = selectedView.getViewTreeObserver();
@ -73,7 +90,7 @@ class TabMenuStripLayout extends LinearLayout
selectedView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
if (strip != null) {
strip.setBounds(selectedView.getLeft(),
strip.setBounds(selectedView.getLeft() + (position == 0 ? tabContentStart : 0),
selectedView.getTop(),
selectedView.getRight(),
selectedView.getBottom());
@ -103,10 +120,25 @@ class TabMenuStripLayout extends LinearLayout
final int toTabLeft = toTab.getLeft();
final int toTabRight = toTab.getRight();
strip.setBounds((int) (fromTabLeft + ((toTabLeft - fromTabLeft) * progress)),
0,
(int) (fromTabRight + ((toTabRight - fromTabRight) * progress)),
getHeight());
// The first tab has a padding applied (tabContentStart). We don't want the 'strip' to jump around so we remove
// this padding slowly (modifier) when scrolling to or from the first tab.
final int modifier;
if (fromPosition == 0 && toPosition == 1) {
// Slowly remove extra padding (tabContentStart) based on scroll progress
modifier = (int) (tabContentStart * (1 - progress));
} else if (fromPosition == 1 && toPosition == 0) {
// Slowly add extra padding (tabContentStart) based on scroll progress
modifier = (int) (tabContentStart * progress);
} else {
// We are not scrolling tab 0 in any way, no modifier needed
modifier = 0;
}
strip.setBounds((int) (fromTabLeft + ((toTabLeft - fromTabLeft) * progress)) + modifier,
0,
(int) (fromTabRight + ((toTabRight - fromTabRight) * progress)),
getHeight());
invalidate();
}
@ -123,15 +155,18 @@ class TabMenuStripLayout extends LinearLayout
final float currProgress = position + positionOffset;
if (prevProgress > currProgress) {
toTab = getChildAt(position);
fromTab = getChildAt(position + 1);
toPosition = position;
fromPosition = position + 1;
progress = 1 - positionOffset;
} else {
toTab = getChildAt(position + 1);
fromTab = getChildAt(position);
toPosition = position + 1;
fromPosition = position;
progress = positionOffset;
}
toTab = getChildAt(toPosition);
fromTab = getChildAt(fromPosition);
prevProgress = currProgress;
}

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

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:color="@color/placeholder_grey" />
<item android:color="@color/panel_tab_text_normal"/>
</selector>

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

@ -1,22 +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/. -->
<!-- This file is used to include the home pager in gecko app
layout based on screen size -->
<org.mozilla.gecko.home.HomePager xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
android:id="@+id/home_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<org.mozilla.gecko.home.TabMenuStrip android:layout_width="match_parent"
android:layout_height="@dimen/tabs_strip_height"
android:background="@color/about_page_header_grey"
android:layout_gravity="top"
gecko:strip="@drawable/home_tab_menu_strip"/>
</org.mozilla.gecko.home.HomePager>

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

@ -13,12 +13,11 @@
android:layout_height="match_parent"
android:background="@android:color/white">
<org.mozilla.gecko.home.HomePagerTabStrip android:layout_width="match_parent"
android:layout_height="@dimen/tabs_strip_height"
android:layout_gravity="top"
android:gravity="center_vertical"
android:background="@color/about_page_header_grey"
gecko:tabIndicatorColor="@color/fennec_ui_orange"
android:textAppearance="@style/TextAppearance.Widget.HomePagerTabStrip"/>
<org.mozilla.gecko.home.TabMenuStrip android:layout_width="match_parent"
android:layout_height="@dimen/tabs_strip_height"
android:background="@color/about_page_header_grey"
android:layout_gravity="top"
gecko:strip="@drawable/home_tab_menu_strip"
gecko:tabContentStart="72dp" />
</org.mozilla.gecko.home.HomePager>

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