Merge m-c to inbound, a=merge
|
@ -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" >
|
||||
« {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
|
||||
<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;
|
||||
}
|
||||
|
|
После Ширина: | Высота: | Размер: 421 B |
Двоичные данные
browser/themes/windows/actionicon-tab.png
До Ширина: | Высота: | Размер: 421 B После Ширина: | Высота: | Размер: 194 B |
После Ширина: | Высота: | Размер: 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>
|
||||
|
|