author | Ryan VanderMeulen <ryanvm@gmail.com> |
Fri, 27 Mar 2015 11:17:04 -0400 | |
changeset 236123 | 44e454b5e93b64cdb77a025c5d6b8d8ca5c2926e |
parent 236122 | aef75ff8a91166c258a166384275ca8697707fd5 (current diff) |
parent 235975 | 94c247bc2480052cd0d27238f77279a0dcc5f265 (diff) |
child 236124 | 3c34fd480729e3b6684fba747ff61078f672ce16 |
child 236167 | 16c68807669eb4c3ddd2ef01268bb4e7313ad418 |
child 236196 | bcc307ea64f495daebc40b65cb3d070ccf65205d |
push id | 28488 |
push user | ryanvm@gmail.com |
push date | Fri, 27 Mar 2015 16:19:11 +0000 |
treeherder | mozilla-central@44e454b5e93b [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 39.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
browser/app/profile/firefox.js | file | annotate | diff | comparison | revisions | |
dom/base/ScriptSettings.cpp | file | annotate | diff | comparison | revisions | |
mobile/android/base/tests/robocop_login.html | file | annotate | diff | comparison | revisions | |
toolkit/components/places/tests/unit/test_398914.js | file | annotate | diff | comparison | revisions |
--- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -10,25 +10,25 @@ <!--original fetch url was git://codeaurora.org/--> <remote fetch="https://git.mozilla.org/external/caf" name="caf"/> <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db"> <copyfile dest="Makefile" src="core/root.mk"/> </project> - <project name="gaia" path="gaia" remote="mozillaorg" revision="249b8c08c1d57961ef6c905f3498fa62b032bf24"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="9cc496cecc37d7a29f9279827cdf6e4891211f67"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/> <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="5a63e2b9f3ef85e82a33440cb73c55dff4e9bf78"/> + <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/> <!-- 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"/> <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/> <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/> <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/> <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="83760d213fb3bec7b4117d266fcfbf6fe2ba14ab"/> <project name="device/common" path="device/common" revision="6a2995683de147791e516aae2ccb31fdfbe2ad30"/> @@ -110,20 +110,20 @@ <project name="platform/libcore" path="libcore" revision="e195beab082c09217318fc19250caeaf4c1bd800"/> <project name="platform/libnativehelper" path="libnativehelper" revision="feeb36c2bd4adfe285f98f5de92e0f3771b2c115"/> <project name="platform/ndk" path="ndk" revision="e58ef003be4306bb53a8c11331146f39e4eab31f"/> <project name="platform_prebuilts_misc" path="prebuilts/misc" remote="b2g" revision="0e7c060db684b409616fe67ea433ef19f5634c60"/> <project name="platform/prebuilts/ndk" path="prebuilts/ndk" revision="c792f0bd9fff7aea2887c60bbb3a9bbdb534ffa3"/> <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="f7d9bf71cf6693474f3f2a81a4ba62c0fc5646aa"/> <project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="cfcef469537869947abb9aa1d656774cc2678d4c"/> <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="5a48c04c4bb5f079bc757e29864a42427378e051"/> - <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="e0fc03e0a3062063c3c85996dcc881c0a49ed98d"/> + <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="48d2332e6d8400cdc0de273ceff2abe8aaababf8"/> <project name="platform/system/extras" path="system/extras" revision="10e78a05252b3de785f88c2d0b9ea8a428009c50"/> <project name="platform/system/media" path="system/media" revision="7ff72c2ea2496fa50b5e8a915e56e901c3ccd240"/> - <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="8f7d94ac711af4678169805137c6c42def39b3ed"/> + <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="3c5405863d2002f665ef2b901abb3853c420129b"/> <project name="platform/system/netd" path="system/netd" revision="3ae56364946d4a5bf5a5f83f12f9a45a30398e33"/> <project name="platform/system/security" path="system/security" revision="ee8068b9e7bfb2770635062fc9c2035be2142bd8"/> <project name="platform/system/vold" path="system/vold" revision="bb33b1ce8ad9cd3fc4311801b4d56db1d5c8175b"/> <!--original fetch url was http://sprdsource.spreadtrum.com:8085/b2g/android--> <remote fetch="https://git.mozilla.org/external/sprd-aosp" name="sprd-aosp"/> <default remote="sprd-aosp" revision="sprdb2g_gonk4.4" sync-j="4"/> <!-- Stock Android things --> <project name="platform/external/icu4c" path="external/icu4c" revision="2bb01561780583cc37bc667f0ea79f48a122d8a2"/>
--- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -14,17 +14,17 @@ <!--original fetch url was git://github.com/apitrace/--> <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/> <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/> <!-- Gonk specific things and forks --> <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444"> <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="249b8c08c1d57961ef6c905f3498fa62b032bf24"/> + <project name="gaia.git" path="gaia" remote="mozillaorg" revision="9cc496cecc37d7a29f9279827cdf6e4891211f67"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/> <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="527d1c939ee57deb7192166e56e2a3fffa8cb087"/> <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/> <!-- Stock Android things --> <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -12,20 +12,20 @@ <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd"> <copyfile dest="Makefile" src="core/root.mk"/> </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="249b8c08c1d57961ef6c905f3498fa62b032bf24"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="9cc496cecc37d7a29f9279827cdf6e4891211f67"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/> <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/> - <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5a63e2b9f3ef85e82a33440cb73c55dff4e9bf78"/> + <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <!-- Stock Android things --> <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/> <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/> <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="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/> <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="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/> <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/> @@ -112,20 +112,20 @@ <project name="platform/libnativehelper" path="libnativehelper" revision="4792069e90385889b0638e97ae62c67cdf274e22"/> <project name="platform/ndk" path="ndk" revision="7666b97bbaf1d645cdd6b4430a367b7a2bb53369"/> <project name="platform/prebuilts/misc" path="prebuilts/misc" revision="f6ab40b3257abc07741188fd173ac392575cc8d2"/> <project name="platform/prebuilts/ndk" path="prebuilts/ndk" revision="e52099755d0bd3a579130eefe8e58066cc6c0cb6"/> <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="02c32feb2fe97037be0ac4dace3a6a5025ac895d"/> <project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="842e33e43a55ea44833b9e23e4d180fa17c843af"/> <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="5db24726f0f42124304195a6bdea129039eeeaeb"/> <project name="platform/system/bluetooth" path="system/bluetooth" revision="930ae098543881f47eac054677726ee4b998b2f8"/> - <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="e0fc03e0a3062063c3c85996dcc881c0a49ed98d"/> + <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="48d2332e6d8400cdc0de273ceff2abe8aaababf8"/> <project name="platform_system_core" path="system/core" remote="b2g" revision="542d1f59dc331b472307e5bd043101d14d5a3a3e"/> <project name="platform/system/extras" path="system/extras" revision="18c1180e848e7ab8691940481f5c1c8d22c37b3e"/> - <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="8f7d94ac711af4678169805137c6c42def39b3ed"/> + <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="3c5405863d2002f665ef2b901abb3853c420129b"/> <project name="platform/system/media" path="system/media" revision="d90b836f66bf1d9627886c96f3a2d9c3007fbb80"/> <project name="platform/system/netd" path="system/netd" revision="56112dd7b811301b718d0643a82fd5cac9522073"/> <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/> <project name="platform/system/vold" path="system/vold" revision="8de05d4a52b5a91e7336e6baa4592f945a6ddbea"/> <default remote="caf" revision="refs/tags/android-4.3_r2.1" sync-j="4"/> <!-- Emulator specific things --> <project name="android-development" path="development" remote="b2g" revision="dab55669da8f48b6e57df95d5af9f16b4a87b0b1"/> <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
--- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -10,25 +10,25 @@ <!--original fetch url was git://codeaurora.org/--> <remote fetch="https://git.mozilla.org/external/caf" name="caf"/> <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db"> <copyfile dest="Makefile" src="core/root.mk"/> </project> - <project name="gaia" path="gaia" remote="mozillaorg" revision="249b8c08c1d57961ef6c905f3498fa62b032bf24"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="9cc496cecc37d7a29f9279827cdf6e4891211f67"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/> <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="5a63e2b9f3ef85e82a33440cb73c55dff4e9bf78"/> + <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/> <!-- 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"/> <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="1950e4760fa14688b83cdbb5acaa1af9f82ef434"/> <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="ac6eb97a37035c09fb5ede0852f0881e9aadf9ad"/> <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="737f591c5f95477148d26602c7be56cbea0cdeb9"/> <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="51da9b1981be481b92a59a826d4d78dc73d0989a"/> <project name="device/common" path="device/common" revision="798a3664597e6041985feab9aef42e98d458bc3d"/> @@ -110,20 +110,20 @@ <project name="platform/libcore" path="libcore" revision="9877ade9617bb0db6e59aa2a54719a9bc92600f3"/> <project name="platform/libnativehelper" path="libnativehelper" revision="46c96ace65eb1ccab05bf15b9bf8e53e443039af"/> <project name="platform/ndk" path="ndk" revision="cb5519af32ae7b4a9c334913a612462ecd04c5d0"/> <project name="platform_prebuilts_misc" path="prebuilts/misc" remote="b2g" revision="0e7c060db684b409616fe67ea433ef19f5634c60"/> <project name="platform/prebuilts/ndk" path="prebuilts/ndk" revision="6aa61f8557a22039a30b42b7f283996381fd625d"/> <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="f7d9bf71cf6693474f3f2a81a4ba62c0fc5646aa"/> <project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="b562b01c93de9578d5db537b6a602a38e1aaa0ce"/> <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="387f03e815f57d536dd922706db1622bddba8d81"/> - <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="e0fc03e0a3062063c3c85996dcc881c0a49ed98d"/> + <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="48d2332e6d8400cdc0de273ceff2abe8aaababf8"/> <project name="platform/system/extras" path="system/extras" revision="5356165f67f4a81c2ef28671c13697f1657590df"/> <project name="platform/system/media" path="system/media" revision="be0e2fe59a8043fa5200f75697df9220a99abe9d"/> - <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="8f7d94ac711af4678169805137c6c42def39b3ed"/> + <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="3c5405863d2002f665ef2b901abb3853c420129b"/> <project name="platform/system/netd" path="system/netd" revision="36704b0da24debcab8090156568ac236315036bb"/> <project name="platform/system/security" path="system/security" revision="583374f69f531ba68fc3dcbff1f74893d2a96406"/> <project name="platform/system/vold" path="system/vold" revision="d4455b8cf361f8353e8aebac15ffd64b4aedd2b9"/> <project name="platform/external/icu4c" path="external/icu4c" remote="aosp" revision="b4c6379528887dc25ca9991a535a8d92a61ad6b6"/> <project name="platform_frameworks_av" path="frameworks/av" remote="b2g" revision="f3cedd7fd9b1649aa5107d466be9078bb7602af6"/> <project name="platform_system_core" path="system/core" remote="b2g" revision="9395eb5aa885cf6d305a202de6e9694a58a89717"/> <default remote="caf" revision="refs/tags/android-4.4.2_r1" sync-j="4"/> <!-- Emulator specific things -->
--- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -10,25 +10,25 @@ <!--original fetch url was git://codeaurora.org/--> <remote fetch="https://git.mozilla.org/external/caf" name="caf"/> <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="52775e03a2d8532429dff579cb2cd56718e488c3"> <copyfile dest="Makefile" src="core/root.mk"/> </project> - <project name="gaia" path="gaia" remote="mozillaorg" revision="249b8c08c1d57961ef6c905f3498fa62b032bf24"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="9cc496cecc37d7a29f9279827cdf6e4891211f67"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/> <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="5a63e2b9f3ef85e82a33440cb73c55dff4e9bf78"/> + <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/> <!-- 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="50d1ca4ab8add54523b7bc692860d57e8ee4c0d1"/> <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="fb3845864573857677f9b500040a8f011eaf5078"/> <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="354496e8eddd28c743d8e02c02eeab02958367e6"/> <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="b37c91354272b7413a0dc058b7445e677921d39e"/> <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" revision="a227c92e0170bcf2296a63386956946b0dd78ca7"/> <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" revision="884626610186b6dbea52cec5194b1c4bcfe1cb98"/> <project groups="pdk,linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" path="prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" revision="29f9b82faa1af9730f52e933dca848546cbea84c"/> @@ -122,19 +122,19 @@ <project name="platform/libcore" path="libcore" revision="1a07f00d8163f497a785a3285ec55fe551ba95c1"/> <project name="platform/libnativehelper" path="libnativehelper" revision="4834b58ed7af3ee69523177e00e55603ac90ed50"/> <project name="platform/ndk" path="ndk" revision="869c05cab7b4315c2bc607493db3f5b18ead2580"/> <project name="platform_prebuilts_misc" path="prebuilts/misc" remote="b2g" revision="da8b660db117e2a69a7624bfdca2f02cad397f2e"/> <project name="platform/prebuilts/ndk" path="prebuilts/ndk" revision="5fd638a4a0ff3677fc3c970fab038d6db1bb7665"/> <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="2c0d193349c55337e37196a7f2d5cef37753ed3e"/> <project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="a982f43b7f2d5916dc3a859667a8ba78e50b6202"/> <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="6e18b61ee446bdd9880c07ae84197a087490c2e5"/> - <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="e0fc03e0a3062063c3c85996dcc881c0a49ed98d"/> + <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="48d2332e6d8400cdc0de273ceff2abe8aaababf8"/> <project name="platform/system/extras" path="system/extras" revision="18f7c51415917eb0e21b30f220db7bd0be4130a7"/> - <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="8f7d94ac711af4678169805137c6c42def39b3ed"/> + <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="3c5405863d2002f665ef2b901abb3853c420129b"/> <project name="platform/system/media" path="system/media" revision="adf8fbacf7395858884690df5e3ce46bc75fa683"/> <project name="platform/system/netd" path="system/netd" revision="655392625db084a7122d65a15acf74db7f1da7f7"/> <project name="platform/system/security" path="system/security" revision="e6b3fdd892ad994ec3fd0b8959d630e31881801b"/> <project name="platform/system/vold" path="system/vold" revision="eb59d2afd5f6e1cbab2ef985a8dd1c7105b499e8"/> <project name="platform_frameworks_av" path="frameworks/av" remote="b2g" revision="ea531874885eed7f68802048218ed86dde927f58"/> <project name="platform_frameworks_base" path="frameworks/base" remote="b2g" revision="df7e0cfbbc7e954ed26c73ac17832a5ff035f046"/> <project name="platform_frameworks_wilhelm" path="frameworks/wilhelm" remote="b2g" revision="73f7e7f12c8c5459f7a39e2fa343f083c942864d"/> <project name="platform_system_core" path="system/core" remote="b2g" revision="4df51d9abf6cc9a6ec49b965e621699e0e6dc4fb"/>
new file mode 100644 --- /dev/null +++ b/b2g/config/emulator-x86-kk/config.json @@ -0,0 +1,32 @@ +{ + "config_version": 2, + "tooltool_manifest": "releng-emulator-kk.tt", + "mock_target": "mozilla-centos6-x86_64", + "mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "glibc-devel.i686", "libstdc++.i686", "zlib-devel.i686", "ncurses-devel.i686", "libX11-devel.i686", "mesa-libGL-devel.i686", "mesa-libGL-devel", "libX11-devel", "git", "libxml2"], + "mock_files": [["/home/cltbld/.ssh", "/home/mock_mozilla/.ssh"]], + "build_targets": ["droid", "package-emulator", "package-tests"], + "upload_files": [ + "{workdir}/out/target/product/generic/*.tar.bz2", + "{workdir}/out/target/product/generic/tests/*.zip", + "{workdir}/out/emulator.tar.gz", + "{objdir}/dist/b2g-*.crashreporter-symbols.zip", + "{workdir}/sources.xml" + ], + "public_upload_files": [ + "{workdir}/out/target/product/generic/*.tar.bz2", + "{workdir}/out/target/product/generic/tests/*.zip", + "{objdir}/dist/b2g-*.crashreporter-symbols.zip", + "{objdir}/dist/b2g-*.tar.gz", + "{workdir}/sources.xml" + ], + "upload_platform": "emulator-kk", + "gecko_l10n_root": "https://hg.mozilla.org/l10n-central", + "gaia": { + "l10n": { + "vcs": "hgtool", + "root": "https://hg.mozilla.org/gaia-l10n" + } + }, + "b2g_manifest": "emulator-kk.xml", + "b2g_manifest_intree": true +}
new file mode 100644 --- /dev/null +++ b/b2g/config/emulator-x86-kk/releng-emulator-kk.tt @@ -0,0 +1,9 @@ +[ +{ +"size": 80458572, +"digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad", +"algorithm": "sha512", +"filename": "gcc.tar.xz", +"unpack": "True" +} +]
new file mode 120000 --- /dev/null +++ b/b2g/config/emulator-x86-kk/sources.xml @@ -0,0 +1,1 @@ +../emulator-kk/sources.xml \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/b2g/config/emulator-x86-l/config.json @@ -0,0 +1,32 @@ +{ + "config_version": 2, + "tooltool_manifest": "releng-emulator-l.tt", + "mock_target": "mozilla-centos6-x86_64", + "mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "glibc-devel.i686", "libstdc++.i686", "zlib-devel.i686", "ncurses-devel.i686", "libX11-devel.i686", "mesa-libGL-devel.i686", "mesa-libGL-devel", "libX11-devel", "git", "libxml2"], + "mock_files": [["/home/cltbld/.ssh", "/home/mock_mozilla/.ssh"]], + "build_targets": ["droid", "package-emulator", "package-tests"], + "upload_files": [ + "{workdir}/out/target/product/generic/*.tar.bz2", + "{workdir}/out/target/product/generic/tests/*.zip", + "{workdir}/out/emulator.tar.gz", + "{objdir}/dist/b2g-*.crashreporter-symbols.zip", + "{workdir}/sources.xml" + ], + "public_upload_files": [ + "{workdir}/out/target/product/generic/*.tar.bz2", + "{workdir}/out/target/product/generic/tests/*.zip", + "{objdir}/dist/b2g-*.crashreporter-symbols.zip", + "{objdir}/dist/b2g-*.tar.gz", + "{workdir}/sources.xml" + ], + "upload_platform": "emulator-l", + "gecko_l10n_root": "https://hg.mozilla.org/l10n-central", + "gaia": { + "l10n": { + "vcs": "hgtool", + "root": "https://hg.mozilla.org/gaia-l10n" + } + }, + "b2g_manifest": "emulator-l.xml", + "b2g_manifest_intree": true +}
new file mode 100644 --- /dev/null +++ b/b2g/config/emulator-x86-l/releng-emulator-l.tt @@ -0,0 +1,9 @@ +[ +{ +"size": 80458572, +"digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad", +"algorithm": "sha512", +"filename": "gcc.tar.xz", +"unpack": "True" +} +]
new file mode 120000 --- /dev/null +++ b/b2g/config/emulator-x86-l/sources.xml @@ -0,0 +1,1 @@ +../emulator-l/sources.xml \ No newline at end of file
--- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -14,17 +14,17 @@ <!--original fetch url was git://github.com/apitrace/--> <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/> <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/> <!-- Gonk specific things and forks --> <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444"> <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="249b8c08c1d57961ef6c905f3498fa62b032bf24"/> + <project name="gaia.git" path="gaia" remote="mozillaorg" revision="9cc496cecc37d7a29f9279827cdf6e4891211f67"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/> <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="527d1c939ee57deb7192166e56e2a3fffa8cb087"/> <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/> <!-- Stock Android things --> <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -10,25 +10,25 @@ <!--original fetch url was git://codeaurora.org/--> <remote fetch="https://git.mozilla.org/external/caf" name="caf"/> <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db"> <copyfile dest="Makefile" src="core/root.mk"/> </project> - <project name="gaia" path="gaia" remote="mozillaorg" revision="249b8c08c1d57961ef6c905f3498fa62b032bf24"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="9cc496cecc37d7a29f9279827cdf6e4891211f67"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/> <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="5a63e2b9f3ef85e82a33440cb73c55dff4e9bf78"/> + <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/> <!-- 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"/> <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/> <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/> <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/> <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="a32003194f707f66a2d8cdb913ed1869f1926c5d"/> <project name="device/common" path="device/common" revision="96d4d2006c4fcb2f19a3fa47ab10cb409faa017b"/> @@ -104,19 +104,19 @@ <project name="platform/libcore" path="libcore" revision="baf7d8068dd501cfa338d3a8b1b87216d6ce0571"/> <project name="platform/libnativehelper" path="libnativehelper" revision="50c4430e32849530ced32680fd6ee98963b3f7ac"/> <project name="platform/ndk" path="ndk" revision="e58ef003be4306bb53a8c11331146f39e4eab31f"/> <project name="platform_prebuilts_misc" path="prebuilts/misc" remote="b2g" revision="0e7c060db684b409616fe67ea433ef19f5634c60"/> <project name="platform/prebuilts/ndk" path="prebuilts/ndk" revision="c792f0bd9fff7aea2887c60bbb3a9bbdb534ffa3"/> <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="f7d9bf71cf6693474f3f2a81a4ba62c0fc5646aa"/> <project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="69d524e80cdf3981006627c65ac85f3a871238a3"/> <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="5a48c04c4bb5f079bc757e29864a42427378e051"/> - <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="e0fc03e0a3062063c3c85996dcc881c0a49ed98d"/> + <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="48d2332e6d8400cdc0de273ceff2abe8aaababf8"/> <project name="platform/system/extras" path="system/extras" revision="576f57b6510de59c08568b53c0fb60588be8689e"/> - <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="8f7d94ac711af4678169805137c6c42def39b3ed"/> + <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="3c5405863d2002f665ef2b901abb3853c420129b"/> <project name="platform/system/netd" path="system/netd" revision="a6531f7befb49b1c81bc0de7e51c5482b308e1c5"/> <project name="platform/system/security" path="system/security" revision="ee8068b9e7bfb2770635062fc9c2035be2142bd8"/> <project name="platform/system/vold" path="system/vold" revision="42fa2a0f14f965970a4b629a176bbd2666edf017"/> <project name="platform/external/curl" path="external/curl" revision="e68addd988448959ea8157c5de637346b4180c33"/> <project name="platform/external/icu4c" path="external/icu4c" revision="d3ec7428eb276db43b7ed0544e09344a6014806c"/> <project name="platform/hardware/libhardware_legacy" path="hardware/libhardware_legacy" revision="76c4bf4bc430a1b8317f2f21ef735867733e50cc"/> <project name="platform/system/media" path="system/media" revision="c1332c21c608f4932a6d7e83450411cde53315ef"/> <!--original fetch url was git://github.com/t2m-foxfone/-->
--- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -12,20 +12,20 @@ <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd"> <copyfile dest="Makefile" src="core/root.mk"/> </project> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> - <project name="gaia" path="gaia" remote="mozillaorg" revision="249b8c08c1d57961ef6c905f3498fa62b032bf24"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="9cc496cecc37d7a29f9279827cdf6e4891211f67"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/> <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/> - <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5a63e2b9f3ef85e82a33440cb73c55dff4e9bf78"/> + <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <!-- Stock Android things --> <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/> <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/> <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"/> <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
--- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "249b8c08c1d57961ef6c905f3498fa62b032bf24", + "git_revision": "9cc496cecc37d7a29f9279827cdf6e4891211f67", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "87ffbce1342f44bdeee96c7d08d41ac630254eb3", + "revision": "9e79307fd6bcade07847b92d42948a6a6a334f79", "repo_path": "integration/gaia-central" }
--- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -12,20 +12,20 @@ <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd"> <copyfile dest="Makefile" src="core/root.mk"/> </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="249b8c08c1d57961ef6c905f3498fa62b032bf24"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="9cc496cecc37d7a29f9279827cdf6e4891211f67"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/> <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/> - <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5a63e2b9f3ef85e82a33440cb73c55dff4e9bf78"/> + <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <!-- Stock Android things --> <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/> <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/> <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="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/> <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="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/> <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/> @@ -112,20 +112,20 @@ <project name="platform/libnativehelper" path="libnativehelper" revision="4792069e90385889b0638e97ae62c67cdf274e22"/> <project name="platform/ndk" path="ndk" revision="7666b97bbaf1d645cdd6b4430a367b7a2bb53369"/> <project name="platform/prebuilts/misc" path="prebuilts/misc" revision="f6ab40b3257abc07741188fd173ac392575cc8d2"/> <project name="platform/prebuilts/ndk" path="prebuilts/ndk" revision="e52099755d0bd3a579130eefe8e58066cc6c0cb6"/> <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="02c32feb2fe97037be0ac4dace3a6a5025ac895d"/> <project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="842e33e43a55ea44833b9e23e4d180fa17c843af"/> <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="5db24726f0f42124304195a6bdea129039eeeaeb"/> <project name="platform/system/bluetooth" path="system/bluetooth" revision="930ae098543881f47eac054677726ee4b998b2f8"/> - <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="e0fc03e0a3062063c3c85996dcc881c0a49ed98d"/> + <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="48d2332e6d8400cdc0de273ceff2abe8aaababf8"/> <project name="platform_system_core" path="system/core" remote="b2g" revision="542d1f59dc331b472307e5bd043101d14d5a3a3e"/> <project name="platform/system/extras" path="system/extras" revision="18c1180e848e7ab8691940481f5c1c8d22c37b3e"/> - <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="8f7d94ac711af4678169805137c6c42def39b3ed"/> + <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="3c5405863d2002f665ef2b901abb3853c420129b"/> <project name="platform/system/media" path="system/media" revision="d90b836f66bf1d9627886c96f3a2d9c3007fbb80"/> <project name="platform/system/netd" path="system/netd" revision="56112dd7b811301b718d0643a82fd5cac9522073"/> <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/> <project name="platform/system/vold" path="system/vold" revision="8de05d4a52b5a91e7336e6baa4592f945a6ddbea"/> <default remote="caf" revision="refs/tags/android-4.3_r2.1" sync-j="4"/> <!-- Nexus 4 specific things --> <project name="device-mako" path="device/lge/mako" remote="b2g" revision="78d17f0c117f0c66dd55ee8d5c5dde8ccc93ecba"/> <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
--- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -10,25 +10,25 @@ <!--original fetch url was git://codeaurora.org/--> <remote fetch="https://git.mozilla.org/external/caf" name="caf"/> <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="52775e03a2d8532429dff579cb2cd56718e488c3"> <copyfile dest="Makefile" src="core/root.mk"/> </project> - <project name="gaia" path="gaia" remote="mozillaorg" revision="249b8c08c1d57961ef6c905f3498fa62b032bf24"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="9cc496cecc37d7a29f9279827cdf6e4891211f67"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/> <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="5a63e2b9f3ef85e82a33440cb73c55dff4e9bf78"/> + <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/> <!-- 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="50d1ca4ab8add54523b7bc692860d57e8ee4c0d1"/> <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="fb3845864573857677f9b500040a8f011eaf5078"/> <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="354496e8eddd28c743d8e02c02eeab02958367e6"/> <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="b37c91354272b7413a0dc058b7445e677921d39e"/> <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" revision="a227c92e0170bcf2296a63386956946b0dd78ca7"/> <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" revision="884626610186b6dbea52cec5194b1c4bcfe1cb98"/> <project groups="pdk,linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" path="prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" revision="29f9b82faa1af9730f52e933dca848546cbea84c"/> @@ -122,19 +122,19 @@ <project name="platform/libcore" path="libcore" revision="1a07f00d8163f497a785a3285ec55fe551ba95c1"/> <project name="platform/libnativehelper" path="libnativehelper" revision="4834b58ed7af3ee69523177e00e55603ac90ed50"/> <project name="platform/ndk" path="ndk" revision="869c05cab7b4315c2bc607493db3f5b18ead2580"/> <project name="platform_prebuilts_misc" path="prebuilts/misc" remote="b2g" revision="da8b660db117e2a69a7624bfdca2f02cad397f2e"/> <project name="platform/prebuilts/ndk" path="prebuilts/ndk" revision="5fd638a4a0ff3677fc3c970fab038d6db1bb7665"/> <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="2c0d193349c55337e37196a7f2d5cef37753ed3e"/> <project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="a982f43b7f2d5916dc3a859667a8ba78e50b6202"/> <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="6e18b61ee446bdd9880c07ae84197a087490c2e5"/> - <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="e0fc03e0a3062063c3c85996dcc881c0a49ed98d"/> + <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="48d2332e6d8400cdc0de273ceff2abe8aaababf8"/> <project name="platform/system/extras" path="system/extras" revision="18f7c51415917eb0e21b30f220db7bd0be4130a7"/> - <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="8f7d94ac711af4678169805137c6c42def39b3ed"/> + <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="3c5405863d2002f665ef2b901abb3853c420129b"/> <project name="platform/system/media" path="system/media" revision="adf8fbacf7395858884690df5e3ce46bc75fa683"/> <project name="platform/system/netd" path="system/netd" revision="655392625db084a7122d65a15acf74db7f1da7f7"/> <project name="platform/system/security" path="system/security" revision="e6b3fdd892ad994ec3fd0b8959d630e31881801b"/> <project name="platform/system/vold" path="system/vold" revision="eb59d2afd5f6e1cbab2ef985a8dd1c7105b499e8"/> <project name="platform_frameworks_av" path="frameworks/av" remote="b2g" revision="ea531874885eed7f68802048218ed86dde927f58"/> <project name="platform_frameworks_base" path="frameworks/base" remote="b2g" revision="df7e0cfbbc7e954ed26c73ac17832a5ff035f046"/> <project name="platform_frameworks_wilhelm" path="frameworks/wilhelm" remote="b2g" revision="73f7e7f12c8c5459f7a39e2fa343f083c942864d"/> <project name="platform_system_core" path="system/core" remote="b2g" revision="4df51d9abf6cc9a6ec49b965e621699e0e6dc4fb"/>
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1866,19 +1866,14 @@ pref("dom.ipc.processHangMonitor", true) #ifdef DEBUG // Don't report hangs in DEBUG builds. They're too slow and often a // debugger is attached. pref("dom.ipc.reportProcessHangs", false); #else pref("dom.ipc.reportProcessHangs", true); #endif -#ifndef NIGHTLY_BUILD -// Disable reader mode by default. -pref("reader.parse-on-load.enabled", false); -#endif - // Enable ReadingList browser UI by default. pref("browser.readinglist.enabled", true); pref("browser.readinglist.sidebarEverOpened", false); // Enable the readinglist engine by default. pref("readinglist.scheduler.enabled", true); pref("readinglist.server", "https://readinglist.services.mozilla.com/v1");
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -2095,63 +2095,91 @@ function loadURI(uri, referrer, postData openLinkIn(uri, "current", { referrerURI: referrer, referrerPolicy: referrerPolicy, postData: postData, allowThirdPartyFixup: allowThirdPartyFixup }); } catch (e) {} } -function getShortcutOrURIAndPostData(aURL, aCallback) { - let mayInheritPrincipal = false; - let postData = null; - let shortcutURL = null; - let keyword = aURL; - let param = ""; - - // XXX Bug 1100294 will remove this little hack by using an async version of - // PlacesUtils.getURLAndPostDataForKeyword(). For now we simulate an async - // execution with at least a setTimeout(fn, 0). - let originalCallback = aCallback; - aCallback = data => setTimeout(() => originalCallback(data)); - - let offset = aURL.indexOf(" "); - if (offset > 0) { - keyword = aURL.substr(0, offset); - param = aURL.substr(offset + 1); - } - - let engine = Services.search.getEngineByAlias(keyword); - if (engine) { - let submission = engine.getSubmission(param, null, "keyword"); - postData = submission.postData; - aCallback({ postData: submission.postData, url: submission.uri.spec, - mayInheritPrincipal: mayInheritPrincipal }); - return; - } - - [shortcutURL, postData] = - PlacesUtils.getURLAndPostDataForKeyword(keyword); - - if (!shortcutURL) { - aCallback({ postData: postData, url: aURL, - mayInheritPrincipal: mayInheritPrincipal }); - return; - } - - let escapedPostData = ""; - if (postData) - escapedPostData = unescape(postData); - - if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) { - let charset = ""; - const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/; - let matches = shortcutURL.match(re); - - let continueOperation = function () { +/** + * Given a urlbar value, discerns between URIs, keywords and aliases. + * + * @param url + * The urlbar value. + * @param callback (optional, deprecated) + * The callback function invoked when done. This parameter is + * deprecated, please use the Promise that is returned. + * + * @return Promise<{ postData, url, mayInheritPrincipal }> + */ +function getShortcutOrURIAndPostData(url, callback = null) { + if (callback) { + Deprecated.warning("Please use the Promise returned by " + + "getShortcutOrURIAndPostData() instead of passing a " + + "callback", + "https://bugzilla.mozilla.org/show_bug.cgi?id=1100294"); + } + + return Task.spawn(function* () { + let mayInheritPrincipal = false; + let postData = null; + let shortcutURL = null; + let keyword = url; + let param = ""; + + let offset = url.indexOf(" "); + if (offset > 0) { + keyword = url.substr(0, offset); + param = url.substr(offset + 1); + } + + let engine = Services.search.getEngineByAlias(keyword); + if (engine) { + let submission = engine.getSubmission(param, null, "keyword"); + postData = submission.postData; + return { postData: submission.postData, url: submission.uri.spec, + mayInheritPrincipal }; + } + + let entry = yield PlacesUtils.keywords.fetch(keyword); + if (entry) { + shortcutURL = entry.url.href; + postData = entry.postData; + } + + if (!shortcutURL) { + return { postData, url, mayInheritPrincipal }; + } + + let escapedPostData = ""; + if (postData) + escapedPostData = unescape(postData); + + if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) { + let charset = ""; + const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/; + let matches = shortcutURL.match(re); + + if (matches) { + [, shortcutURL, charset] = matches; + } else { + let uri; + try { + // makeURI() throws if URI is invalid. + uri = makeURI(shortcutURL); + } catch (ex) {} + + if (uri) { + // Try to get the saved character-set. + // Will return an empty string if character-set is not found. + charset = yield PlacesUtils.getCharsetForURI(uri); + } + } + // encodeURIComponent produces UTF-8, and cannot be used for other charsets. // escape() works in those cases, but it doesn't uri-encode +, @, and /. // Therefore we need to manually replace these ASCII characters by their // encodeURIComponent result, to match the behavior of nsEscape() with // url_XPAlphas let encodedParam = ""; if (charset && charset != "UTF-8") encodedParam = escape(convertFromUnicode(charset, param)). @@ -2164,50 +2192,39 @@ function getShortcutOrURIAndPostData(aUR if (/%s/i.test(escapedPostData)) // POST keyword postData = getPostDataStream(escapedPostData, param, encodedParam, "application/x-www-form-urlencoded"); // This URL came from a bookmark, so it's safe to let it inherit the current // document's principal. mayInheritPrincipal = true; - aCallback({ postData: postData, url: shortcutURL, - mayInheritPrincipal: mayInheritPrincipal }); - } - - if (matches) { - [, shortcutURL, charset] = matches; - continueOperation(); - } else { - // Try to get the saved character-set. - // makeURI throws if URI is invalid. - // Will return an empty string if character-set is not found. - try { - PlacesUtils.getCharsetForURI(makeURI(shortcutURL)) - .then(c => { charset = c; continueOperation(); }); - } catch (ex) { - continueOperation(); - } - } - } - else if (param) { - // This keyword doesn't take a parameter, but one was provided. Just return - // the original URL. - postData = null; - - aCallback({ postData: postData, url: aURL, - mayInheritPrincipal: mayInheritPrincipal }); - } else { + return { postData, url: shortcutURL, mayInheritPrincipal }; + } + + if (param) { + // This keyword doesn't take a parameter, but one was provided. Just return + // the original URL. + postData = null; + + return { postData, url, mayInheritPrincipal }; + } + // This URL came from a bookmark, so it's safe to let it inherit the current // document's principal. mayInheritPrincipal = true; - aCallback({ postData: postData, url: shortcutURL, - mayInheritPrincipal: mayInheritPrincipal }); - } + return { postData, url: shortcutURL, mayInheritPrincipal }; + }).then(data => { + if (callback) { + callback(data); + } + + return data; + }); } function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) { var dataStream = Cc["@mozilla.org/io/string-input-stream;1"]. createInstance(Ci.nsIStringInputStream); aStringData = aStringData.replace(/%s/g, aEncKeyword).replace(/%S/g, aKeyword); dataStream.data = aStringData; @@ -3247,17 +3264,17 @@ var newTabButtonObserver = { onDragExit: function (aEvent) { }, onDrop: function (aEvent) { let url = browserDragAndDrop.drop(aEvent, { }); - getShortcutOrURIAndPostData(url, data => { + getShortcutOrURIAndPostData(url).then(data => { if (data.url) { // allow third-party services to fixup this URL openNewTabWith(data.url, null, data.postData, aEvent, true); } }); } } @@ -3267,17 +3284,17 @@ var newWindowButtonObserver = { browserDragAndDrop.dragOver(aEvent); }, onDragExit: function (aEvent) { }, onDrop: function (aEvent) { let url = browserDragAndDrop.drop(aEvent, { }); - getShortcutOrURIAndPostData(url, data => { + getShortcutOrURIAndPostData(url).then(data => { if (data.url) { // allow third-party services to fixup this URL openNewWindowWith(data.url, null, data.postData, true); } }); } } @@ -5603,17 +5620,17 @@ function middleMousePaste(event) { // if it's not the current tab, we don't need to do anything because the // browser doesn't exist. let where = whereToOpenLink(event, true, false); let lastLocationChange; if (where == "current") { lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; } - getShortcutOrURIAndPostData(clipboard, data => { + getShortcutOrURIAndPostData(clipboard).then(data => { try { makeURI(data.url); } catch (ex) { // Not a valid URI. return; } try { @@ -5640,17 +5657,17 @@ function stripUnsafeProtocolOnPaste(past // LOAD_FLAGS_DISALLOW_INHERIT_OWNER for those. return pasteData.replace(/^(?:\s*javascript:)+/i, ""); } function handleDroppedLink(event, url, name) { let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; - getShortcutOrURIAndPostData(url, data => { + getShortcutOrURIAndPostData(url).then(data => { if (data.url && lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) loadURI(data.url, null, data.postData, false); }); // Keep the event from being handled by the dragDrop listeners // built-in to gecko if they happen to be above us. event.preventDefault();
--- a/browser/base/content/newtab/grid.js +++ b/browser/base/content/newtab/grid.js @@ -185,19 +185,19 @@ let gGrid = { // Same goes for the grid if that's not ready yet. if (!this.isDocumentLoaded || !this._ready) { return; } // Save the cell's computed height/width including margin and border if (this._cellMargin === undefined) { let refCell = document.querySelector(".newtab-cell"); - this._cellMargin = parseFloat(getComputedStyle(refCell).marginTop) + + this._cellMargin = parseFloat(getComputedStyle(refCell).marginTop); + this._cellHeight = refCell.offsetHeight + this._cellMargin + parseFloat(getComputedStyle(refCell).marginBottom); - this._cellHeight = refCell.offsetHeight + this._cellMargin; this._cellWidth = refCell.offsetWidth + this._cellMargin; } let availSpace = document.documentElement.clientHeight - this._cellMargin - document.querySelector("#newtab-search-container").offsetHeight; let visibleRows = Math.floor(availSpace / this._cellHeight); this._node.style.height = this._computeHeight() + "px"; this._node.style.maxHeight = this._computeHeight(visibleRows) + "px";
--- a/browser/base/content/test/general/browser_action_keyword.js +++ b/browser/base/content/test/general/browser_action_keyword.js @@ -8,56 +8,56 @@ function* promise_first_result(inputText let firstResult = gURLBar.popup.richlistbox.firstChild; return firstResult; } add_task(function*() { // This test is only relevant if UnifiedComplete is enabled. - if (!Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete")) { - todo(false, "Stop supporting old autocomplete components."); - return; - } + let ucpref = Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete"); + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true); + registerCleanupFunction(() => { + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", ucpref); + }); let tab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla"); let tabs = [tab]; - registerCleanupFunction(() => { + registerCleanupFunction(function* () { for (let tab of tabs) gBrowser.removeTab(tab); - PlacesUtils.bookmarks.removeItem(itemId); + yield PlacesUtils.bookmarks.remove(bm); }); yield promiseTabLoadEvent(tab); - let itemId = - PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, - NetUtil.newURI("http://example.com/?q=%s"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "test"); - PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword"); + let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example.com/?q=%s", + title: "test" }); + yield PlacesUtils.keywords.insert({ keyword: "keyword", + url: "http://example.com/?q=%s" }); let result = yield promise_first_result("keyword something"); isnot(result, null, "Expect a keyword result"); is(result.getAttribute("type"), "action keyword", "Expect correct `type` attribute"); is(result.getAttribute("actiontype"), "keyword", "Expect correct `actiontype` attribute"); - is(result.getAttribute("title"), "test", "Expect correct title"); + is(result.getAttribute("title"), "example.com", "Expect correct title"); // We need to make a real URI out of this to ensure it's normalised for // comparison. let uri = NetUtil.newURI(result.getAttribute("url")); is(uri.spec, makeActionURI("keyword", {url: "http://example.com/?q=something", input: "keyword something"}).spec, "Expect correct url"); is_element_visible(result._title, "Title element should be visible"); is(result._title.childNodes.length, 1, "Title element should have 1 child"); is(result._title.childNodes[0].nodeName, "#text", "That child should be a text node"); - is(result._title.childNodes[0].data, "test", "Node should contain the name of the bookmark"); + is(result._title.childNodes[0].data, "example.com", "Node should contain the name of the bookmark"); - is_element_visible(result._extra, "Extra element should be visible"); + is_element_visible(result._extraBox, "Extra element should be visible"); is(result._extra.childNodes.length, 1, "Title element should have 1 child"); is(result._extra.childNodes[0].nodeName, "span", "That child should be a span node"); let span = result._extra.childNodes[0]; is(span.childNodes.length, 1, "span element should have 1 child"); is(span.childNodes[0].nodeName, "#text", "That child should be a text node"); is(span.childNodes[0].data, "something", "Node should contain the query for the keyword"); is_element_hidden(result._url, "URL element should be hidden");
--- a/browser/base/content/test/general/browser_action_keyword_override.js +++ b/browser/base/content/test/general/browser_action_keyword_override.js @@ -1,35 +1,35 @@ add_task(function*() { // This test is only relevant if UnifiedComplete is enabled. - if (!Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete")) { - todo(false, "Stop supporting old autocomplete components."); - return; - } + let ucpref = Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete"); + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true); + registerCleanupFunction(() => { + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", ucpref); + }); - let itemId = - PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, - NetUtil.newURI("http://example.com/?q=%s"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "test"); - PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword"); + let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example.com/?q=%s", + title: "test" }); + yield PlacesUtils.keywords.insert({ keyword: "keyword", + url: "http://example.com/?q=%s" }) - registerCleanupFunction(() => { - PlacesUtils.bookmarks.removeItem(itemId); + registerCleanupFunction(function* () { + yield PlacesUtils.bookmarks.remove(bm); }); yield promiseAutocompleteResultPopup("keyword search"); let result = gURLBar.popup.richlistbox.children[0]; info("Before override"); is_element_hidden(result._url, "URL element should be hidden"); - is_element_visible(result._extra, "Extra element should be visible"); + is_element_visible(result._extraBox, "Extra element should be visible"); info("During override"); EventUtils.synthesizeKey("VK_SHIFT" , { type: "keydown" }); is_element_hidden(result._url, "URL element should be hidden"); - is_element_visible(result._extra, "Extra element should be visible"); + is_element_visible(result._extraBox, "Extra element should be visible"); EventUtils.synthesizeKey("VK_SHIFT" , { type: "keyup" }); gURLBar.popup.hidePopup(); yield promisePopupHidden(gURLBar.popup); });
--- a/browser/base/content/test/general/browser_action_searchengine.js +++ b/browser/base/content/test/general/browser_action_searchengine.js @@ -1,17 +1,18 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ add_task(function* () { // This test is only relevant if UnifiedComplete is enabled. - if (!Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete")) { - todo(false, "Stop supporting old autocomplete components."); - return; - } + let ucpref = Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete"); + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true); + registerCleanupFunction(() => { + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", ucpref); + }); Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET", "http://example.com/?q={searchTerms}"); let engine = Services.search.getEngineByName("MozSearch"); let originalEngine = Services.search.currentEngine; Services.search.currentEngine = engine; let tab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla", {animate: false});
--- a/browser/base/content/test/general/browser_action_searchengine_alias.js +++ b/browser/base/content/test/general/browser_action_searchengine_alias.js @@ -1,31 +1,35 @@ /** * Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ **/ add_task(function* () { + // This test is only relevant if UnifiedComplete is enabled. + let ucpref = Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete"); Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true); + registerCleanupFunction(() => { + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", ucpref); + }); let iconURI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC"; Services.search.addEngineWithDetails("MozSearch", iconURI, "moz", "", "GET", "http://example.com/?q={searchTerms}"); let engine = Services.search.getEngineByName("MozSearch"); let originalEngine = Services.search.currentEngine; Services.search.currentEngine = engine; let tab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla", {animate: false}); yield promiseTabLoaded(gBrowser.selectedTab); registerCleanupFunction(() => { Services.search.currentEngine = originalEngine; let engine = Services.search.getEngineByName("MozSearch"); Services.search.removeEngine(engine); - Services.prefs.clearUserPref("browser.urlbar.unifiedcomplete"); try { gBrowser.removeTab(tab); } catch(ex) { /* tab may have already been closed in case of failure */ } return PlacesTestUtils.clearHistory(); });
--- a/browser/base/content/test/general/browser_autocomplete_a11y_label.js +++ b/browser/base/content/test/general/browser_autocomplete_a11y_label.js @@ -1,17 +1,18 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ add_task(function*() { // This test is only relevant if UnifiedComplete is enabled. - if (!Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete")) { - todo(false, "Stop supporting old autocomplete components."); - return; - } + let ucpref = Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete"); + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true); + registerCleanupFunction(() => { + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", ucpref); + }); let tab = gBrowser.addTab("about:about"); yield promiseTabLoaded(tab); let actionURL = makeActionURI("switchtab", {url: "about:about"}).spec; yield promiseAutocompleteResultPopup("% about"); ok(gURLBar.popup.richlistbox.children.length > 1, "Should get at least 2 results");
--- a/browser/base/content/test/general/browser_autocomplete_autoselect.js +++ b/browser/base/content/test/general/browser_autocomplete_autoselect.js @@ -5,22 +5,25 @@ function repeat(limit, func) { } function is_selected(index) { is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`); } add_task(function*() { // This test is only relevant if UnifiedComplete is enabled. - if (!Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete")) { - todo(false, "Stop supporting old autocomplete components."); - return; - } + let ucpref = Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete"); + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true); + registerCleanupFunction(() => { + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", ucpref); + }); - registerCleanupFunction(() => PlacesTestUtils.clearHistory()); + registerCleanupFunction(function* () { + yield PlacesTestUtils.clearHistory(); + }); let visits = []; repeat(10, i => { visits.push({ uri: makeURI("http://example.com/autocomplete/?" + i), }); }); yield PlacesTestUtils.addVisits(visits);
--- a/browser/base/content/test/general/browser_autocomplete_enter_race.js +++ b/browser/base/content/test/general/browser_autocomplete_enter_race.js @@ -1,23 +1,22 @@ add_task(function*() { // This test is only relevant if UnifiedComplete is enabled. Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true); - registerCleanupFunction(() => { - PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId); + registerCleanupFunction(function* () { Services.prefs.clearUserPref("browser.urlbar.unifiedcomplete"); + yield PlacesUtils.bookmarks.remove(bm); }); - let itemId = - PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, - NetUtil.newURI("http://example.com/?q=%s"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "test"); - PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword"); + let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example.com/?q=%s", + title: "test" }); + yield PlacesUtils.keywords.insert({ keyword: "keyword", + url: "http://example.com/?q=%s" }); yield new Promise(resolve => waitForFocus(resolve, window)); yield promiseAutocompleteResultPopup("keyword bear"); gURLBar.focus(); EventUtils.synthesizeKey("d", {}); EventUtils.synthesizeKey("VK_RETURN", {});
--- a/browser/base/content/test/general/browser_autocomplete_no_title.js +++ b/browser/base/content/test/general/browser_autocomplete_no_title.js @@ -1,17 +1,18 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ add_task(function*() { // This test is only relevant if UnifiedComplete is enabled. - if (!Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete")) { - todo(false, "Stop supporting old autocomplete components."); - return; - } + let ucpref = Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete"); + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true); + registerCleanupFunction(() => { + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", ucpref); + }); let tab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla", {animate: false}); yield promiseTabLoaded(tab); let uri = NetUtil.newURI("http://bug1060642.example.com/beards/are/pretty/great"); yield PlacesTestUtils.addVisits([{uri: uri, title: ""}]); yield promiseAutocompleteResultPopup("bug1060642");
--- a/browser/base/content/test/general/browser_autocomplete_oldschool_wrap.js +++ b/browser/base/content/test/general/browser_autocomplete_oldschool_wrap.js @@ -10,33 +10,37 @@ function is_selected(index) { add_task(function*() { // This test is only relevant if UnifiedComplete is *disabled*. if (Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete")) { ok(true, "Don't run this test with UnifiedComplete enabled.") return; } - registerCleanupFunction(() => PlacesTestUtils.clearHistory()); - + yield PlacesTestUtils.clearHistory(); let visits = []; repeat(10, i => { visits.push({ uri: makeURI("http://example.com/autocomplete/?" + i), }); }); yield PlacesTestUtils.addVisits(visits); + registerCleanupFunction(function* () { + yield PlacesTestUtils.clearHistory(); + }); + let tab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla", {animate: false}); yield promiseTabLoaded(tab); yield promiseAutocompleteResultPopup("example.com/autocomplete"); let popup = gURLBar.popup; - let results = popup.richlistbox.children; - is(results.length, 10, "Should get 11 results"); + let results = popup.richlistbox.children.filter(is_visible); + + is(results.length, 10, "Should get 10 results"); is_selected(-1); info("Key Down to select the next item"); EventUtils.synthesizeKey("VK_DOWN", {}); is_selected(0); info("Key Up to select the previous item"); EventUtils.synthesizeKey("VK_UP", {});
--- a/browser/base/content/test/general/browser_bug1003461-switchtab-override.js +++ b/browser/base/content/test/general/browser_bug1003461-switchtab-override.js @@ -1,18 +1,19 @@ /* 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/. */ add_task(function* test_switchtab_override() { // This test is only relevant if UnifiedComplete is enabled. - if (!Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete")) { - todo(false, "Stop supporting old autocomplete components."); - return; - } + let ucpref = Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete"); + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true); + registerCleanupFunction(() => { + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", ucpref); + }); let testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; info("Opening first tab"); let tab = gBrowser.addTab(testURL); let deferred = Promise.defer(); whenTabLoaded(tab, deferred.resolve); yield deferred.promise;
--- a/browser/base/content/test/general/browser_bug1070778.js +++ b/browser/base/content/test/general/browser_bug1070778.js @@ -2,39 +2,42 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ function is_selected(index) { is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`); } add_task(function*() { // This test is only relevant if UnifiedComplete is enabled. - if (!Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete")) { - todo(false, "Stop supporting old autocomplete components."); - return; - } - + let ucpref = Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete"); + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true); registerCleanupFunction(() => { - PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId); + Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", ucpref); }); - let itemId = - PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, - NetUtil.newURI("http://example.com/?q=%s"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "test"); - PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword"); + let bookmarks = []; + bookmarks.push((yield PlacesUtils.bookmarks + .insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example.com/?q=%s", + title: "test" }))); + yield PlacesUtils.keywords.insert({ keyword: "keyword", + url: "http://example.com/?q=%s" }); // This item only needed so we can select the keyword item, select something // else, then select the keyword item again. - itemId = - PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, - NetUtil.newURI("http://example.com/keyword"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "keyword abc"); + bookmarks.push((yield PlacesUtils.bookmarks + .insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example.com/keyword", + title: "keyword abc" }))); + + registerCleanupFunction(function* () { + for (let bm of bookmarks) { + yield PlacesUtils.bookmarks.remove(bm); + } + }); let tab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla", {animate: false}); yield promiseTabLoaded(tab); yield promiseAutocompleteResultPopup("keyword a"); // First item should already be selected is_selected(0); // Select next one (important!)
--- a/browser/base/content/test/general/browser_getshortcutoruri.js +++ b/browser/base/content/test/general/browser_getshortcutoruri.js @@ -95,18 +95,17 @@ add_task(function* test_getshortcutoruri yield setupKeywords(); for (let item of testData) { let [data, result] = item; let query = data.keyword; if (data.searchWord) query += " " + data.searchWord; - let returnedData = yield new Promise( - resolve => getShortcutOrURIAndPostData(query, resolve)); + let returnedData = yield getShortcutOrURIAndPostData(query); // null result.url means we should expect the same query we sent in let expected = result.url || query; is(returnedData.url, expected, "got correct URL for " + data.keyword); is(getPostDataString(returnedData.postData), result.postData, "got correct postData for " + data.keyword); is(returnedData.mayInheritPrincipal, !result.isUnsafe, "got correct mayInheritPrincipal for " + data.keyword); } yield cleanupKeywords();
--- a/browser/base/content/test/general/browser_urlbarEnterAfterMouseOver.js +++ b/browser/base/content/test/general/browser_urlbarEnterAfterMouseOver.js @@ -11,17 +11,19 @@ function* promiseAutoComplete(inputText) yield promiseSearchComplete(); } function is_selected(index) { is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`); } add_task(function*() { - registerCleanupFunction(() => PlacesTestUtils.clearHistory()); + registerCleanupFunction(function* () { + yield PlacesTestUtils.clearHistory(); + }); yield PlacesTestUtils.clearHistory(); let tabCount = gBrowser.tabs.length; let visits = []; repeat(10, i => { visits.push({ uri: makeURI("http://example.com/autocomplete/?" + i),
--- a/browser/base/content/urlbarBindings.xml +++ b/browser/base/content/urlbarBindings.xml @@ -445,17 +445,17 @@ } else { url = url + suffix; } url = "http://www." + url; } } - getShortcutOrURIAndPostData(url, data => { + getShortcutOrURIAndPostData(url).then(data => { aCallback([data.url, data.postData, data.mayInheritPrincipal]); }); ]]></body> </method> <field name="_contentIsCropped">false</field> <method name="_initURLTooltip">
--- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -1656,17 +1656,17 @@ BrowserGlue.prototype = { var notifyBox = win.gBrowser.getNotificationBox(); var notification = notifyBox.appendNotification(text, title, null, notifyBox.PRIORITY_CRITICAL_MEDIUM, buttons); notification.persistence = -1; // Until user closes it }, _migrateUI: function BG__migrateUI() { - const UI_VERSION = 28; + const UI_VERSION = 29; const BROWSER_DOCURL = "chrome://browser/content/browser.xul"; let currentUIVersion = 0; try { currentUIVersion = Services.prefs.getIntPref("browser.migration.version"); } catch(ex) {} if (currentUIVersion >= UI_VERSION) return; @@ -2011,16 +2011,31 @@ BrowserGlue.prototype = { Services.prefs.setCharPref("lightweightThemes.selectedThemeID", ""); } // Not clearing browser.devedition.theme.enabled, to preserve user's pref // if for some reason this function runs again (even though it shouldn't) Services.prefs.clearUserPref("browser.devedition.showCustomizeButton"); } + if (currentUIVersion < 29) { + let group = null; + try { + group = Services.prefs.getComplexValue("font.language.group", + Ci.nsIPrefLocalizedString); + } catch (ex) {} + if (group && + ["tr", "x-baltic", "x-central-euro"].some(g => g == group.data)) { + // Latin groups were consolidated. + group.data = "x-western"; + Services.prefs.setComplexValue("font.language.group", + Ci.nsIPrefLocalizedString, group); + } + } + // Update the migration version. Services.prefs.setIntPref("browser.migration.version", UI_VERSION); }, // ------------------------------ // public nsIBrowserGlue members // ------------------------------
--- a/browser/components/preferences/permissions.js +++ b/browser/components/preferences/permissions.js @@ -1,9 +1,8 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ /* 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/. */ Components.utils.import("resource://gre/modules/Services.jsm"); const nsIPermissionManager = Components.interfaces.nsIPermissionManager; const nsICookiePermission = Components.interfaces.nsICookiePermission; @@ -227,16 +226,30 @@ var gPermissionManager = { urlField.value = aParams.prefilledHost; urlField.hidden = !urlFieldVisible; this.onHostInput(urlField); var urlLabel = document.getElementById("urlLabel"); urlLabel.hidden = !urlFieldVisible; + let treecols = document.getElementsByTagName("treecols")[0]; + treecols.addEventListener("click", event => { + if (event.target.nodeName != "treecol" || event.button != 0) { + return; + } + + let sortField = event.target.getAttribute("data-field-name"); + if (!sortField) { + return; + } + + gPermissionManager.onPermissionSort(sortField); + }); + Services.obs.notifyObservers(null, NOTIFICATION_FLUSH_PERMISSIONS, this._type); Services.obs.addObserver(this, "perm-changed", false); this._loadPermissions(); urlField.focus(); },
--- a/browser/components/preferences/permissions.xul +++ b/browser/components/preferences/permissions.xul @@ -48,20 +48,20 @@ </hbox> <separator class="thin"/> <tree id="permissionsTree" flex="1" style="height: 18em;" hidecolumnpicker="true" onkeypress="gPermissionManager.onPermissionKeyPress(event)" onselect="gPermissionManager.onPermissionSelected();"> <treecols> <treecol id="siteCol" label="&treehead.sitename.label;" flex="3" - onclick="gPermissionManager.onPermissionSort('rawHost');" persist="width"/> + data-field-name="rawHost" persist="width"/> <splitter class="tree-splitter"/> <treecol id="statusCol" label="&treehead.status.label;" flex="1" - onclick="gPermissionManager.onPermissionSort('capability');" persist="width"/> + data-field-name="capability" persist="width"/> </treecols> <treechildren/> </tree> </vbox> <vbox> <hbox class="actionButtons" align="left" flex="1"> <button id="removePermission" disabled="true" accesskey="&removepermission.accesskey;"
--- a/browser/components/readinglist/Scheduler.jsm +++ b/browser/components/readinglist/Scheduler.jsm @@ -28,16 +28,22 @@ XPCOMUtils.defineLazyModuleGetter(this, // The main readinglist module. XPCOMUtils.defineLazyModuleGetter(this, 'ReadingList', 'resource:///modules/readinglist/ReadingList.jsm'); // The "engine" XPCOMUtils.defineLazyModuleGetter(this, 'Sync', 'resource:///modules/readinglist/Sync.jsm'); +// FxAccountsCommon.js doesn't use a "namespace", so create one here. +XPCOMUtils.defineLazyGetter(this, "fxAccountsCommon", function() { + let namespace = {}; + Cu.import("resource://gre/modules/FxAccountsCommon.js", namespace); + return namespace; +}); this.EXPORTED_SYMBOLS = ["ReadingListScheduler"]; // A list of "external" observer topics that may cause us to change when we // sync. const OBSERVERS = [ // We don't sync when offline and restart when online. "network:offline-status-changed", @@ -70,16 +76,17 @@ let intervals = { // This is the implementation, but it's not exposed directly. function InternalScheduler(readingList = null) { // oh, I don't know what logs yet - let's guess! let logs = [ "browserwindow.syncui", "FirefoxAccounts", "readinglist.api", + "readinglist.scheduler", "readinglist.serverclient", "readinglist.sync", ]; this._logManager = new LogManager("readinglist.", logs, "readinglist"); this.log = Log.repository.getLogger("readinglist.scheduler"); this.log.info("readinglist scheduler created.") this.state = this.STATE_OK; @@ -170,16 +177,18 @@ InternalScheduler.prototype = { case "readinglist:user-sync": this._syncNow(); break; case "fxaccounts:onverified": // If we were in an authentication error state, reset that now. if (this.state == this.STATE_ERROR_AUTHENTICATION) { this.state = this.STATE_OK; } + // and sync now. + this._syncNow(); break; // The rest just indicate that now is probably a good time to check if // we can sync as normal using whatever schedule was previously set. default: break; } // When observers fire we ignore the current sync error state as the @@ -280,20 +289,34 @@ InternalScheduler.prototype = { // Write a pref in the same format used to services/sync to indicate // the last success. prefs.set("lastSync", new Date().toString()); this.state = this.STATE_OK; this._logManager.resetFileLog(this._logManager.REASON_SUCCESS); Services.obs.notifyObservers(null, "readinglist:sync:finish", null); return intervals.schedule; }).catch(err => { - this.log.error("Sync failed", err); - // XXX - how to detect an auth error? - this.state = err == this._engine.ERROR_AUTHENTICATION ? + // This isn't ideal - we really should have _canSync() check this - but + // that requires a refactor to turn _canSync() into a promise-based + // function. + if (err.message == fxAccountsCommon.ERROR_NO_ACCOUNT || + err.message == fxAccountsCommon.ERROR_UNVERIFIED_ACCOUNT) { + // make everything look like success. + this.log.info("Can't sync due to FxA account state " + err.message); + this.state = this.STATE_OK; + this._logManager.resetFileLog(this._logManager.REASON_SUCCESS); + Services.obs.notifyObservers(null, "readinglist:sync:finish", null); + // it's unfortunate that we are probably going to hit this every + // 2 hours, but it should be invisible to the user. + return intervals.schedule; + } + this.state = err.message == fxAccountsCommon.ERROR_AUTH_ERROR ? this.STATE_ERROR_AUTHENTICATION : this.STATE_ERROR_OTHER; + this.log.error("Sync failed, now in state '${state}': ${err}", + {state: this.state, err}); this._logManager.resetFileLog(this._logManager.REASON_ERROR); Services.obs.notifyObservers(null, "readinglist:sync:error", null); return intervals.retry; }).then(nextDelay => { this._timerRunning = false; // ensure a new timer is setup for the appropriate next time. this._maybeReschedule(nextDelay); this._setupTimer();
--- a/browser/components/readinglist/Sync.jsm +++ b/browser/components/readinglist/Sync.jsm @@ -273,18 +273,23 @@ SyncImpl.prototype = { // "See Other": An item with the URL already exists. Mark the item as // having material changes, and reconcile and upload it in the // material-changes phase. // TODO continue; } // Note that the server seems to return a 200 if an identical item already // exists, but we shouldn't be uploading identical items in this phase in - // normal usage, so treat 200 as an unexpected response. - if (response.status != 201) { + // normal usage. But if something goes wrong locally (eg, we upload but + // get some error even though the upload worked) we will see this. + // So allow 200 but log a warning. + if (response.status == 200) { + log.debug("Attempting to upload a new item found the server already had it", response); + // but we still process it. + } else if (response.status != 201) { this._handleUnexpectedResponse("uploading a new item", response); continue; } let item = yield this.list.itemForURL(response.body.url); yield this._updateItemWithServerRecord(item, response.body); } }), @@ -399,17 +404,23 @@ SyncImpl.prototype = { if (serverRecord.deleted) { yield this._deleteItemForGUID(serverRecord.id); continue; } yield this._updateItemWithServerRecord(localItem, serverRecord); continue; } // new item - yield this.list.addItem(localRecordFromServerRecord(serverRecord)); + let localRecord = localRecordFromServerRecord(serverRecord); + try { + yield this.list.addItem(localRecord); + } catch (ex) { + log.warn("Failed to add a new item from server record ${serverRecord}: ${ex}", + {serverRecord, ex}); + } } }), /** * Phase 3 (material changes) * * Uploads not-new items with material changes. */ @@ -437,41 +448,56 @@ SyncImpl.prototype = { * @param item A local ReadingListItem. * @param serverRecord A server record representing the item. */ _updateItemWithServerRecord: Task.async(function* (localItem, serverRecord) { if (!localItem) { throw new Error("Item should exist"); } localItem._record = localRecordFromServerRecord(serverRecord); - yield this.list.updateItem(localItem); + try { + yield this.list.updateItem(localItem); + } catch (ex) { + log.warn("Failed to update an item from server record ${serverRecord}: ${ex}", + {serverRecord, ex}); + } }), /** * Truly deletes the local ReadingListItem with the given GUID. * * @param guid The item's GUID. */ _deleteItemForGUID: Task.async(function* (guid) { let item = yield this._itemForGUID(guid); if (item) { // If item is non-null, then it hasn't been deleted locally. Therefore // it's important to delete it through its list so that the list and its // consumers are notified properly. Set the syncStatus to NEW so that the // list truly deletes the item. item._record.syncStatus = ReadingList.SyncStatus.NEW; - yield this.list.deleteItem(item); + try { + yield this.list.deleteItem(item); + } catch (ex) { + log.warn("Failed delete local item with id ${guid}: ${ex}", + {guid, ex}); + } return; } // If item is null, then it may not actually exist locally, or it may have // been synced and then deleted so that it's marked as being deleted. In // that case, try to delete it directly from the store. As far as the list // is concerned, the item has already been deleted. log.debug("Item not present in list, deleting it by GUID instead"); - this.list._store.deleteItemByGUID(guid); + try { + this.list._store.deleteItemByGUID(guid); + } catch (ex) { + log.warn("Failed to delete local item with id ${guid}: ${ex}", + {guid, ex}); + } }), /** * Sends a request to the server. * * @param req The request object: { method, path, body, headers }. * @return Promise<response> Resolved with the server's response object: * { status, body, headers }.
--- a/browser/modules/DirectoryLinksProvider.jsm +++ b/browser/modules/DirectoryLinksProvider.jsm @@ -52,18 +52,21 @@ const PREF_NEWTAB_ENHANCED = "browser.ne const ALLOWED_LINK_SCHEMES = new Set(["http", "https"]); // Only allow link image urls that are https or data const ALLOWED_IMAGE_SCHEMES = new Set(["https", "data"]); // The frecency of a directory link const DIRECTORY_FRECENCY = 1000; -// The frecency of a related link -const RELATED_FRECENCY = Infinity; +// The frecency of a suggested link +const SUGGESTED_FRECENCY = Infinity; + +// Default number of times to show a link +const DEFAULT_FREQUENCY_CAP = 5; // Divide frecency by this amount for pings const PING_SCORE_DIVISOR = 10000; // Allowed ping actions remotely stored as columns: case-insensitive [a-z0-9_] const PING_ACTIONS = ["block", "click", "pin", "sponsored", "sponsored_link", "unpin", "view"]; /** @@ -84,24 +87,29 @@ let DirectoryLinksProvider = { _downloadIntervalMS: 86400000, /** * A mapping from eTLD+1 to an enhanced link objects */ _enhancedLinks: new Map(), /** - * A mapping from site to a list of related link objects + * A mapping from site to remaining number of views */ - _relatedLinks: new Map(), + _frequencyCaps: new Map(), /** - * A set of top sites that we can provide related links for + * A mapping from site to a list of suggested link objects */ - _topSitesWithRelatedLinks: new Set(), + _suggestedLinks: new Map(), + + /** + * A set of top sites that we can provide suggested links for + */ + _topSitesWithSuggestedLinks: new Set(), get _observedPrefs() Object.freeze({ enhanced: PREF_NEWTAB_ENHANCED, linksURL: PREF_DIRECTORY_SOURCE, matchOSLocale: PREF_MATCH_OS_LOCALE, prefSelectedLocale: PREF_SELECTED_LOCALE, }), @@ -196,21 +204,21 @@ let DirectoryLinksProvider = { _removePrefsObserver: function DirectoryLinksProvider_removeObserver() { for (let pref in this._observedPrefs) { let prefName = this._observedPrefs[pref]; Services.prefs.removeObserver(prefName, this); } }, - _cacheRelatedLinks: function(link) { - for (let relatedSite of link.frecent_sites) { - let relatedMap = this._relatedLinks.get(relatedSite) || new Map(); - relatedMap.set(link.url, link); - this._relatedLinks.set(relatedSite, relatedMap); + _cacheSuggestedLinks: function(link) { + for (let suggestedSite of link.frecent_sites) { + let suggestedMap = this._suggestedLinks.get(suggestedSite) || new Map(); + suggestedMap.set(link.url, link); + this._suggestedLinks.set(suggestedSite, suggestedMap); } }, _fetchAndCacheLinks: function DirectoryLinksProvider_fetchAndCacheLinks(uri) { // Replace with the same display locale used for selecting links data uri = uri.replace("%LOCALE%", this.locale); let deferred = Promise.defer(); @@ -320,16 +328,33 @@ let DirectoryLinksProvider = { /** * Report some action on a newtab page (view, click) * @param sites Array of sites shown on newtab page * @param action String of the behavior to report * @param triggeringSiteIndex optional Int index of the site triggering action * @return download promise */ reportSitesAction: function DirectoryLinksProvider_reportSitesAction(sites, action, triggeringSiteIndex) { + // Check if the suggested tile was shown + if (action == "view") { + sites.slice(0, triggeringSiteIndex + 1).forEach(site => { + let {targetedSite, url} = site.link; + if (targetedSite) { + this._decreaseFrequencyCap(url, 1); + } + }); + } + // Use up all views if the user clicked on a frequency capped tile + else if (action == "click") { + let {targetedSite, url} = sites[triggeringSiteIndex].link; + if (targetedSite) { + this._decreaseFrequencyCap(url, DEFAULT_FREQUENCY_CAP); + } + } + let newtabEnhanced = false; let pingEndPoint = ""; try { newtabEnhanced = Services.prefs.getBoolPref(PREF_NEWTAB_ENHANCED); pingEndPoint = Services.prefs.getCharPref(PREF_DIRECTORY_PING); } catch (ex) {} @@ -408,19 +433,20 @@ let DirectoryLinksProvider = { }, /** * Gets the current set of directory links. * @param aCallback The function that the array of links is passed to. */ getLinks: function DirectoryLinksProvider_getLinks(aCallback) { this._readDirectoryLinksFile().then(rawLinks => { - // Reset the cache of related tiles and enhanced images for this new set of links + // Reset the cache of suggested tiles and enhanced images for this new set of links this._enhancedLinks.clear(); - this._relatedLinks.clear(); + this._frequencyCaps.clear(); + this._suggestedLinks.clear(); let validityFilter = function(link) { // Make sure the link url is allowed and images too if they exist return this.isURLAllowed(link.url, ALLOWED_LINK_SCHEMES) && this.isURLAllowed(link.imageURI, ALLOWED_IMAGE_SCHEMES) && this.isURLAllowed(link.enhancedImageURI, ALLOWED_IMAGE_SCHEMES); }.bind(this); @@ -430,26 +456,32 @@ let DirectoryLinksProvider = { this._enhancedLinks.set(NewTabUtils.extractSite(link.url), link); } link.lastVisitDate = length - position; }.bind(this); rawLinks.suggested.filter(validityFilter).forEach((link, position) => { setCommonProperties(link, rawLinks.suggested.length, position); - // We cache related tiles here but do not push any of them in the links list yet. - // The decision for which related tile to include will be made separately. - this._cacheRelatedLinks(link); + // We cache suggested tiles here but do not push any of them in the links list yet. + // The decision for which suggested tile to include will be made separately. + this._cacheSuggestedLinks(link); + this._frequencyCaps.set(link.url, DEFAULT_FREQUENCY_CAP); }); - return rawLinks.directory.filter(validityFilter).map((link, position) => { + let links = rawLinks.directory.filter(validityFilter).map((link, position) => { setCommonProperties(link, rawLinks.directory.length, position); link.frecency = DIRECTORY_FRECENCY; return link; }); + + // Allow for one link suggestion on top of the default directory links + this.maxNumLinks = links.length + 1; + + return links; }).catch(ex => { Cu.reportError(ex); return []; }).then(links => { aCallback(links); this._populatePlacesLinks(); }); }, @@ -471,145 +503,171 @@ let DirectoryLinksProvider = { this._lastDownloadMS = Date.parse(fileInfo.lastModificationDate); } // fetch directory on startup without force yield this._fetchAndCacheLinksIfNecessary(); }.bind(this)); }, _handleManyLinksChanged: function() { - this._topSitesWithRelatedLinks.clear(); - this._relatedLinks.forEach((relatedLinks, site) => { + this._topSitesWithSuggestedLinks.clear(); + this._suggestedLinks.forEach((suggestedLinks, site) => { if (NewTabUtils.isTopPlacesSite(site)) { - this._topSitesWithRelatedLinks.add(site); + this._topSitesWithSuggestedLinks.add(site); } }); - this._updateRelatedTile(); + this._updateSuggestedTile(); }, /** - * Updates _topSitesWithRelatedLinks based on the link that was changed. + * Updates _topSitesWithSuggestedLinks based on the link that was changed. * - * @return true if _topSitesWithRelatedLinks was modified, false otherwise. + * @return true if _topSitesWithSuggestedLinks was modified, false otherwise. */ _handleLinkChanged: function(aLink) { let changedLinkSite = NewTabUtils.extractSite(aLink.url); - let linkStored = this._topSitesWithRelatedLinks.has(changedLinkSite); + let linkStored = this._topSitesWithSuggestedLinks.has(changedLinkSite); if (!NewTabUtils.isTopPlacesSite(changedLinkSite) && linkStored) { - this._topSitesWithRelatedLinks.delete(changedLinkSite); + this._topSitesWithSuggestedLinks.delete(changedLinkSite); return true; } - if (this._relatedLinks.has(changedLinkSite) && + if (this._suggestedLinks.has(changedLinkSite) && NewTabUtils.isTopPlacesSite(changedLinkSite) && !linkStored) { - this._topSitesWithRelatedLinks.add(changedLinkSite); + this._topSitesWithSuggestedLinks.add(changedLinkSite); return true; } return false; }, _populatePlacesLinks: function () { NewTabUtils.links.populateProviderCache(NewTabUtils.placesProvider, () => { this._handleManyLinksChanged(); }); }, onLinkChanged: function (aProvider, aLink) { // Make sure NewTabUtils.links handles the notification first. setTimeout(() => { if (this._handleLinkChanged(aLink)) { - this._updateRelatedTile(); + this._updateSuggestedTile(); } }, 0); }, onManyLinksChanged: function () { // Make sure NewTabUtils.links handles the notification first. setTimeout(() => { this._handleManyLinksChanged(); }, 0); }, /** - * Chooses and returns a related tile based on a user's top sites - * that we have an available related tile for. + * Record for a url that some number of views have been used + * @param url String url of the suggested link + * @param amount Number of equivalent views to decrease + */ + _decreaseFrequencyCap(url, amount) { + let remainingViews = this._frequencyCaps.get(url) - amount; + this._frequencyCaps.set(url, remainingViews); + + // Reached the number of views, so pick a new one. + if (remainingViews <= 0) { + this._updateSuggestedTile(); + } + }, + + /** + * Chooses and returns a suggested tile based on a user's top sites + * that we have an available suggested tile for. * - * @return the chosen related tile, or undefined if there isn't one + * @return the chosen suggested tile, or undefined if there isn't one */ - _updateRelatedTile: function() { + _updateSuggestedTile: function() { let sortedLinks = NewTabUtils.getProviderLinks(this); if (!sortedLinks) { // If NewTabUtils.links.resetCache() is called before getting here, // sortedLinks may be undefined. return; } - // Delete the current related tile, if one exists. + // Delete the current suggested tile, if one exists. let initialLength = sortedLinks.length; - this.maxNumLinks = initialLength; if (initialLength) { let mostFrecentLink = sortedLinks[0]; if (mostFrecentLink.targetedSite) { this._callObservers("onLinkChanged", { url: mostFrecentLink.url, - frecency: 0, + frecency: SUGGESTED_FRECENCY, lastVisitDate: mostFrecentLink.lastVisitDate, type: mostFrecentLink.type, }, 0, true); } } - if (this._topSitesWithRelatedLinks.size == 0) { - // There are no potential related links we can show. + if (this._topSitesWithSuggestedLinks.size == 0) { + // There are no potential suggested links we can show. return; } - // Create a flat list of all possible links we can show as related. - // Note that many top sites may map to the same related links, but we only - // want to count each related link once (based on url), thus possibleLinks is a map - // from url to relatedLink. Thus, each link has an equal chance of being chosen at + // Create a flat list of all possible links we can show as suggested. + // Note that many top sites may map to the same suggested links, but we only + // want to count each suggested link once (based on url), thus possibleLinks is a map + // from url to suggestedLink. Thus, each link has an equal chance of being chosen at // random from flattenedLinks if it appears only once. let possibleLinks = new Map(); let targetedSites = new Map(); - this._topSitesWithRelatedLinks.forEach(topSiteWithRelatedLink => { - let relatedLinksMap = this._relatedLinks.get(topSiteWithRelatedLink); - relatedLinksMap.forEach((relatedLink, url) => { - possibleLinks.set(url, relatedLink); + this._topSitesWithSuggestedLinks.forEach(topSiteWithSuggestedLink => { + let suggestedLinksMap = this._suggestedLinks.get(topSiteWithSuggestedLink); + suggestedLinksMap.forEach((suggestedLink, url) => { + // Skip this link if we've shown it too many times already + if (this._frequencyCaps.get(url) <= 0) { + return; + } + + possibleLinks.set(url, suggestedLink); // Keep a map of URL to targeted sites. We later use this to show the user // what site they visited to trigger this suggestion. if (!targetedSites.get(url)) { targetedSites.set(url, []); } - targetedSites.get(url).push(topSiteWithRelatedLink); + targetedSites.get(url).push(topSiteWithSuggestedLink); }) }); + + // We might have run out of possible links to show + let numLinks = possibleLinks.size; + if (numLinks == 0) { + return; + } + let flattenedLinks = [...possibleLinks.values()]; - // Choose our related link at random - let relatedIndex = Math.floor(Math.random() * flattenedLinks.length); - let chosenRelatedLink = flattenedLinks[relatedIndex]; + // Choose our suggested link at random + let suggestedIndex = Math.floor(Math.random() * numLinks); + let chosenSuggestedLink = flattenedLinks[suggestedIndex]; // Show the new directory tile. this._callObservers("onLinkChanged", { - url: chosenRelatedLink.url, - title: chosenRelatedLink.title, - frecency: RELATED_FRECENCY, - lastVisitDate: chosenRelatedLink.lastVisitDate, - type: chosenRelatedLink.type, + url: chosenSuggestedLink.url, + title: chosenSuggestedLink.title, + frecency: SUGGESTED_FRECENCY, + lastVisitDate: chosenSuggestedLink.lastVisitDate, + type: chosenSuggestedLink.type, // Choose the first site a user has visited as the target. In the future, // this should be the site with the highest frecency. However, we currently // store frecency by URL not by site. - targetedSite: targetedSites.get(chosenRelatedLink.url).length ? - targetedSites.get(chosenRelatedLink.url)[0] : null + targetedSite: targetedSites.get(chosenSuggestedLink.url).length ? + targetedSites.get(chosenSuggestedLink.url)[0] : null }); - return chosenRelatedLink; + return chosenSuggestedLink; }, /** * Return the object to its pre-init state */ reset: function DirectoryLinksProvider_reset() { delete this.__linksURL; this._removePrefsObserver(); @@ -619,21 +677,21 @@ let DirectoryLinksProvider = { addObserver: function DirectoryLinksProvider_addObserver(aObserver) { this._observers.add(aObserver); }, removeObserver: function DirectoryLinksProvider_removeObserver(aObserver) { this._observers.delete(aObserver); }, - _callObservers: function DirectoryLinksProvider__callObservers(aMethodName, aArg) { + _callObservers(methodName, ...args) { for (let obs of this._observers) { - if (typeof(obs[aMethodName]) == "function") { + if (typeof(obs[methodName]) == "function") { try { - obs[aMethodName](this, aArg); + obs[methodName](this, ...args); } catch (err) { Cu.reportError(err); } } } }, _removeObservers: function() {
--- a/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js +++ b/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js @@ -21,16 +21,17 @@ XPCOMUtils.defineLazyModuleGetter(this, "resource://gre/modules/NetUtil.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils", "resource://gre/modules/NewTabUtils.jsm"); do_get_profile(); const DIRECTORY_LINKS_FILE = "directoryLinks.json"; const DIRECTORY_FRECENCY = 1000; +const SUGGESTED_FRECENCY = Infinity; const kURLData = {"directory": [{"url":"http://example.com","title":"LocalSource"}]}; const kTestURL = 'data:application/json,' + JSON.stringify(kURLData); // DirectoryLinksProvider preferences const kLocalePref = DirectoryLinksProvider._observedPrefs.prefSelectedLocale; const kSourceUrlPref = DirectoryLinksProvider._observedPrefs.linksURL; const kPingUrlPref = "browser.newtabpage.directory.ping"; const kNewtabEnhancedPref = "browser.newtabpage.enhanced"; @@ -56,50 +57,50 @@ const kHttpHandlerData = {}; kHttpHandlerData[kExamplePath] = {"directory": [{"url":"http://example.com","title":"RemoteSource"}]}; const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream"); let gLastRequestPath; -let relatedTile1 = { +let suggestedTile1 = { url: "http://turbotax.com", type: "affiliate", lastVisitDate: 3, frecent_sites: [ "taxact.com", "hrblock.com", "1040.com", "taxslayer.com" ] }; -let relatedTile2 = { +let suggestedTile2 = { url: "http://irs.gov", type: "affiliate", lastVisitDate: 2, frecent_sites: [ "taxact.com", "hrblock.com", "freetaxusa.com", "taxslayer.com" ] }; -let relatedTile3 = { +let suggestedTile3 = { url: "http://hrblock.com", type: "affiliate", lastVisitDate: 1, frecent_sites: [ "taxact.com", "freetaxusa.com", "1040.com", "taxslayer.com" ] }; -let someOtherSite = {url: "http://someothersite.com", title: "Not_A_Related_Site"}; +let someOtherSite = {url: "http://someothersite.com", title: "Not_A_Suggested_Site"}; function getHttpHandler(path) { let code = 200; let body = JSON.stringify(kHttpHandlerData[path]); if (path == kFailPath) { code = 204; } return function(aRequest, aResponse) { @@ -209,21 +210,21 @@ function run_test() { DirectoryLinksProvider.reset(); Services.prefs.clearUserPref(kLocalePref); Services.prefs.clearUserPref(kSourceUrlPref); Services.prefs.clearUserPref(kPingUrlPref); Services.prefs.clearUserPref(kNewtabEnhancedPref); }); } -add_task(function test_updateRelatedTile() { +add_task(function test_updateSuggestedTile() { let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"]; // Initial setup - let data = {"suggested": [relatedTile1, relatedTile2, relatedTile3], "directory": [someOtherSite]}; + let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3], "directory": [someOtherSite]}; let dataURI = 'data:application/json,' + JSON.stringify(data); let testObserver = new TestFirstRun(); DirectoryLinksProvider.addObserver(testObserver); yield promiseSetupDirectoryLinksProvider({linksURL: dataURI}); let links = yield fetchData(); @@ -232,201 +233,337 @@ add_task(function test_updateRelatedTile return topSites.indexOf(site) >= 0; } let origGetProviderLinks = NewTabUtils.getProviderLinks; NewTabUtils.getProviderLinks = function(provider) { return links; } - do_check_eq(DirectoryLinksProvider._updateRelatedTile(), undefined); + do_check_eq(DirectoryLinksProvider._updateSuggestedTile(), undefined); function TestFirstRun() { this.promise = new Promise(resolve => { this.onLinkChanged = (directoryLinksProvider, link) => { links.unshift(link); - let possibleLinks = [relatedTile1.url, relatedTile2.url, relatedTile3.url]; + let possibleLinks = [suggestedTile1.url, suggestedTile2.url, suggestedTile3.url]; - isIdentical([...DirectoryLinksProvider._topSitesWithRelatedLinks], ["hrblock.com", "1040.com", "freetaxusa.com"]); + isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], ["hrblock.com", "1040.com", "freetaxusa.com"]); do_check_true(possibleLinks.indexOf(link.url) > -1); - do_check_eq(link.frecency, Infinity); + do_check_eq(link.frecency, SUGGESTED_FRECENCY); do_check_eq(link.type, "affiliate"); resolve(); }; }); } - function TestChangingRelatedTile() { + function TestChangingSuggestedTile() { this.count = 0; this.promise = new Promise(resolve => { this.onLinkChanged = (directoryLinksProvider, link) => { this.count++; - let possibleLinks = [relatedTile1.url, relatedTile2.url, relatedTile3.url]; + let possibleLinks = [suggestedTile1.url, suggestedTile2.url, suggestedTile3.url]; do_check_true(possibleLinks.indexOf(link.url) > -1); do_check_eq(link.type, "affiliate"); do_check_true(this.count <= 2); if (this.count == 1) { - // The removed related link is the one we added initially. + // The removed suggested link is the one we added initially. do_check_eq(link.url, links.shift().url); - do_check_eq(link.frecency, 0); + do_check_eq(link.frecency, SUGGESTED_FRECENCY); } else { links.unshift(link); - do_check_eq(link.frecency, Infinity); + do_check_eq(link.frecency, SUGGESTED_FRECENCY); } - isIdentical([...DirectoryLinksProvider._topSitesWithRelatedLinks], ["hrblock.com", "freetaxusa.com"]); + isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], ["hrblock.com", "freetaxusa.com"]); resolve(); } }); } - function TestRemovingRelatedTile() { + function TestRemovingSuggestedTile() { this.count = 0; this.promise = new Promise(resolve => { this.onLinkChanged = (directoryLinksProvider, link) => { this.count++; do_check_eq(link.type, "affiliate"); do_check_eq(this.count, 1); - do_check_eq(link.frecency, 0); + do_check_eq(link.frecency, SUGGESTED_FRECENCY); do_check_eq(link.url, links.shift().url); - isIdentical([...DirectoryLinksProvider._topSitesWithRelatedLinks], []); + isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], []); resolve(); } }); } - // Test first call to '_updateRelatedTile()', called when fetching directory links. + // Test first call to '_updateSuggestedTile()', called when fetching directory links. yield testObserver.promise; DirectoryLinksProvider.removeObserver(testObserver); - // Removing a top site that doesn't have a related link should - // not change the current related tile. + // Removing a top site that doesn't have a suggested link should + // not change the current suggested tile. let removedTopsite = topSites.shift(); do_check_eq(removedTopsite, "site0.com"); do_check_false(NewTabUtils.isTopPlacesSite(removedTopsite)); - let updateRelatedTile = DirectoryLinksProvider._handleLinkChanged({ + let updateSuggestedTile = DirectoryLinksProvider._handleLinkChanged({ url: "http://" + removedTopsite, type: "history", }); - do_check_false(updateRelatedTile); + do_check_false(updateSuggestedTile); - // Removing a top site that has a related link should - // remove any current related tile and add a new one. - testObserver = new TestChangingRelatedTile(); + // Removing a top site that has a suggested link should + // remove any current suggested tile and add a new one. + testObserver = new TestChangingSuggestedTile(); DirectoryLinksProvider.addObserver(testObserver); removedTopsite = topSites.shift(); do_check_eq(removedTopsite, "1040.com"); do_check_false(NewTabUtils.isTopPlacesSite(removedTopsite)); DirectoryLinksProvider.onLinkChanged(DirectoryLinksProvider, { url: "http://" + removedTopsite, type: "history", }); yield testObserver.promise; do_check_eq(testObserver.count, 2); DirectoryLinksProvider.removeObserver(testObserver); - // Removing all top sites with related links should remove - // the current related link and not replace it. + // Removing all top sites with suggested links should remove + // the current suggested link and not replace it. topSites = []; - testObserver = new TestRemovingRelatedTile(); + testObserver = new TestRemovingSuggestedTile(); DirectoryLinksProvider.addObserver(testObserver); DirectoryLinksProvider.onManyLinksChanged(); yield testObserver.promise; // Cleanup yield promiseCleanDirectoryLinksProvider(); NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; NewTabUtils.getProviderLinks = origGetProviderLinks; }); -add_task(function test_relatedLinksMap() { - let data = {"suggested": [relatedTile1, relatedTile2, relatedTile3], "directory": [someOtherSite]}; +add_task(function test_suggestedLinksMap() { + let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3], "directory": [someOtherSite]}; let dataURI = 'data:application/json,' + JSON.stringify(data); yield promiseSetupDirectoryLinksProvider({linksURL: dataURI}); let links = yield fetchData(); - // Ensure the related tiles were not considered directory tiles. + // Ensure the suggested tiles were not considered directory tiles. do_check_eq(links.length, 1); - let expected_data = [{url: "http://someothersite.com", title: "Not_A_Related_Site", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}]; + let expected_data = [{url: "http://someothersite.com", title: "Not_A_Suggested_Site", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}]; isIdentical(links, expected_data); - // Check for correctly saved related tiles data. + // Check for correctly saved suggested tiles data. expected_data = { - "taxact.com": [relatedTile1, relatedTile2, relatedTile3], - "hrblock.com": [relatedTile1, relatedTile2], - "1040.com": [relatedTile1, relatedTile3], - "taxslayer.com": [relatedTile1, relatedTile2, relatedTile3], - "freetaxusa.com": [relatedTile2, relatedTile3], + "taxact.com": [suggestedTile1, suggestedTile2, suggestedTile3], + "hrblock.com": [suggestedTile1, suggestedTile2], + "1040.com": [suggestedTile1, suggestedTile3], + "taxslayer.com": [suggestedTile1, suggestedTile2, suggestedTile3], + "freetaxusa.com": [suggestedTile2, suggestedTile3], }; - DirectoryLinksProvider._relatedLinks.forEach((relatedLinks, site) => { - let relatedLinksItr = relatedLinks.values(); + DirectoryLinksProvider._suggestedLinks.forEach((suggestedLinks, site) => { + let suggestedLinksItr = suggestedLinks.values(); for (let link of expected_data[site]) { - isIdentical(relatedLinksItr.next().value, link); + isIdentical(suggestedLinksItr.next().value, link); } }) yield promiseCleanDirectoryLinksProvider(); }); -add_task(function test_topSitesWithRelatedLinks() { +add_task(function test_topSitesWithSuggestedLinks() { let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"]; let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite; NewTabUtils.isTopPlacesSite = function(site) { return topSites.indexOf(site) >= 0; } // Mock out getProviderLinks() so we don't have to populate cache in NewTabUtils let origGetProviderLinks = NewTabUtils.getProviderLinks; NewTabUtils.getProviderLinks = function(provider) { return []; } - // We start off with no top sites with related links. - do_check_eq(DirectoryLinksProvider._topSitesWithRelatedLinks.size, 0); + // We start off with no top sites with suggested links. + do_check_eq(DirectoryLinksProvider._topSitesWithSuggestedLinks.size, 0); - let data = {"suggested": [relatedTile1, relatedTile2, relatedTile3], "directory": [someOtherSite]}; + let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3], "directory": [someOtherSite]}; let dataURI = 'data:application/json,' + JSON.stringify(data); yield promiseSetupDirectoryLinksProvider({linksURL: dataURI}); let links = yield fetchData(); - // Check we've populated related links as expected. - do_check_eq(DirectoryLinksProvider._relatedLinks.size, 5); + // Check we've populated suggested links as expected. + do_check_eq(DirectoryLinksProvider._suggestedLinks.size, 5); - // When many sites change, we update _topSitesWithRelatedLinks as expected. - let expectedTopSitesWithRelatedLinks = ["hrblock.com", "1040.com", "freetaxusa.com"]; + // When many sites change, we update _topSitesWithSuggestedLinks as expected. + let expectedTopSitesWithSuggestedLinks = ["hrblock.com", "1040.com", "freetaxusa.com"]; DirectoryLinksProvider._handleManyLinksChanged(); - isIdentical([...DirectoryLinksProvider._topSitesWithRelatedLinks], expectedTopSitesWithRelatedLinks); + isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks); - // Removing site6.com as a topsite has no impact on _topSitesWithRelatedLinks. + // Removing site6.com as a topsite has no impact on _topSitesWithSuggestedLinks. let popped = topSites.pop(); DirectoryLinksProvider._handleLinkChanged({url: "http://" + popped}); - isIdentical([...DirectoryLinksProvider._topSitesWithRelatedLinks], expectedTopSitesWithRelatedLinks); + isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks); - // Removing freetaxusa.com as a topsite will remove it from _topSitesWithRelatedLinks. + // Removing freetaxusa.com as a topsite will remove it from _topSitesWithSuggestedLinks. popped = topSites.pop(); - expectedTopSitesWithRelatedLinks.pop(); + expectedTopSitesWithSuggestedLinks.pop(); DirectoryLinksProvider._handleLinkChanged({url: "http://" + popped}); - isIdentical([...DirectoryLinksProvider._topSitesWithRelatedLinks], expectedTopSitesWithRelatedLinks); + isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks); - // Re-adding freetaxusa.com as a topsite will add it to _topSitesWithRelatedLinks. + // Re-adding freetaxusa.com as a topsite will add it to _topSitesWithSuggestedLinks. topSites.push(popped); - expectedTopSitesWithRelatedLinks.push(popped); + expectedTopSitesWithSuggestedLinks.push(popped); DirectoryLinksProvider._handleLinkChanged({url: "http://" + popped}); - isIdentical([...DirectoryLinksProvider._topSitesWithRelatedLinks], expectedTopSitesWithRelatedLinks); + isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks); // Cleanup. NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; NewTabUtils.getProviderLinks = origGetProviderLinks; }); +add_task(function test_frequencyCappedSites_views() { + Services.prefs.setCharPref(kPingUrlPref, ""); + let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite; + NewTabUtils.isTopPlacesSite = () => true; + + let testUrl = "http://frequency.capped/link"; + let targets = ["top.site.com"]; + let data = { + suggested: [{ + type: "sponsored", + frecent_sites: targets, + url: testUrl + }], + directory: [{ + type: "organic", + url: "http://directory.site/" + }] + }; + let dataURI = "data:application/json," + JSON.stringify(data); + + yield promiseSetupDirectoryLinksProvider({linksURL: dataURI}); + + // Wait for links to get loaded + let gLinks = NewTabUtils.links; + gLinks.addProvider(DirectoryLinksProvider); + gLinks.populateCache(); + yield new Promise(resolve => { + NewTabUtils.allPages.register({ + observe: _ => _, + update() { + NewTabUtils.allPages.unregister(this); + resolve(); + } + }); + }); + + function synthesizeAction(action) { + DirectoryLinksProvider.reportSitesAction([{ + link: { + targetedSite: targets[0], + url: testUrl + } + }], action, 0); + } + + function checkFirstTypeAndLength(type, length) { + let links = gLinks.getLinks(); + do_check_eq(links[0].type, type); + do_check_eq(links.length, length); + } + + // Make sure we get 5 views of the link before it is removed + checkFirstTypeAndLength("sponsored", 2); + synthesizeAction("view"); + checkFirstTypeAndLength("sponsored", 2); + synthesizeAction("view"); + checkFirstTypeAndLength("sponsored", 2); + synthesizeAction("view"); + checkFirstTypeAndLength("sponsored", 2); + synthesizeAction("view"); + checkFirstTypeAndLength("sponsored", 2); + synthesizeAction("view"); + checkFirstTypeAndLength("organic", 1); + + // Cleanup. + NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; + gLinks.removeProvider(DirectoryLinksProvider); + DirectoryLinksProvider.removeObserver(gLinks); + Services.prefs.setCharPref(kPingUrlPref, kPingUrl); +}); + +add_task(function test_frequencyCappedSites_click() { + Services.prefs.setCharPref(kPingUrlPref, ""); + let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite; + NewTabUtils.isTopPlacesSite = () => true; + + let testUrl = "http://frequency.capped/link"; + let targets = ["top.site.com"]; + let data = { + suggested: [{ + type: "sponsored", + frecent_sites: targets, + url: testUrl + }], + directory: [{ + type: "organic", + url: "http://directory.site/" + }] + }; + let dataURI = "data:application/json," + JSON.stringify(data); + + yield promiseSetupDirectoryLinksProvider({linksURL: dataURI}); + + // Wait for links to get loaded + let gLinks = NewTabUtils.links; + gLinks.addProvider(DirectoryLinksProvider); + gLinks.populateCache(); + yield new Promise(resolve => { + NewTabUtils.allPages.register({ + observe: _ => _, + update() { + NewTabUtils.allPages.unregister(this); + resolve(); + } + }); + }); + + function synthesizeAction(action) { + DirectoryLinksProvider.reportSitesAction([{ + link: { + targetedSite: targets[0], + url: testUrl + } + }], action, 0); + } + + function checkFirstTypeAndLength(type, length) { + let links = gLinks.getLinks(); + do_check_eq(links[0].type, type); + do_check_eq(links.length, length); + } + + // Make sure the link disappears after the first click + checkFirstTypeAndLength("sponsored", 2); + synthesizeAction("view"); + checkFirstTypeAndLength("sponsored", 2); + synthesizeAction("click"); + checkFirstTypeAndLength("organic", 1); + + // Cleanup. + NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; + gLinks.removeProvider(DirectoryLinksProvider); + DirectoryLinksProvider.removeObserver(gLinks); + Services.prefs.setCharPref(kPingUrlPref, kPingUrl); +}); + add_task(function test_reportSitesAction() { yield DirectoryLinksProvider.init(); let deferred, expectedPath, expectedPost; let done = false; server.registerPrefixHandler(kPingPath, (aRequest, aResponse) => { if (done) { return; }
--- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp +++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp @@ -223,16 +223,17 @@ BluetoothHfpManager::Cleanup() mSignal = 0; mController = nullptr; } void BluetoothHfpManager::Reset() { + mFirstCKPD = false; // Phone & Device CIND ResetCallArray(); // Clear Sco state mAudioState = HFP_AUDIO_STATE_DISCONNECTED; Cleanup(); } bool @@ -1281,16 +1282,18 @@ BluetoothHfpManager::Disconnect(Bluetoot new DisconnectResultHandler(this)); } void BluetoothHfpManager::OnConnect(const nsAString& aErrorStr) { MOZ_ASSERT(NS_IsMainThread()); + mFirstCKPD = true; + /** * On the one hand, notify the controller that we've done for outbound * connections. On the other hand, we do nothing for inbound connections. */ NS_ENSURE_TRUE_VOID(mController); mController->NotifyCompletion(aErrorStr); mController = nullptr; @@ -1608,39 +1611,49 @@ BluetoothHfpManager::KeyPressedNotificat MOZ_ASSERT(NS_IsMainThread()); bool hasActiveCall = (FindFirstCall(nsITelephonyService::CALL_STATE_CONNECTED) > 0); // Refer to AOSP HeadsetStateMachine.processKeyPressed if (FindFirstCall(nsITelephonyService::CALL_STATE_INCOMING) && !hasActiveCall) { - /** + /* * Bluetooth HSP spec 4.2.2 * There is an incoming call, notify Dialer to pick up the phone call * and SCO will be established after we get the CallStateChanged event * indicating the call is answered successfully. */ NotifyDialer(NS_LITERAL_STRING("ATA")); } else if (hasActiveCall) { if (!IsScoConnected()) { - /** + /* * Bluetooth HSP spec 4.3 * If there's no SCO, set up a SCO link. */ ConnectSco(); - } else { - /** + } else if (mFirstCKPD) { + /* + * Bluetooth HSP spec 4.2 & 4.3 + * The SCO link connection may be set up prior to receiving the AT+CKPD=200 + * command from the HS. + * + * Once FxOS initiates a SCO connection before receiving the + * AT+CKPD=200, we should ignore this key press. + */ + } else { + /* * Bluetooth HSP spec 4.5 * There are two ways to release SCO: sending CHUP to dialer or closing * SCO socket directly. We notify dialer only if there is at least one * active call. */ NotifyDialer(NS_LITERAL_STRING("CHUP")); } + mFirstCKPD = false; } else { // BLDN mDialingRequestProcessed = false; NotifyDialer(NS_LITERAL_STRING("BLDN")); MessageLoop::current()->PostDelayedTask(FROM_HERE, new RespondToBLDNTask(),
--- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.h +++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.h @@ -204,16 +204,17 @@ private: BluetoothHandsfreeNetworkState mService; BluetoothHandsfreeServiceType mRoam; int mSignal; int mCurrentVgs; int mCurrentVgm; bool mReceiveVgsFlag; bool mDialingRequestProcessed; + bool mFirstCKPD; PhoneType mPhoneType; nsString mDeviceAddress; nsString mMsisdn; nsString mOperatorName; nsTArray<Call> mCurrentCallArray; nsAutoPtr<BluetoothRilListener> mListener; nsRefPtr<BluetoothProfileController> mController;
--- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -1,45 +1,32 @@ /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- * 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/. */ package org.mozilla.gecko; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.lang.reflect.Method; -import java.net.URLEncoder; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Vector; - -import org.json.JSONException; -import org.json.JSONObject; import org.mozilla.gecko.AppConstants.Versions; import org.mozilla.gecko.DynamicToolbar.PinReason; import org.mozilla.gecko.DynamicToolbar.VisibilityTransition; import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException; import org.mozilla.gecko.Tabs.TabEvents; import org.mozilla.gecko.animation.PropertyAnimator; import org.mozilla.gecko.animation.TransitionsTracker; import org.mozilla.gecko.animation.ViewHelper; import org.mozilla.gecko.db.BrowserContract.Combined; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.db.SuggestedSites; import org.mozilla.gecko.distribution.Distribution; import org.mozilla.gecko.favicons.Favicons; import org.mozilla.gecko.favicons.LoadFaviconTask; import org.mozilla.gecko.favicons.OnFaviconLoadedListener; import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry; +import org.mozilla.gecko.firstrun.FirstrunPane; import org.mozilla.gecko.fxa.FirefoxAccounts; import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity; import org.mozilla.gecko.gfx.BitmapUtils; import org.mozilla.gecko.gfx.ImmutableViewportMetrics; import org.mozilla.gecko.gfx.LayerMarginsAnimator; import org.mozilla.gecko.gfx.LayerView; import org.mozilla.gecko.health.BrowserHealthRecorder; import org.mozilla.gecko.health.BrowserHealthReporter; @@ -52,28 +39,28 @@ import org.mozilla.gecko.home.HomePager. import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; import org.mozilla.gecko.home.HomePanelsManager; import org.mozilla.gecko.home.SearchEngine; import org.mozilla.gecko.menu.GeckoMenu; import org.mozilla.gecko.menu.GeckoMenuItem; import org.mozilla.gecko.mozglue.ContextUtils; import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent; import org.mozilla.gecko.mozglue.RobocopTarget; -import org.mozilla.gecko.firstrun.FirstrunPane; import org.mozilla.gecko.overlays.ui.ShareDialog; import org.mozilla.gecko.preferences.ClearOnShutdownPref; import org.mozilla.gecko.preferences.GeckoPreferences; import org.mozilla.gecko.prompts.Prompt; import org.mozilla.gecko.prompts.PromptListItem; import org.mozilla.gecko.sync.setup.SyncAccounts; +import org.mozilla.gecko.tabqueue.TabQueueHelper; import org.mozilla.gecko.tabs.TabHistoryController; +import org.mozilla.gecko.tabs.TabHistoryController.OnShowTabHistory; import org.mozilla.gecko.tabs.TabHistoryFragment; import org.mozilla.gecko.tabs.TabHistoryPage; import org.mozilla.gecko.tabs.TabsPanel; -import org.mozilla.gecko.tabs.TabHistoryController.OnShowTabHistory; import org.mozilla.gecko.toolbar.AutocompleteHandler; import org.mozilla.gecko.toolbar.BrowserToolbar; import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState; import org.mozilla.gecko.toolbar.ToolbarProgressView; import org.mozilla.gecko.util.ActivityUtils; import org.mozilla.gecko.util.Clipboard; import org.mozilla.gecko.util.EventCallback; import org.mozilla.gecko.util.GamepadUtils; @@ -134,16 +121,30 @@ import android.view.ViewGroup; import android.view.ViewStub; import android.view.ViewTreeObserver; import android.view.Window; import android.view.animation.Interpolator; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.Toast; import android.widget.ViewFlipper; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URLEncoder; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Vector; public class BrowserApp extends GeckoApp implements TabsPanel.TabsLayoutChangeListener, PropertyAnimator.PropertyAnimationListener, View.OnKeyListener, LayerView.OnMetricsChangedListener, BrowserSearch.OnSearchListener, BrowserSearch.OnEditSuggestionListener, @@ -900,16 +901,29 @@ public class BrowserApp extends GeckoApp } @Override public void onAttachedToWindow() { // We can't show the first run experience until Gecko has finished initialization (bug 1077583). checkFirstrun(this, new SafeIntent(getIntent())); } + private void processTabQueue() { + if (AppConstants.NIGHTLY_BUILD && AppConstants.MOZ_ANDROID_TAB_QUEUE) { + ThreadUtils.postToBackgroundThread(new Runnable() { + @Override + public void run() { + if (TabQueueHelper.shouldOpenTabQueueUrls(BrowserApp.this)) { + TabQueueHelper.openQueuedUrls(BrowserApp.this, mProfile, TabQueueHelper.FILE_NAME); + } + } + }); + } + } + @Override public void onResume() { super.onResume(); final String args = ContextUtils.getStringExtra(getIntent(), "args"); // If an external intent tries to start Fennec in guest mode, and it's not already // in guest mode, this will change modes before opening the url. // NOTE: OnResume is called twice sometimes when showing on the lock screen. @@ -918,16 +932,18 @@ public class BrowserApp extends GeckoApp if (enableGuestSession != inGuestSession) { doRestart(getIntent()); GeckoAppShell.gracefulExit(); return; } EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener)this, "Prompt:ShowTop"); + + processTabQueue(); } @Override public void onPause() { super.onPause(); // Register for Prompt:ShowTop so we can foreground this activity even if it's hidden. EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener)this, "Prompt:ShowTop"); @@ -3375,16 +3391,17 @@ public class BrowserApp extends GeckoApp * open a new tab with about:feedback when launching the app from the icon shortcut. */ @Override protected void onNewIntent(Intent intent) { String action = intent.getAction(); final boolean isViewAction = Intent.ACTION_VIEW.equals(action); final boolean isBookmarkAction = GeckoApp.ACTION_HOMESCREEN_SHORTCUT.equals(action); + final boolean isTabQueueAction = TabQueueHelper.LOAD_URLS_ACTION.equals(action); if (mInitialized && (isViewAction || isBookmarkAction)) { // Dismiss editing mode if the user is loading a URL from an external app. mBrowserToolbar.cancelEdit(); // Hide firstrun-pane if the user is loading a URL from an external app. hideFirstrunPager(); @@ -3403,16 +3420,27 @@ public class BrowserApp extends GeckoApp GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri)); } // Only solicit feedback when the app has been launched from the icon shortcut. if (GuestSession.NOTIFICATION_INTENT.equals(action)) { GuestSession.handleIntent(this, intent); } + // If the user has clicked the tab queue notification then load the tabs. + if(AppConstants.NIGHTLY_BUILD && AppConstants.MOZ_ANDROID_TAB_QUEUE && mInitialized && isTabQueueAction) { + int queuedTabCount = TabQueueHelper.getTabQueueLength(this); + TabQueueHelper.openQueuedUrls(this, mProfile, TabQueueHelper.FILE_NAME); + + // If there's more than one tab then also show the tabs panel. + if (queuedTabCount > 1) { + showNormalTabs(); + } + } + if (!mInitialized || !Intent.ACTION_MAIN.equals(action)) { return; } // Check to see how many times the app has been launched. final String keyName = getPackageName() + ".feedback_launch_count"; final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
--- a/mobile/android/base/DoorHangerPopup.java +++ b/mobile/android/base/DoorHangerPopup.java @@ -17,16 +17,17 @@ import org.mozilla.gecko.util.GeckoEvent import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.gecko.widget.AnchoredPopup; import org.mozilla.gecko.widget.DoorHanger; import android.content.Context; import android.util.Log; import android.view.View; import android.widget.CheckBox; +import org.mozilla.gecko.widget.DoorhangerConfig; public class DoorHangerPopup extends AnchoredPopup implements GeckoEventListener, Tabs.OnTabsChangedListener, DoorHanger.OnButtonClickListener { private static final String LOGTAG = "GeckoDoorHangerPopup"; // Stores a set of all active DoorHanger notifications. A DoorHanger is @@ -71,26 +72,22 @@ public class DoorHangerPopup extends Anc mDisabled = false; updatePopup(); } @Override public void handleMessage(String event, JSONObject geckoObject) { try { if (event.equals("Doorhanger:Add")) { - final int tabId = geckoObject.getInt("tabID"); - final String value = geckoObject.getString("value"); - final String message = geckoObject.getString("message"); - final JSONArray buttons = geckoObject.getJSONArray("buttons"); - final JSONObject options = geckoObject.getJSONObject("options"); + final DoorhangerConfig config = makeConfigFromJSON(geckoObject); ThreadUtils.postToUiThread(new Runnable() { @Override public void run() { - addDoorHanger(tabId, value, message, buttons, options); + addDoorHanger(config); } }); } else if (event.equals("Doorhanger:Remove")) { final int tabId = geckoObject.getInt("tabID"); final String value = geckoObject.getString("value"); ThreadUtils.postToUiThread(new Runnable() { @Override @@ -104,16 +101,32 @@ public class DoorHangerPopup extends Anc } }); } } catch (Exception e) { Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e); } } + private DoorhangerConfig makeConfigFromJSON(JSONObject json) throws JSONException { + final int tabId = json.getInt("tabID"); + final String id = json.getString("value"); + final DoorhangerConfig config = new DoorhangerConfig(tabId, id); + + config.setMessage(json.getString("message")); + config.setButtons(json.getJSONArray("buttons")); + config.setOptions(json.getJSONObject("options")); + final String typeString = json.optString("category"); + if (DoorHanger.Type.LOGIN.toString().equals(typeString)) { + config.setType(DoorHanger.Type.LOGIN); + } + + return config; + } + // This callback is automatically executed on the UI thread. @Override public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final Object data) { switch(msg) { case CLOSED: // Remove any doorhangers for a tab when it's closed (make // a temporary set to avoid a ConcurrentModificationException) HashSet<DoorHanger> doorHangersToRemove = new HashSet<DoorHanger>(); @@ -144,37 +157,36 @@ public class DoorHangerPopup extends Anc } } /** * Adds a doorhanger. * * This method must be called on the UI thread. */ - void addDoorHanger(final int tabId, final String value, final String message, - final JSONArray buttons, final JSONObject options) { + void addDoorHanger(DoorhangerConfig config) { + final int tabId = config.getTabId(); // Don't add a doorhanger for a tab that doesn't exist if (Tabs.getInstance().getTab(tabId) == null) { return; } // Replace the doorhanger if it already exists - DoorHanger oldDoorHanger = getDoorHanger(tabId, value); + DoorHanger oldDoorHanger = getDoorHanger(tabId, config.getId()); if (oldDoorHanger != null) { removeDoorHanger(oldDoorHanger); } if (!mInflated) { init(); } - final DoorHanger newDoorHanger = new DoorHanger(mContext, tabId, value); - newDoorHanger.setMessage(message); - newDoorHanger.setOptions(options); + final DoorHanger newDoorHanger = DoorHanger.Get(mContext, config); + final JSONArray buttons = config.getButtons(); for (int i = 0; i < buttons.length(); i++) { try { JSONObject buttonObject = buttons.getJSONObject(i); String label = buttonObject.getString("label"); String tag = String.valueOf(buttonObject.getInt("callback")); newDoorHanger.addButton(label, tag, this); } catch (JSONException e) { Log.e(LOGTAG, "Error creating doorhanger button", e); @@ -225,17 +237,17 @@ public class DoorHangerPopup extends Anc /** * Gets a doorhanger. * * This method must be called on the UI thread. */ DoorHanger getDoorHanger(int tabId, String value) { for (DoorHanger dh : mDoorHangers) { - if (dh.getTabId() == tabId && dh.getValue().equals(value)) + if (dh.getTabId() == tabId && dh.getIdentifier().equals(value)) return dh; } // If there's no doorhanger for the given tabId and value, return null return null; } /**
--- a/mobile/android/base/GeckoProfile.java +++ b/mobile/android/base/GeckoProfile.java @@ -22,16 +22,17 @@ import java.util.regex.Pattern; import org.json.JSONException; import org.json.JSONArray; import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException; import org.mozilla.gecko.GeckoProfileDirectories.NoSuchProfileException; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.db.LocalBrowserDB; import org.mozilla.gecko.db.StubBrowserDB; import org.mozilla.gecko.distribution.Distribution; +import org.mozilla.gecko.mozglue.ContextUtils; import org.mozilla.gecko.mozglue.RobocopTarget; import org.mozilla.gecko.firstrun.FirstrunPane; import org.mozilla.gecko.util.INIParser; import org.mozilla.gecko.util.INISection; import android.app.Activity; import android.content.ContentResolver; import android.content.Context; @@ -148,17 +149,17 @@ public final class GeckoProfile { final GeckoApp geckoApp = (GeckoApp) context; if (geckoApp.mProfile != null) { return geckoApp.mProfile; } } final String args; if (context instanceof Activity) { - args = ((Activity) context).getIntent().getStringExtra("args"); + args = ContextUtils.getStringExtra(((Activity) context).getIntent(), "args"); } else { args = null; } if (GuestSession.shouldUse(context, args)) { final GeckoProfile p = GeckoProfile.getOrCreateGuestProfile(context); if (isGeckoApp) { ((GeckoApp) context).mProfile = p; @@ -674,16 +675,24 @@ public final class GeckoProfile { read = fr.read(buf); } return sb.toString(); } finally { fr.close(); } } + public boolean deleteFileFromProfileDir(String fileName) throws IllegalArgumentException { + if (TextUtils.isEmpty(fileName)) { + throw new IllegalArgumentException("Filename cannot be empty."); + } + File file = new File(getDir(), fileName); + return file.delete(); + } + private boolean remove() { try { synchronized (this) { final File dir = getDir(); if (dir.exists()) { delete(dir); }
--- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -484,30 +484,33 @@ gbjar.sources += [ 'widget/AllCapsTextView.java', 'widget/AnchoredPopup.java', 'widget/AnimatedHeightLayout.java', 'widget/BasicColorPicker.java', 'widget/ButtonToast.java', 'widget/CheckableLinearLayout.java', 'widget/ClickableWhenDisabledEditText.java', 'widget/DateTimePicker.java', + 'widget/DefaultDoorHanger.java', 'widget/Divider.java', 'widget/DoorHanger.java', + 'widget/DoorhangerConfig.java', 'widget/EllipsisTextView.java', 'widget/FadedMultiColorTextView.java', 'widget/FadedSingleColorTextView.java', 'widget/FadedTextView.java', 'widget/FaviconView.java', 'widget/FloatingHintEditText.java', 'widget/FlowLayout.java', 'widget/GeckoActionProvider.java', 'widget/GeckoPopupMenu.java', 'widget/GeckoSwipeRefreshLayout.java', 'widget/GeckoViewFlipper.java', 'widget/IconTabWidget.java', + 'widget/LoginDoorHanger.java', 'widget/ResizablePathDrawable.java', 'widget/SquaredImageView.java', 'widget/SwipeDismissListViewTouchListener.java', 'widget/TabThumbnailWrapper.java', 'widget/ThumbnailView.java', 'widget/TwoWayView.java', 'ZoomConstraints.java', 'ZoomedView.java',
--- a/mobile/android/base/reading/LocalReadingListStorage.java +++ b/mobile/android/base/reading/LocalReadingListStorage.java @@ -261,25 +261,27 @@ public class LocalReadingListStorage imp public Cursor getModifiedWithSelection(final String selection) { final String[] projection = new String[] { ReadingListItems.GUID, ReadingListItems.IS_FAVORITE, ReadingListItems.RESOLVED_TITLE, ReadingListItems.RESOLVED_URL, ReadingListItems.EXCERPT, + // TODO: ReadingListItems.IS_ARTICLE, + // TODO: ReadingListItems.WORD_COUNT, }; - try { return client.query(URI_WITHOUT_DELETED, projection, selection, null, null); } catch (RemoteException e) { throw new IllegalStateException(e); } } + @Override public Cursor getModified() { final String selection = ReadingListItems.SYNC_STATUS + " = " + ReadingListItems.SYNC_STATUS_MODIFIED; return getModifiedWithSelection(selection); } // Return changed items that aren't just status changes. // This isn't necessary because we insist on processing status changes before modified items.
--- a/mobile/android/base/reading/ReadingListClient.java +++ b/mobile/android/base/reading/ReadingListClient.java @@ -477,17 +477,17 @@ public class ReadingListClient { final BaseResource r = getRelativeArticleResource(guid); r.delegate = new DelegatingUploadResourceDelegate(r, auth, ReadingListRecordResponse.FACTORY, up, uploadDelegate); final ExtendedJSONObject body = up.toJSON(); if (ReadingListConstants.DEBUG) { Logger.info(LOG_TAG, "Patching record " + guid + ": " + body.toJSONString()); } - r.post(body); + r.patch(body); } /** * Mutates the provided queue. */ public void add(final Queue<ClientReadingListRecord> queue, final Executor executor, final ReadingListRecordUploadDelegate batchUploadDelegate) { if (queue.isEmpty()) { batchUploadDelegate.onBatchDone();
--- a/mobile/android/base/reading/ReadingListConstants.java +++ b/mobile/android/base/reading/ReadingListConstants.java @@ -5,14 +5,14 @@ package org.mozilla.gecko.reading; import org.mozilla.gecko.AppConstants; public class ReadingListConstants { public static final String GLOBAL_LOG_TAG = "FxReadingList"; public static final String USER_AGENT = "Firefox-Android-FxReader/" + AppConstants.MOZ_APP_VERSION + " (" + AppConstants.MOZ_APP_DISPLAYNAME + ")"; public static final String DEFAULT_DEV_ENDPOINT = "https://readinglist.dev.mozaws.net/v1/"; - public static final String DEFAULT_PROD_ENDPOINT = null; // TODO + public static final String DEFAULT_PROD_ENDPOINT = "https://readinglist.services.mozilla.com/v1/"; public static final String OAUTH_ENDPOINT_PROD = "https://oauth.accounts.firefox.com/v1"; public static boolean DEBUG = false; }
--- a/mobile/android/base/reading/ReadingListSynchronizer.java +++ b/mobile/android/base/reading/ReadingListSynchronizer.java @@ -243,21 +243,20 @@ public class ReadingListSynchronizer { try { acc.finish(); } catch (Exception e) { next.fail(e); return; } if (failures == 0) { - try { - next.next(); - } catch (Exception e) { - } + next.next(); + return; } + next.fail(); } } private Queue<ClientReadingListRecord> collectStatusChangesFromCursor(final Cursor cursor) { try { final Queue<ClientReadingListRecord> toUpload = new LinkedList<>(); @@ -300,16 +299,159 @@ public class ReadingListSynchronizer { } return toUpload; } finally { cursor.close(); } } + private static class ModifiedUploadDelegate implements ReadingListRecordUploadDelegate { + private final ReadingListChangeAccumulator acc; + + public volatile int failures = 0; + private final StageDelegate next; + + ModifiedUploadDelegate(ReadingListChangeAccumulator acc, StageDelegate next) { + this.acc = acc; + this.next = next; + } + + @Override + public void onInvalidUpload(ClientReadingListRecord up, + ReadingListResponse response) { + recordFailed(up); + } + + @Override + public void onConflict(ClientReadingListRecord up, + ReadingListResponse response) { + // This can happen for a material change. + failures++; + } + + @Override + public void onSuccess(ClientReadingListRecord up, + ReadingListRecordResponse response, + ServerReadingListRecord down) { + if (!TextUtils.equals(up.getGUID(), down.getGUID())) { + // Uh oh! + // This should never occur. We should get an onConflict instead, + // so this would imply a server bug, or something like a truncated + // over-long GUID string. + // + // Should we wish to recover from this case, probably the right approach + // is to ensure that the GUID is overwritten locally (given that we know + // the numeric ID). + } + + // We could upload our material changes but get back additional status + // changes from the server. Apply them. + acc.addChangedRecord(up.givenServerRecord(down)); + } + + @Override + public void onBadRequest(ClientReadingListRecord up, MozResponse response) { + recordFailed(up); + } + + @Override + public void onFailure(ClientReadingListRecord up, Exception ex) { + recordFailed(up); + } + + @Override + public void onFailure(ClientReadingListRecord up, MozResponse response) { + // Since we download and apply remote changes before uploading local changes, the conflict + // window is very small. We should essentially never see true conflicts here. + if (response.getStatusCode() == 404) { + // We shouldn't see a 404; we should see a record with deleted=true when + // we fetch remote changes. + Logger.warn(LOG_TAG, "Ignoring 404 response patching record with guid: " + up.getGUID()); + } else if (response.getStatusCode() == 409) { + // A 409 indicates that resolved_url has collided with an existing + // record. Not much to be done here. + Logger.info(LOG_TAG, "409 response seen; deleting record with guid: " + up.getGUID()); + acc.addDeletion(up); + } else { + // We should never see a 412 since we race to upload our changes (and + // accept whatever the server gives us back). + recordFailed(up); + } + } + + private void recordFailed(ClientReadingListRecord up) { + ++failures; + } + + @Override + public void onBatchDone() { + try { + acc.finish(); + } catch (Exception e) { + next.fail(e); + return; + } + + if (failures == 0) { + next.next(); + return; + } + + next.fail(); + } + } + + private Queue<ClientReadingListRecord> collectModifiedFromCursor(final Cursor cursor) { + try { + final Queue<ClientReadingListRecord> toUpload = new LinkedList<>(); + + final int columnGUID = cursor.getColumnIndexOrThrow(ReadingListItems.GUID); + final int columnExcerpt = cursor.getColumnIndexOrThrow(ReadingListItems.EXCERPT); + final int columnResolvedURL = cursor.getColumnIndexOrThrow(ReadingListItems.RESOLVED_URL); + final int columnResolvedTitle = cursor.getColumnIndexOrThrow(ReadingListItems.RESOLVED_TITLE); + // TODO: final int columnIsArticle = cursor.getColumnIndexOrThrow(ReadingListItems.IS_ARTICLE); + // TODO: final int columnWordCount = cursor.getColumnIndexOrThrow(ReadingListItems.WORD_COUNT); + + while (cursor.moveToNext()) { + final String guid = cursor.getString(columnGUID); + if (guid == null) { + // Nothing we can do here, but this should never happen: we should + // have uploaded this record as new before trying to upload a + // material modification! + continue; + } + + final ExtendedJSONObject o = new ExtendedJSONObject(); + o.put("id", guid); + final String excerpt = cursor.getString(columnExcerpt); // Can be NULL. + final String resolvedURL = cursor.getString(columnResolvedURL); // Can be NULL. + final String resolvedTitle = cursor.getString(columnResolvedTitle); // Can be NULL. + if (excerpt == null && resolvedURL == null && resolvedTitle == null) { + // Nothing material to upload, so skip this record. + continue; + } + o.put("excerpt", excerpt); + o.put("resolved_url", resolvedURL); + o.put("resolved_title", resolvedTitle); + // TODO: o.put("is_article", cursor.getInt(columnIsArticle) == 1); + // TODO: o.put("word_count", cursor.getInt(columnWordCount)); + + final ClientMetadata cm = null; + final ServerMetadata sm = new ServerMetadata(guid, -1L); + final ClientReadingListRecord record = new ClientReadingListRecord(sm, cm, o); + toUpload.add(record); + } + + return toUpload; + } finally { + cursor.close(); + } + } + private Queue<ClientReadingListRecord> accumulateNewItems(Cursor cursor) { try { final Queue<ClientReadingListRecord> toUpload = new LinkedList<>(); final ReadingListClientRecordFactory factory = new ReadingListClientRecordFactory(cursor); ClientReadingListRecord record; while ((record = factory.getNext()) != null) { toUpload.add(record); @@ -330,18 +472,21 @@ public class ReadingListSynchronizer { delegate.fail(new RuntimeException("Unable to get unread item cursor.")); return; } final Queue<ClientReadingListRecord> toUpload = collectStatusChangesFromCursor(cursor); // Nothing to do. if (toUpload.isEmpty()) { + Logger.debug(LOG_TAG, "No new unread changes to upload. Skipping."); delegate.next(); return; + } else { + Logger.debug(LOG_TAG, "Uploading " + toUpload.size() + " new unread changes."); } // Upload each record. This looks like batching, but it's really chained serial requests. final ReadingListChangeAccumulator acc = this.local.getChangeAccumulator(); final StatusUploadDelegate uploadDelegate = new StatusUploadDelegate(acc, delegate); // Don't send I-U-S; in the case of favorites we're // happy to overwrite the server value, and in the case of unread status @@ -363,16 +508,18 @@ public class ReadingListSynchronizer { Queue<ClientReadingListRecord> toUpload = accumulateNewItems(cursor); // Nothing to do. if (toUpload.isEmpty()) { Logger.debug(LOG_TAG, "No new items to upload. Skipping."); delegate.next(); return; + } else { + Logger.debug(LOG_TAG, "Uploading " + toUpload.size() + " new items."); } final ReadingListChangeAccumulator acc = this.local.getChangeAccumulator(); final NewItemUploadDelegate uploadDelegate = new NewItemUploadDelegate(acc, new StageDelegate() { private boolean tryFlushChanges() { Logger.debug(LOG_TAG, "Flushing post-upload changes."); try { acc.finish(); @@ -415,19 +562,89 @@ public class ReadingListSynchronizer { // ... we need to apply it locally. this.remote.add(toUpload, executor, uploadDelegate); } catch (Exception e) { delegate.fail(e); return; } } - private void uploadModified(final StageDelegate delegate) { - // TODO - delegate.next(); + protected void uploadModified(final StageDelegate delegate) { + try { + // This looks strange because modified includes material changes and + // status changes, but this is called after status changes have been + // uploaded and removed from local storage. So what's left should be + // material changes. Even so, it should be safe to upload status changes + // here. + final Cursor cursor = this.local.getModified(); + + if (cursor == null) { + delegate.fail(new RuntimeException("Unable to get modified item cursor.")); + return; + } + + final Queue<ClientReadingListRecord> toUpload = collectModifiedFromCursor(cursor); + + // Nothing to do. + if (toUpload.isEmpty()) { + Logger.debug(LOG_TAG, "No modified items to upload. Skipping."); + delegate.next(); + return; + } else { + Logger.debug(LOG_TAG, "Uploading " + toUpload.size() + " modified items."); + } + + final ReadingListChangeAccumulator acc = this.local.getChangeAccumulator(); + final ModifiedUploadDelegate uploadDelegate = new ModifiedUploadDelegate(acc, new StageDelegate() { + private boolean tryFlushChanges() { + Logger.debug(LOG_TAG, "Flushing post-upload changes."); + try { + acc.finish(); + return true; + } catch (Exception e) { + Logger.warn(LOG_TAG, "Flushing changes failed! This sync went wrong.", e); + delegate.fail(e); + return false; + } + } + + @Override + public void next() { + Logger.debug(LOG_TAG, "Modified items uploaded successfully."); + + if (tryFlushChanges()) { + delegate.next(); + } + } + + @Override + public void fail() { + Logger.warn(LOG_TAG, "Couldn't upload modified items."); + if (tryFlushChanges()) { + delegate.fail(); + } + } + + @Override + public void fail(Exception e) { + Logger.warn(LOG_TAG, "Couldn't upload modified items.", e); + if (tryFlushChanges()) { + delegate.fail(e); + } + } + }); + + // Handle 201 for success, 400 for invalid, 303 for redirect. + // TODO: 200 == "was already on the server, we didn't touch it, here it is." + // ... we need to apply it locally. + this.remote.patch(toUpload, executor, uploadDelegate); + } catch (Exception e) { + delegate.fail(e); + return; + } } private void downloadIncoming(final long since, final StageDelegate delegate) { final ReadingListChangeAccumulator postDownload = this.local.getChangeAccumulator(); final FetchSpec spec = new FetchSpec.Builder().setSince(since).build(); // TODO: should we flush the accumulator if we get a failure?
--- a/mobile/android/base/reading/ServerReadingListRecord.java +++ b/mobile/android/base/reading/ServerReadingListRecord.java @@ -23,9 +23,13 @@ public class ServerReadingListRecord ext public String getTitle() { return this.fields.getString("title"); // TODO: resolved_title } @Override public String getAddedBy() { return this.fields.getString("added_by"); } -} \ No newline at end of file + + public String getExcerpt() { + return this.fields.getString("excerpt"); + } +}
new file mode 100644 index 0000000000000000000000000000000000000000..e6a7ab9f11dd496b13af8ae1294df877ad031349 GIT binary patch literal 1732 zc$@*m20QtQP)<h;3K|Lk000e1NJLTq002Ay002A)1^@s6I{evk00001b5ch_0Itp) z=>Px*en~_@RA>d=TWf3-RTRE=W_FiC6CN5s0TV^TL!lrcAVkGT_+ip+cU!7Z#egXU ze-KTSAB02_V-yn;e)tFxl~h4(_q~k<Omz8Se3AwlV<IMk1h5j66)nqdXO7=poOZi* zcV~7UyWuA7+`0GMbIy0~Ip@A+IjghO)6+A{<MXvM;`b3}v+<nGIh#pDX~y{l#^?ek zb_(Tvx!l!MM@L823<Pe5*eTjQd-f<zP4{jgiEZMHuYw$-di}+SK2<sGZTI{8jp%2T zu@PQX(S&2MwVX@)0QByv7+h~e0RCYv%XW8kwER+zPt5>#cXv-}oILeCD6qbqf@bPq z_F2Yhd(iKHw^X~D0FFc=)0wRFVN{owQp{R8?aN)uJO}?)i}}dK1x~+5qtUrc@}xND z_c$kuuasqF@q+pDdQ+(s70QtcU!2bG-=CbwxST-Lo$ItLMD*6CrORF}mg6em=`-jJ zD6+6v2?yVIMx*h^3;Da|!{KP+2}$DJg~Hv)Bar=_8KOHkZuAcdd9I8w7872Hvsc^z zSS$mvqR}@=dO<ID1+e06=!7ZXstc|5iRg1qBi5N=+AQ%W@}jH~KY;8Cg+g};I;9e^ z4fVopp0~;;4@}K9wFQHh3V0pH6nBJV&j|>bRZw1>Rk+9ZEDE1?e1s#B<Z49a1;rYe zeJ5f&gJZ$wS%@-GSBalQ<OdCR958WBtc@+FEZ0({n3Ss<X#<xEhZ8Ft2aI*{$`PR! zGTz}~ShcmpW!CICV5n#dU?zJXDh1a|(wb6rh2`+`c_N@VmM|SPdVCF6>=2*_tzth$ z(7K6L4qAB?;OpNy9i<!X04sQiC@i9JIF{V0`)qz>IeS&q8I&`}QNXItCwUBNVuVZp zL&QHcL0>PKv`@61=BN+n(&@AjAg<96{q88>#>OdYT?1o45YEWXIR+S;g{NS7VFP-& zMuNjXX8?y|iCx$k9l*lf^#g{oy+@QX>GoyWiAo-cCH4X6j*65uAjpt<o2^kg8clRz zS5%i^8L{IVT3gTB>cbk>F4kb+42i^={!Kw>E8w=))~Ke@W~_=bhO}^r2#3}`ob2yE zD8$+A!+8k3vDhk^NngO1z4?L~|09)}gTa<Qp@yv^tmDcJ$v>edhxGSaJ~6r<=wO8& zwgxT1P~0dqOk{lsUQ@(-O%t(i(tlp+D%8PNlNN_{ig!X1ro66DNKNuYXEimqqOGkB zH;$vpHj~B;pycsR=&lE_FyrqsoK_Zt6|ijsylCmN*I~wwj`n43a@g}8O8;@`fen8D z`SCQc#fQVu_>1Vd?c)h>s|-`&DI#?jrp8b?nU+U55=-uoBx!d!A<ov(uK>IKWdHGn zmB0!;ECsDdJl@Hf^uAR-COZT!%1+>4f|HsE1Oms+w67X)C>DD}=5iduY;lWHN(0!} zn#T9aZ1}vUY3Bz927W_;94<v)sk|z{;c$Glr0}CKkcpK9SgI$wjHzV_qU{+=d<q(y z_TipbY>^~$EYqxmMMV85ca84A4WGD*fh&$p8q>P$1S`0k8sVOv#O;cge~H7eTgIwp z{taM5!`Z8k>7OMa9&AB8IH2o<`nQ9v&0EcLF5klN2p>Kip8>F+*#nl20&J%aR;VaG zM8Qt$NeuK5{Px(zLH0~})vK#DG{RUiPF8#k$3WavotV*9FyF5(4)V3zwl$Btxe(uM zSqVy`lE%+f1PrhZ9&f`D82|m_voKzYkvo~z)CV^Q0+)@qvX`=~NsHdb`uJlzz`|62 z&iq5`Y6(_!UAZ}wFJ7|j9e`~%+s6pECsb{Cj1T6nN<Zq~S(xQ#Cw)o4IDLE#0culq z!szn)Kp<6JTW4F1YtrIe8QVPZiNFn){x`xnYTb%$-b*(e(*FRB8DV_c+K&5|w{BFh z0@uh0_ePTsb4G_?%40ggD0r-l&Jbw_uha9>M%%efm!F66vBz@U0(}m!RbOC>)ltyj zvc_!yX5}fNP;$Pc&?i_{R}EGO<-~Jlh1Y^!krfi#yql{LE>=$0G*x{6#I&KI`nvos z0Z2hao>QW(axw8~8v=pTl?m&IqN)I;tB-}rOyn!B$94L8cv=Ds09UZNm|71&jOs8l zEg5Z~GHrQ|Xi&~(2hILw$fyh_=}ZI0?}NT~QX!{#TQzB?^|PJ7gsDXwwHiyr28<Eb aFV(+kITQH|w*}w;0000<MNUMnLSTa0dPCv>
new file mode 100644 index 0000000000000000000000000000000000000000..14d3c53d54350e52736d7da315e90266e19084ee GIT binary patch literal 1148 zc$@)z1cUpDP)<h;3K|Lk000e1NJLTq001Ze001Zm1^@s6jQ+T700001b5ch_0Itp) z=>Px(Hc3Q5R9FeUm|bWbRTRh1nVEDO8`7s5+CVFUU?~WSpn?zSlYMYMN0%VM8uJho z4Ej*0SU+D{KOWkMNPN($(bjcmW@ne8;!FF`7qMa#RK%!l!B$(WjhbeY-8r5cyJdDV zw>vv?lYwFGJ@=gd{oQ-^-np{~t9Tu8oSRab@gM;00>W*8FbG8d0^m6=e@`COnoA4E zipAoeG7OY8B(axDwXH^qUPZuSXC0a+f`jw_%)LA^GBOwRR}N^~?rzZVO+@Ssc6Qx5 z(QiP_JYQe<{WO0iY1yt+pFv$a+%=r&7;y_}#)-01%lSE!&7kE}AJH^?$1fhIOhi|x ziT9dj`YREy1kkqa?Wh}HvfK@EpjO7vnLp0s_VMxbyqXm2#{Q^qJVT4TD4t#bZ&2K# z%=c0|ByQa_Ku-x<;u$QLYq#n>@Lg0b0aVDP5BP!XN^LJ9@F3f+xxX)8+LCw%HG_7u zY~+DbbMf6a46fACdoO6?dVgQv#`_WjGTJ>M;!@M1VMHDbXj(QYAZG^el+uJWq)2-> zXb!UpA0Ek$=%Wd$a=lb4={>35Mo5#?Bo+yG{d_B}SK(gbk@f_G2;f2@Ko&&Dt$9IV zRdohjNR(p7ac))x6Q+Yi=VJqwUH5j-^pj!2s%!-KODsUkb$9BZ9p?-WsDcUALHHyN zAR=iF3-*VKsxwlf?m2M)c_GrXd^gpxtW+YPqs3zOr#OIM<}<GY(qk)S)mV7&SE#YH zPpBDjB&VjPc+bK@oY*KaNHA6~&7XyDoEgL$2R0w(H^{CioptNOv$rpw&m3vRbv=Vl zrFw`@zYnzpR6WI}%qN{OGne_$ui4cM@{#^IhI_&<prXuRWpAl|o0&_u!3piiuR5zz zeOyC)Lj};X2En(yQmf!KJ}wjrGcw&sfVS%n^LOnB{4vzkjQcy~S-RXLFdNXAy>RyI zxrvF1y1!jk?g&_}RQS-S*TPO4645&U&=2w=8shxi)$;KOhB<{Fwdzry*PRi|s@|=m zZbgL?n|ITL;X*F6_peJAhk)QS0YV<U9S2UtN&(B|>JHTL82fAxju9>bkH#Lzrz^sD zbaZrK=Ic}UH@wEqZ)Q%7M~Dw~2~DwOS=-Q9caj?iL!&~C-jn%k_E2b30^^E(kWa~* z81C~<aJ&Lf@Si`&|L+@T&3yL#1RT=PHo)oW>3$=%{!@m#MH+FnLBKK83!VIuwqFCB zoSaN;=-cSB;2lXtRmLv^E`R*2TYr?dg&~#VDzugXjh^1O7_RMKC7HPAc`vR-YFqsq zcEx>#_qj(~2HBI}1gH57_$#3Hqs(VD>;}KR_CygTgqoSnC_gB4>RY-|2Lm;t%|Y8* z)UT~#S!ewE+QaGn9X$9!=Chjij)44LeQh-XI%#v}U{>x#7v*hh)AAocJ!Dd`N_wUM O0000<MNUMnLSTZe{3^x(
new file mode 100644 index 0000000000000000000000000000000000000000..0c7a6856a73135fe0ca54ffb614e3d24bd7d87ee GIT binary patch literal 2272 zc$@*?2p{)}P)<h;3K|Lk000e1NJLTq002+`002-31^@s6juG;$00001b5ch_0Itp) z=>Px-nn^@KRCodHTzhO3MHrvi+iMH0MG23hrQ$ORiUmz5e`qj@L`i$syQYQ`&<aX0 zu_h!M2nivPh+rhzM5V@t7+TPy_pFVSkVvBP2kIZhAVP={l$W4T8tmilcKl7rHP`Fy z-EMbwZ?CY)Wq0TCJ%01e%s1c69wp=8>gecrfXW4nDIxPHp|cqw6_k=v##k}b>m!8r z63WgKMo%)rexXXN%j5AJ6`?wK=I7<}byheFNh82o2_^2;uLV1!5E>yY9*z$Vep_E( zAJ<O<O*58CxvUci1g24k{4Qgpky0v&kOj1k6NPQ?cxu9W>E#W8EiEn1DbvcDBuUx? zB}?@xs;N99hn2YUw%6<WK~s4Y$_oJf{zw&d&@V8qJ*ZVNBjprAKD~V5+?#9Gtchl$ zV|xGtfryJpWG}kwglx(P&1d8gQ=)a4Z2rkaSI8KWiGh*4U@*KE-~}uPj|)x7BI0l! z>F9{e$)tmAEie#@Yyof^GYM}=9%EOT5?cUEJeiK4EdlfgBQHx5-IY$@oTM>!;wp<* zHP+T%P33Pp0DYm*Y$ug|L?4=%Dr`>Q8R?D=_AO2I7i~2LwzPCkl&R#i9eBLuC|M#G z6mCszL|Xwkt&BBeDVA~2PF29#ce)Oa{KLsSZ3AFOIQ#^fc~!D-f}gQ8W}hoDw(wt~ zl=2^c=4s%A6=C)ZlbVA_&i9f_+GY$4gd+!FeT$QY6ZH&qE-)5f;dQ$YrlaW(g`NdA zy8%X7y8P5M7AH@7Jk{UxbX!;;y2WE=fd?I7Wa{8?5IC^B3@^*yvtAP0o+x7r0I4Lu zmJmqv9q6^HSza=)8z8dQ<TSNsbmfI;^hbDp&H>!Kd9%z2U9Kjq(R6e-O`vc-ddgCV z)6MJT9Kbo1l?xFhn8bxK<GL!dV_NCzDT}~tEvK7v05PpC(W>BB%H1|Fplw{!NgQML zd>+)zIe-uZ?u&Vs!%;LeR%zjIdrY4X>U1hga}FT(Dn>Y_S`^h&2A-Q{-%?LmE(4>| z+?)c~+}vCU=Xs_cK=hWyjJb;YdD-gS@CE7WY~@Dgv)MTXu)MsSPabAF^LzoZI}?Zr zM|vbU8r~=^H(U6OQUrs+`-mi;%2-HTCR{?+%3YPjY|F<y-fe-fPRkn>>WV!4n1cr> z<gl;>ffHR#WwLs5!srcZkKMeIoOa><en(FZ+D#ZsO4y~G0(klI=}{40VcJ<k=v*!V zq~#T}wpgR5Ib0YeXAO=5V-t+8f9F;{-ijsM%CS+fM=h-3&sh#258F?hPWE9Of59Fw z7?46?KU+8n65{juCd-9IU0A+V84%kRRpCJ>M2XF?%mVj@LNjIJ=thf~x^%M)T}lpP zulR4v04ySo2)ga~01vIi6c*x*WyvhePgWs*Hrq1mFqsh?=x97D?XwKPp;JzjZU945 zVsj?rHHthO=$-ZT)n_aNkTdeQs&`=2<zet^{5@|k#(JZJ{qH2?x1<Xvn0Z~UNIXgw zpbPw!NXxgo!r1#b-f<}*x1|7brge4Izrh30ywQ^o)E;+s>UwX_j$~OZ^#eKM8;8T$ z5rhG!Ckv7X&(~NyS_RkTc(SaPTHu~Nom1pO)}3$Qai<yMcS9<897xOjK&(PaoCWL~ zfcH>x3+BZ$rR%+}nyz%RT4I5G@55P86e85z9k_G~8cE}phY4>3Jc#$z=$LMSh`dNM zD`vF=yl2#euw^>j6?}-Zyv|+SnMpp=0YunpCzf#DOhVW#58nRoN{rONW5{}{!PG8{ zgW_8e^Ly290IA7?F58FSw%9=5{JOfDtl)7vrnxbJaOXM@|3FPxJEa4(ION$C8|>c< zC;6-%*-Qa25D33S9nvlcVhVjd3T7<JKVcwrAG+%f?%-!EtCA^PL4P=0BN53Dex{GF zDmtkq^H~+aTy#x#NTKZD+0&=Ho0^&iR4Zu65DFl`dx{c?2ThAK@HVvw!e@KY0e&Jx z=_PT3&$Z$wdwP0KH8wW(i)1VeKwmIeDO357nP>epE6&Jw_@?9O)NeX8*Vj={$Smyh zh3|Jtj{RnWhwe8B_u?qOXVLFUKZ!550z58<VF3Mp{|sj#J%H|FVsL<+i4kQURs@y& zj@7|wH4qgRU$xBb{!=Yqbh;q`+uPgkkQ@a(IB<_>!4mXfb(dH?wz%Hy{*|Zk`v8kA zN(Um+cH*#jFj3cv`<Mab`@0j1OTUJr@~ByY0+1d=L6)R`DY>k+_K#E$F=-r&4fd@w zOBOY{W12+xCehTg8DY2t&#MtOnvSuqk0|uH#0M?u=pn*W<}57Q!t?sBvyBZ6u8Zl) znVB{g0BJB3X+v<J!7RaGc~Q!ktG2cV;V*k%y1OvI`^a4I5JDnkMLvMXn^qTq2o7ul zcyE|#6O@s)UU&6ZX7Uh8TXProha#^_lzuFdc`7Z8d+R;!nr*4+c7E3cpg+|4A~uuv z;E1d)i&ay+)>W;EuUdUOrDp}CBNSQ!ZFh?T&rZsVkR@Zpg#Y0Ux-E~aI7KiUk2F** zkpEu_{w=5rN*9ku#YO3q)&jA<y_?FEhovTqVl-aqjIuy@_fx5(0H5EJyT+^1BJ~<! zfo*MVr8t7SAK+Q?Cto5J$rY_(0Qu&3amgh9|9SI83p1YFmQ-wp07SQS;N-xUh^#D1 zEpY2SGXg^Z%FYR&0lcCA0LWbYR&FW+av&7mj!^t7sToGz6-C*C`h2eyOJ`~LFrpEf zcC9Nz!MB79r^G(-dcEg`)3qVNQErR~8Sy`-cZ#5s4>U{x5T60h$Oq*`$Y=_H7*lT= z3mo<X4YbB}M365c5PE^%LFXdA203AXUS3r-3?Q83lDyJbu&}0dVZk!mn%pe_4K@z| uWd9kYaZ^{9um}6$m_cMWKKZ<v5&R!hTeiIU{}~<t0000<MNUMnLSTZmP$$O#
new file mode 100644 index 0000000000000000000000000000000000000000..c4a189f29747dc575aaca982517ef5c9a35af46b GIT binary patch literal 3536 zc$@*y4KMPEP)<h;3K|Lk000e1NJLTq004LZ004Lh1^@s6Ib=4{00001b5ch_0Itp) z=>Px?ib+I4RCodHU3ric)fs=UduC@51PlQ|Kw~aJ3L&6YL_!pAlHIwMjUZ?d4<e;j zG=+b8V3INw#YDlRTqvU=Alcfvc9DP<u!N#aj6v8+2$IsML2$uXBEqse)BW;$lMKx4 z_U!ab_iVpDrfRCY-|_YL{eJzu?srq-{d6M`kH<$)>bqPe>|#pkBxIaG2pvHv8A=Ho zit`%A$O(iW&;BVpOc*^t2s=O-+5Z_)-dGe4f2R9}CM2XL$Z)iXc)aZjM%6h~qBjsm zrh{0iQiB;|DjtSCl+f)=VVgQScDFBEwoI*5o}PRj!5S#RQ%4cWcPqfTgHU>rMiNtk z;Oz$~CF|2nd8siJ+GmQ;UP3&A^8k4znH*nBrH2@!ivYYoFRJRNjIfPFp^rx*b#It? zGml`P+1%Vb(qB{iFue6HTn5YpxLjEvdYhVN_ct^IJIc+gmEQFPClaXzjMAs^ZXKZo zkQHHgS1b59d-mjsV~;FYu;6neI=Gf#c<oqUpmsGrcy2NRU{&)nc1WdcK{OcLUg>@0 zN;BFyUo4fHCCmP;@Y<<D+d=8s_;O!(^St?V;)IKKwzs#la`knc2ct;7he*<|0hF&? zuq;i_$d+_ZS3_fC;}_-V;VOa?skY?+=O^U=Bve|)-b(kJngf!L>eb6N1jm!D&!9-Y zPcJZ{rb8M1&u*q%GcOeSw;ml`L2xY9wi3-x_vit{4r$Op@{deU*Gzcwr=|3*xY@0g zU{T`ZskR54L@vh_jW82^HMJX?n%d4Pg|njxZb>HR5XPP?1%e&p@w&NU)VXX;Dcv1Q za7#<eH)JZm0w5e~BV0L}trhgcvLlU04~-=I0x!aDbm2MtCrQg=v9@W&taK#7mrR}Z z0J`O76f@dk;Q{JnYw%EO*6STdFfW=>bp6a>B=&0#np-E~q5iQ}BF7Od`2vrl?pLd2 ztW{yWhlr?bv63beKPBo={^#dBEE{KzVvojRv2ndcIjSC<NF=7Cqy8^2QV6?HUws!O z-W7?|Z|e<w{&hUwI-5vz6{<g9%MUOAoIM{7*58qz&ru%CsQfsL6N6mBcA+ui@>1k- zGI$!E7T&xopPX{fF;L-FK4>97pJNDaX-QpzD$(`%##wdFJvY;Rs$Ne^Q%*;omgmWO zeJ-%fBOirQL*)navpI%f$w&BT3?Y6Pq<3dI<mQyl=0TC3yRPI&8N2<3L}GX@uOkTl zUz44*AZL`7NB+@dq=TLp*G4|PP>Ky2PUOZM296+j)F?HA;%QjUC@YUtCBHX%f5S2; zy)CDMBM4?x`d-c;tB+}k?J%l?qR>AYl7U7Z)F`rF<hCc6H%nsXz;%WIF*9>__eVy> zscP3jqcRxv-N<Fwp5V!or}5ba!i<PK9taq&rkNoFQ)#Ys1i@4$-!=NQo0<P?Le4WP zZ&+FyYg7jEUxksQvh6(>hFoj{7^aEvtJLrVC(CS>VTC9iE=hq&wkH@3?LraEB=Q5J z<d`9Y>mbY2{sg08a-vZ{&CHJnZZ^hUUtOt8-Y(5$RGL3OAD`xGYY$$xZrwRBNVraY z%B0nJ|9vSRY(3}MJm_@LlU7d}iPDL-CfM&Eif<b6e!N!*(_&i7A(^M;d5rfc<>Nue z*_vQUVnPzkAx1HKrLShtn|g|Co`$F8dGhHodX6#F=V%V|Fe-SU^5bb2mSBzn_eM&j zZShp=Hs1W0ZRN@}gaiU(`4>0;pvt~;4cDGEP^UI0Sd~@3L}U%i#0tt1D3RHI-c=nK zbVfOppVi>?^bHspZtEhNX6Uig#&{=v5TKN$?M*Pvcm(&MwIJZQtqE2XWk8oCDuB)& z7xpGtlB7}gz}X@#89icK4{k}Nz9su>c3K3TEpCgEA@A6dU|w&OiPVm7&~t5pv_(o{ zF_-<eBRG~!Hb_MJ127Y=iP9n-i{6$py05C$u%dUaFak5_?#7Zw&pLX>V1?N9&c>Hb zJjRp}w((%Bukk1rPx-A!Zf`2Jui?47`NF5dH&ya6SC>qk{u`L_BW+W>P)!6V%}$pQ zn&1^HRs_bKf8koR={9H@;RO~&K+Uig9w~GYJ?{Jqp7O{o%3;w6RFC#YqxF0ep11^a z@5Sm1ONv_IMHEGVs%O^j5Y~f7j~<Qg%<RhPMGf&H`XT_(PNmb`FXUo`B^VR2dQ_D? zlvC6@>O_F4&jY)UbE$<Tm@60!*RNC=xdnB)uAIW&u{Z*ldYaDkNKfRa5Sn1FXf#x} zKEsq5Snulne8s(UEd<n6jg565=En$6FjuxQ9Nxp!%%xabw>@8Z@B9n`DCLeEV``sQ znk@+CW<(;9{~YNgm_^9`b;d03><j|xQmrj}Y(p?NWbxv<lUVBhgEK~XXITUo*%S$d z)@Y$2bW2umg<BG>QH)Z%s&}}XuTd@f=+sGaTQLl5)0-5FrLLqheVs2+P|O&&h2y=e zC`7$_ZYWfA;DaqZn6IEC$#f$I8hhmCKgE>YJvUgKT%J^Tg0XtrSE*0h0$oq99FuRO zi_ag91cR$f*)Q}*7Ut`WrgF_|c#n)OWr(}t+0)@*-Ou&dEG)qwcbG3QXfvPwp~n!n zq``alQ7l(@zg~NVETTc~Ak4Je2+y6O*AS<t1F>keB}<~A`lfPh6^>xO?lH)1#(bWc z<rw1Zw5(HM^p?5dx~=8fDOwRtr%avpJCOVRat(28dd9YPonTWcA(vw!6v2s9>#tB1 zTIg7^$}kK)L&u1!-n)0t?(65z4|bN3wIHc5MKnfD@`iHFRm%%BcEJV6U32QB`k)bF zb1(_fk`+s~F2;Hi&*hYI<B@+vfsBNTO`@aGy4_}BU^&6@RBA3d5H`4VyfcXX0`@nn z3VE`jp>Drf#OCB!N-(COT#F?RUIn=|IfYzu%wXi>>sX<E?I)cine8R3lHryS9E-JH zhGhu1f!rZgvP~qh84Om~3sSc;%G$e*AKz(oXK%g6Swe6;9-oNKGj@VpVHOe93z$|q z6thRrjrmu~$meK`IF8{({CEhHzuqAvbD*Q6<2}^xGg^789X8+u^GzdaiL?vVp@PjL zs%>Z$<!3O9;5!&-{--M5qhiqmT1{HNzU|8a%3i~~UZ;>tP*;5<8Vw3bE`KTpj9|1c z5AoM9%<H8SDt;UV%ZPXCwvN4T|57lu3e*{pmMlIE1?|fz6w8+u+}-L6#`Y;H31d{m zWl-e_UKiD`68of^u!VTHmXSxlbhA=c-Glk`&++GfWgR?swpmJ>A_r}}M>=VJUKQmn z>^HnaCUP9|1Euc46tZYuBzUM)I;-QW?!lPb`y4zsYPF#S_h;nK>7G-eJaV(q4WZB` zrl=uYbQh%WbpcObf$eWL_6ipAvZ@5fldZo%hy9%*S&nwzzomP+=3xHL7g~BnBEil5 zlFZ1SgCD+Mu)CO+98tomR!xeh+8%)Lr$jN7>w&uIdo0s4Ga8K^E~QsIp1P6BQWEL% zifeXKNz>`oXi_YtPo?oy@nC>+8^~Q=sZ!O*2hTkWa<9`vF26tbWTIwf@yXQunjh5+ zxynxDa=lIW;Fe@tokUmytyID+0}FCLMPtOQhKBmTbEC`s@x1`GriGL%$BOi(5{w1P zW})+73xw8MVT{&2d2bD6$~EC|_)V>Nr-zwVL^mfAQ?XY;t4QR6yl#wT3b~bB{_vVo z4{mNwecdn7&5$R=29ew_c&?%{dUK^?nYaum{0tL}nFnJeALZ@KBCQ+Au(kT_4Uu~O z73}JtA%gj~Hj-S!r<GhN7K-Zq_zHeWES;>;recEmrp#vzCYTULCJ&VAsH*J1@bC8i z??>N_hJ!EnpT<406%pLj)Z`yIny;irXAC4%oLrE%Y#{Gx_j^@BFdcR7m={6rO?Efl z4f>Z|O-dwNpF!urd^dpO1RKjD7@Y@?fZThXV7Oas&`mG~r!PVC<6~|C#wm8{A~=?8 z!}e=rrBjS{lN}`q#wdpyK<>-%-jZqE1e{%L?e{AhgVVo_IYTiR<F|`Jo}PsW6!prM zmexsFw&+zPA6%HlyRs7knjVby<qN2f@@~Jfcd@pU@ovzy55X9}Jo5i5+l;je0992V z^IELTLBD|DwQJW7#rWmT@Z4{jBi@r@H3Fvz=7ZA*4;hw%Les2P%DcaL2xJLHv5b8l zYd3fceVPZXD3Y=S`}~7efm}Y|Ta@2PQ53Pe9=>=o^#lf||Ii9UFxqj0!ZJ<y_r{*U zs*>)`?)j4+?wwbLfDa8+ODoH2LM}?W=Z3=VCP?x`6hWY9Lr0PH<_0r1#$17(wB86< zOE975^;XEcHWvYl3HChLTsr$ovy@=I2n(-&_f^XaF$)1p3FbzO96#QB51YlNhMdza zSsHOw3*t*9plWs$(>Xf>jCuPoR1Im9rmpm-AIMya`fev>>|<3WojpCI^T2_(kBYvw zMSnKgDX!`s90}D6HBZ=1<d~tW<?moKjPPU>N5CVvI5v)m@d$PVjm3@N1mk1Y`#BW> z317G$R?)cbD$4dm>p(zaRJ{)*e}J@RdvdqR*iUun;3cUd0{;cYHx)JO&DDAU0000< KMNUMnLSTZxNVq@%
--- a/mobile/android/base/resources/layout/doorhanger.xml +++ b/mobile/android/base/resources/layout/doorhanger.xml @@ -11,48 +11,50 @@ android:padding="@dimen/doorhanger_padding"> <ImageView android:id="@+id/doorhanger_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingRight="@dimen/doorhanger_padding" android:visibility="gone"/> - <TextView android:id="@+id/doorhanger_title" + <TextView android:id="@+id/doorhanger_message" android:focusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.Widget.DoorHanger.Medium"/> + android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/> </LinearLayout> <LinearLayout android:id="@+id/doorhanger_inputs" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:gravity="right" + android:paddingLeft="@dimen/doorhanger_padding" android:visibility="gone"/> <CheckBox android:id="@+id/doorhanger_checkbox" android:layout_width="match_parent" android:layout_height="wrap_content" android:checked="true" android:textColor="@color/placeholder_active_grey" android:visibility="gone"/> - <View android:id="@+id/divider_choices" + <View android:id="@+id/divider_buttons" android:layout_width="match_parent" android:layout_height="1dp" + android:background="@color/divider_light" android:visibility="gone"/> - <LinearLayout android:id="@+id/doorhanger_choices" + <LinearLayout android:id="@+id/doorhanger_buttons" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:visibility="gone"/> - <View android:id="@+id/divider_doorhanger" + <View android:id="@+id/divider_doorhanger" android:layout_width="match_parent" android:layout_height="1dp" - android:background="@color/fennec_ui_orange" + android:background="@color/divider_light" android:visibility="gone"/> </merge>
--- a/mobile/android/base/resources/layout/doorhanger_button.xml +++ b/mobile/android/base/resources/layout/doorhanger_button.xml @@ -1,12 +1,12 @@ <?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/. --> <Button xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="48dip" android:textColor="@color/placeholder_active_grey" android:textSize="14sp" android:background="@drawable/action_bar_button"/>
new file mode 100644 --- /dev/null +++ b/mobile/android/base/resources/layout/login_doorhanger.xml @@ -0,0 +1,67 @@ +<?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/. --> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingTop="@dimen/doorhanger_section_padding_small" + android:paddingLeft="@dimen/doorhanger_section_padding_small"> + + <ImageView android:id="@+id/doorhanger_icon" + android:layout_width="@dimen/doorhanger_icon_size" + android:layout_height="@dimen/doorhanger_icon_size" + android:layout_gravity="center_horizontal" + android:paddingRight="@dimen/doorhanger_section_padding_small" + android:src="@drawable/icon_key"/> + + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView android:id="@+id/doorhanger_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="@dimen/doorhanger_section_padding_small" + android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"/> + + <TextView android:id="@+id/doorhanger_message" + android:focusable="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="@dimen/doorhanger_section_padding_large" + android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/> + + <TextView android:id="@+id/doorhanger_login" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.DoorHanger.Medium" + android:paddingBottom="@dimen/doorhanger_section_padding_large" + android:visibility="gone"/> + + </LinearLayout> + + </LinearLayout> + + <View android:id="@+id/divider_buttons" + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/divider_light" + android:visibility="gone"/> + + <LinearLayout android:id="@+id/doorhanger_buttons" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:visibility="gone"/> + + <View android:id="@+id/divider_doorhanger" + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/divider_light" + android:visibility="gone"/> + +</merge>
--- a/mobile/android/base/resources/layout/overlay_share_dialog.xml +++ b/mobile/android/base/resources/layout/overlay_share_dialog.xml @@ -4,25 +4,25 @@ - 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/. --> <!-- Serves to position the content on the screen (bottom, centered) and provide the drop-shadow --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/sharedialog" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginLeft="15dp" - android:layout_marginRight="15dp" android:clipChildren="false" android:clipToPadding="false"> <LinearLayout android:layout_width="300dp" android:layout_height="wrap_content" android:layout_gravity="bottom|center" + android:layout_marginLeft="15dp" + android:layout_marginRight="15dp" android:paddingTop="8dp" android:orientation="vertical"> <!-- Title --> <TextView android:id="@+id/title" style="@style/ShareOverlayTitle" android:textAppearance="@style/TextAppearance.ShareOverlay.Header"
--- a/mobile/android/base/resources/layout/site_identity.xml +++ b/mobile/android/base/resources/layout/site_identity.xml @@ -1,68 +1,79 @@ <?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/. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal" - android:padding="@dimen/doorhanger_padding"> + android:orientation="vertical"> - <ImageView android:id="@+id/larry" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/larry" - android:paddingRight="@dimen/doorhanger_padding"/> + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="@dimen/doorhanger_padding"> - <FrameLayout android:layout_width="0dip" - android:layout_height="wrap_content" - android:layout_weight="1.0"> + <ImageView android:id="@+id/larry" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/larry" + android:paddingRight="@dimen/doorhanger_padding"/> - <include layout="@layout/site_identity_unknown" /> + <FrameLayout android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1.0"> + + <include layout="@layout/site_identity_unknown" /> - <LinearLayout android:id="@+id/site_identity_known_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> + <LinearLayout android:id="@+id/site_identity_known_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> - <TextView android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textSize="14sp" - android:textColor="@color/placeholder_active_grey" - android:text="@string/identity_connected_to"/> + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="14sp" + android:textColor="@color/placeholder_active_grey" + android:text="@string/identity_connected_to"/> - <TextView android:id="@+id/host" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textSize="20sp" - android:textColor="@color/placeholder_active_grey" - android:textStyle="bold"/> + <TextView android:id="@+id/host" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="20sp" + android:textColor="@color/placeholder_active_grey" + android:textStyle="bold"/> - <TextView android:id="@+id/owner_label" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textSize="14sp" - android:textColor="@color/placeholder_active_grey" - android:text="@string/identity_run_by" - android:paddingTop="12dip"/> + <TextView android:id="@+id/owner_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="14sp" + android:textColor="@color/placeholder_active_grey" + android:text="@string/identity_run_by" + android:paddingTop="12dip"/> - <TextView android:id="@+id/owner" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textColor="@color/placeholder_active_grey" - android:textSize="16sp" - android:textStyle="bold"/> + <TextView android:id="@+id/owner" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/placeholder_active_grey" + android:textSize="16sp" + android:textStyle="bold"/> - <TextView android:id="@+id/verifier" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textSize="14sp" - android:textColor="@color/placeholder_active_grey" - android:paddingTop="12dip"/> + <TextView android:id="@+id/verifier" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="14sp" + android:textColor="@color/placeholder_active_grey" + android:paddingTop="12dip"/> + + </LinearLayout> - </LinearLayout> + </FrameLayout> + </LinearLayout> - </FrameLayout> + <View android:id="@+id/divider_doorhanger" + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/divider_light" + android:visibility="gone"/> </LinearLayout>
--- a/mobile/android/base/resources/values/dimens.xml +++ b/mobile/android/base/resources/values/dimens.xml @@ -95,16 +95,20 @@ <dimen name="identity_padding_top">5dp</dimen> <dimen name="doorhanger_width">300dp</dimen> <dimen name="doorhanger_input_width">250dp</dimen> <dimen name="doorhanger_spinner_textsize">9sp</dimen> <dimen name="doorhanger_padding">15dp</dimen> <dimen name="doorhanger_offsetX">10dp</dimen> <dimen name="doorhanger_offsetY">7dp</dimen> + <dimen name="doorhanger_drawable_padding">5dp</dimen> + <dimen name="doorhanger_section_padding_small">20dp</dimen> + <dimen name="doorhanger_section_padding_large">30dp</dimen> + <dimen name="doorhanger_icon_size">60dp</dimen> <dimen name="flow_layout_spacing">6dp</dimen> <dimen name="menu_item_icon">21dp</dimen> <dimen name="menu_item_textsize">16sp</dimen> <dimen name="menu_item_state_icon">18dp</dimen> <!-- This is chosen to match Android's listPreferredItemHeight. TODO: We should inherit these from the system. http://androidxref.com/4.2.2_r1/xref/frameworks/base/core/res/res/values/themes.xml#123 -->
--- a/mobile/android/base/resources/values/styles.xml +++ b/mobile/android/base/resources/values/styles.xml @@ -405,24 +405,31 @@ <style name="TextAppearance.Widget.Home.ItemDescription" parent="TextAppearance.Micro"> <item name="android:textColor">#AFB1B3</item> </style> <style name="TextAppearance.Widget.HomeBanner" parent="TextAppearance.Small"> <item name="android:textColor">?android:attr/textColorHint</item> </style> - <style name="TextAppearance.Widget.DoorHanger.Medium" parent="TextAppearance.Medium"> + <style name="TextAppearance.DoorHanger"> <item name="android:textColor">@color/placeholder_active_grey</item> <item name="android:textColorLink">@color/doorhanger_link</item> </style> - <style name="TextAppearance.Widget.DoorHanger.Small" parent="TextAppearance.Small"> - <item name="android:textColor">@color/placeholder_active_grey</item> - <item name="android:textColorLink">@color/doorhanger_link</item> + <style name="TextAppearance.DoorHanger.Medium"> + <item name="android:textSize">16dp</item> + </style> + + <style name="TextAppearance.DoorHanger.Medium.Light"> + <item name="android:fontFamily">sans-serif-light</item> + </style> + + <style name="TextAppearance.DoorHanger.Small"> + <item name="android:textSize">14sp</item> </style> <style name="TextAppearance.UrlBar.Title" parent="TextAppearance.Small"> <item name="android:textSize">15sp</item> </style> <!-- BrowserToolbar --> <style name="BrowserToolbar"> @@ -812,17 +819,17 @@ <item name="android:minHeight">@dimen/menu_item_row_height</item> </style> <style name="FloatingHintEditText" parent="android:style/Widget.EditText"> <item name="android:paddingTop">0dp</item> </style> <!-- Make the share overlay activity appear like an overlay. --> - <style name="ShareOverlayActivity"> + <style name="ShareOverlayActivity" parent="Gecko"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowNoTitle">true</item> <item name="android:windowIsTranslucent">true</item> <item name="android:backgroundDimEnabled">true</item> <!-- We display the overlay on top of other Activities so show their status bar. --> <item name="android:statusBarColor">@android:color/transparent</item> </style>
--- a/mobile/android/base/sync/net/BaseResource.java +++ b/mobile/android/base/sync/net/BaseResource.java @@ -25,16 +25,17 @@ import org.mozilla.gecko.sync.ExtendedJS import ch.boye.httpclientandroidlib.Header; import ch.boye.httpclientandroidlib.HttpEntity; import ch.boye.httpclientandroidlib.HttpResponse; import ch.boye.httpclientandroidlib.HttpVersion; import ch.boye.httpclientandroidlib.client.AuthCache; import ch.boye.httpclientandroidlib.client.ClientProtocolException; import ch.boye.httpclientandroidlib.client.methods.HttpDelete; import ch.boye.httpclientandroidlib.client.methods.HttpGet; +import ch.boye.httpclientandroidlib.client.methods.HttpPatch; import ch.boye.httpclientandroidlib.client.methods.HttpPost; import ch.boye.httpclientandroidlib.client.methods.HttpPut; import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase; import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; import ch.boye.httpclientandroidlib.client.protocol.ClientContext; import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; import ch.boye.httpclientandroidlib.conn.scheme.PlainSocketFactory; import ch.boye.httpclientandroidlib.conn.scheme.Scheme; @@ -337,16 +338,24 @@ public class BaseResource implements Res public void post(HttpEntity body) { Logger.debug(LOG_TAG, "HTTP POST " + this.uri.toASCIIString()); HttpPost request = new HttpPost(this.uri); request.setEntity(body); this.go(request); } @Override + public void patch(HttpEntity body) { + Logger.debug(LOG_TAG, "HTTP PATCH " + this.uri.toASCIIString()); + HttpPatch request = new HttpPatch(this.uri); + request.setEntity(body); + this.go(request); + } + + @Override public void put(HttpEntity body) { Logger.debug(LOG_TAG, "HTTP PUT " + this.uri.toASCIIString()); HttpPut request = new HttpPut(this.uri); request.setEntity(body); this.go(request); } protected static StringEntity stringEntityWithContentTypeApplicationJSON(String s) { @@ -458,9 +467,21 @@ public class BaseResource implements Res public void post(ExtendedJSONObject o) { post(jsonEntity(o)); } public void post(JSONObject jsonObject) throws UnsupportedEncodingException { post(jsonEntity(jsonObject)); } + + public void patch(JSONArray jsonArray) throws UnsupportedEncodingException { + patch(jsonEntity(jsonArray)); + } + + public void patch(ExtendedJSONObject o) { + patch(jsonEntity(o)); + } + + public void patch(JSONObject jsonObject) throws UnsupportedEncodingException { + patch(jsonEntity(jsonObject)); + } }
--- a/mobile/android/base/sync/net/Resource.java +++ b/mobile/android/base/sync/net/Resource.java @@ -10,10 +10,11 @@ import ch.boye.httpclientandroidlib.Http public interface Resource { public abstract URI getURI(); public abstract String getURIString(); public abstract String getHostname(); public abstract void get(); public abstract void delete(); public abstract void post(HttpEntity body); + public abstract void patch(HttpEntity body); public abstract void put(HttpEntity body); }
--- a/mobile/android/base/sync/net/SyncStorageRequest.java +++ b/mobile/android/base/sync/net/SyncStorageRequest.java @@ -186,12 +186,17 @@ public class SyncStorageRequest implemen } @Override public void post(HttpEntity body) { this.resource.post(body); } @Override + public void patch(HttpEntity body) { + this.resource.patch(body); + } + + @Override public void put(HttpEntity body) { this.resource.put(body); } }
--- a/mobile/android/base/tabqueue/TabQueueHelper.java +++ b/mobile/android/base/tabqueue/TabQueueHelper.java @@ -1,34 +1,45 @@ /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- * 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/. */ package org.mozilla.gecko.tabqueue; +import org.mozilla.gecko.BrowserApp; +import org.mozilla.gecko.GeckoAppShell; +import org.mozilla.gecko.GeckoEvent; +import org.mozilla.gecko.GeckoProfile; +import org.mozilla.gecko.GeckoSharedPrefs; +import org.mozilla.gecko.R; +import org.mozilla.gecko.preferences.GeckoPreferences; +import org.mozilla.gecko.util.ThreadUtils; + import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.res.Resources; import android.support.v4.app.NotificationCompat; +import android.util.Log; import org.json.JSONArray; -import org.mozilla.gecko.BrowserApp; -import org.mozilla.gecko.GeckoProfile; -import org.mozilla.gecko.R; -import org.mozilla.gecko.util.ThreadUtils; +import org.json.JSONException; +import org.json.JSONObject; public class TabQueueHelper { private static final String LOGTAG = "Gecko" + TabQueueHelper.class.getSimpleName(); public static final String FILE_NAME = "tab_queue_url_list.json"; public static final String LOAD_URLS_ACTION = "TAB_QUEUE_LOAD_URLS_ACTION"; public static final int TAB_QUEUE_NOTIFICATION_ID = R.id.tabQueueNotification; + public static final String PREF_TAB_QUEUE_COUNT = "tab_queue_count"; + /** * Reads file and converts any content to JSON, adds passed in URL to the data and writes back to the file, * creating the file if it doesn't already exist. This should not be run on the UI thread. * * @param profile * @param url URL to add * @param filename filename to add URL to * @return the number of tabs currently queued @@ -47,34 +58,92 @@ public class TabQueueHelper { /** * Displays a notification showing the total number of tabs queue. If there is already a notification displayed, it * will be replaced. * * @param context * @param tabsQueued */ - static public void showNotification(Context context, int tabsQueued) { + public static void showNotification(final Context context, final int tabsQueued) { + ThreadUtils.assertNotOnUiThread(); + Intent resultIntent = new Intent(context, BrowserApp.class); resultIntent.setAction(TabQueueHelper.LOAD_URLS_ACTION); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, resultIntent, PendingIntent.FLAG_CANCEL_CURRENT); String title, text; final Resources resources = context.getResources(); - if(tabsQueued == 1) { + if (tabsQueued == 1) { title = resources.getString(R.string.tab_queue_notification_title_singular); text = resources.getString(R.string.tab_queue_notification_text_singular); } else { title = resources.getString(R.string.tab_queue_notification_title_plural); text = resources.getString(R.string.tab_queue_notification_text_plural, tabsQueued); } NotificationCompat.Builder builder = new NotificationCompat.Builder(context) - .setSmallIcon(R.drawable.ic_status_logo) - .setContentTitle(title) - .setContentText(text) - .setContentIntent(pendingIntent); + .setSmallIcon(R.drawable.ic_status_logo) + .setContentTitle(title) + .setContentText(text) + .setContentIntent(pendingIntent); NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(TabQueueHelper.TAB_QUEUE_NOTIFICATION_ID, builder.build()); } + + public static boolean shouldOpenTabQueueUrls(final Context context) { + ThreadUtils.assertNotOnUiThread(); + + // TODO: Use profile shared prefs when bug 1147925 gets fixed. + final SharedPreferences prefs = GeckoSharedPrefs.forApp(context); + + boolean tabQueueEnabled = prefs.getBoolean(GeckoPreferences.PREFS_TAB_QUEUE, false); + int tabsQueued = prefs.getInt(PREF_TAB_QUEUE_COUNT, 0); + + return tabQueueEnabled && tabsQueued > 0; + } + + public static int getTabQueueLength(final Context context) { + ThreadUtils.assertNotOnUiThread(); + + // TODO: Use profile shared prefs when bug 1147925 gets fixed. + final SharedPreferences prefs = GeckoSharedPrefs.forApp(context); + return prefs.getInt(PREF_TAB_QUEUE_COUNT, 0); + } + + public static void openQueuedUrls(final Context context, final GeckoProfile profile, final String filename) { + ThreadUtils.assertNotOnUiThread(); + + // Remove the notification. + NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.cancel(TAB_QUEUE_NOTIFICATION_ID); + + // exit early if we don't have any tabs queued + if (getTabQueueLength(context) < 1) { + return; + } + + JSONArray jsonArray = profile.readJSONArrayFromFile(filename); + + if (jsonArray.length() > 0) { + JSONObject data = new JSONObject(); + try { + data.put("urls", jsonArray); + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tabs:OpenMultiple", data.toString())); + } catch (JSONException e) { + // Don't exit early as we perform cleanup at the end of this function. + Log.e(LOGTAG, "Error sending tab queue data", e); + } + } + + try { + profile.deleteFileFromProfileDir(filename); + } catch (IllegalArgumentException e) { + Log.e(LOGTAG, "Error deleting Tab Queue data file.", e); + } + + // TODO: Use profile shared prefs when bug 1147925 gets fixed. + final SharedPreferences prefs = GeckoSharedPrefs.forApp(context); + prefs.edit().remove(PREF_TAB_QUEUE_COUNT).apply(); + } } \ No newline at end of file
--- a/mobile/android/base/tabqueue/TabQueueService.java +++ b/mobile/android/base/tabqueue/TabQueueService.java @@ -3,30 +3,32 @@ * 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/. */ package org.mozilla.gecko.tabqueue; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.PixelFormat; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import android.widget.Button; import android.widget.TextView; import org.mozilla.gecko.BrowserApp; import org.mozilla.gecko.GeckoProfile; +import org.mozilla.gecko.GeckoSharedPrefs; import org.mozilla.gecko.R; import org.mozilla.gecko.mozglue.ContextUtils; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -160,16 +162,23 @@ public class TabQueueService extends Ser // As we're doing disk IO, let's run this stuff in a separate thread. executorService.submit(new Runnable() { @Override public void run() { Context applicationContext = getApplicationContext(); final GeckoProfile profile = GeckoProfile.get(applicationContext); int tabsQueued = TabQueueHelper.queueURL(profile, intentData, filename); TabQueueHelper.showNotification(applicationContext, tabsQueued); + + // Store the number of URLs queued so that we don't have to read and process the file to see if we have + // any urls to open. + // TODO: Use profile shared prefs when bug 1147925 gets fixed. + final SharedPreferences prefs = GeckoSharedPrefs.forApp(applicationContext); + + prefs.edit().putInt(TabQueueHelper.PREF_TAB_QUEUE_COUNT, tabsQueued).apply(); } }); } @Override public void onDestroy() { super.onDestroy(); tabQueueHandler = null;
--- a/mobile/android/base/tests/StringHelper.java +++ b/mobile/android/base/tests/StringHelper.java @@ -1,16 +1,15 @@ /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- * 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/. */ package org.mozilla.gecko.tests; - public class StringHelper { private StringHelper() {} public static final String OK = "OK"; // Note: DEFAULT_BOOKMARKS_TITLES.length == DEFAULT_BOOKMARKS_URLS.length public static final String[] DEFAULT_BOOKMARKS_TITLES = new String[] { "Firefox: About your browser", @@ -50,16 +49,17 @@ public class StringHelper { public static final String CONTEXT_MENU_OPEN_IN_PRIVATE_TAB = "Open in Private Tab"; public static final String CONTEXT_MENU_COPY_LINK = "Copy Link"; public static final String CONTEXT_MENU_SHARE_LINK = "Share Link"; public static final String CONTEXT_MENU_EDIT = "Edit"; public static final String CONTEXT_MENU_SHARE = "Share"; public static final String CONTEXT_MENU_REMOVE = "Remove"; public static final String CONTEXT_MENU_COPY_ADDRESS = "Copy Address"; public static final String CONTEXT_MENU_EDIT_SITE_SETTINGS = "Edit Site Settings"; + public static final String CONTEXT_MENU_SITE_SETTINGS_SAVE_PASSWORD = "Save Password"; public static final String CONTEXT_MENU_ADD_TO_HOME_SCREEN = "Add to Home Screen"; public static final String CONTEXT_MENU_PIN_SITE = "Pin Site"; public static final String CONTEXT_MENU_UNPIN_SITE = "Unpin Site"; // Context Menu menu items public static final String[] CONTEXT_MENU_ITEMS_IN_PRIVATE_TAB = new String[] { CONTEXT_MENU_OPEN_LINK_IN_PRIVATE_TAB, CONTEXT_MENU_COPY_LINK, @@ -100,17 +100,18 @@ public class StringHelper { public static final String ROBOCOP_BIG_MAILTO_URL = "/robocop/robocop_big_mailto.html"; public static final String ROBOCOP_BLANK_PAGE_01_URL = "/robocop/robocop_blank_01.html"; public static final String ROBOCOP_BLANK_PAGE_02_URL = "/robocop/robocop_blank_02.html"; public static final String ROBOCOP_BLANK_PAGE_03_URL = "/robocop/robocop_blank_03.html"; public static final String ROBOCOP_BLANK_PAGE_04_URL = "/robocop/robocop_blank_04.html"; public static final String ROBOCOP_BLANK_PAGE_05_URL = "/robocop/robocop_blank_05.html"; public static final String ROBOCOP_BOXES_URL = "/robocop/robocop_boxes.html"; public static final String ROBOCOP_GEOLOCATION_URL = "/robocop/robocop_geolocation.html"; - public static final String ROBOCOP_LOGIN_URL = "/robocop/robocop_login.html"; + public static final String ROBOCOP_LOGIN_01_URL= "/robocop/robocop_login_01.html"; + public static final String ROBOCOP_LOGIN_02_URL= "/robocop/robocop_login_02.html"; public static final String ROBOCOP_POPUP_URL = "/robocop/robocop_popup.html"; public static final String ROBOCOP_OFFLINE_STORAGE_URL = "/robocop/robocop_offline_storage.html"; public static final String ROBOCOP_PICTURE_LINK_URL = "/robocop/robocop_picture_link.html"; public static final String ROBOCOP_SEARCH_URL = "/robocop/robocop_search.html"; public static final String ROBOCOP_TEXT_PAGE_URL = "/robocop/robocop_text_page.html"; public static final String ROBOCOP_ADOBE_FLASH_URL = "/robocop/robocop_adobe_flash.html"; public static final String ROBOCOP_INPUT_URL = "/robocop/robocop_input.html"; public static final String ROBOCOP_READER_MODE_BASIC_ARTICLE = "/robocop/reader_mode_pages/basic_article.html"; @@ -258,19 +259,19 @@ public class StringHelper { public static final String GEO_MESSAGE = "Share your location with"; public static final String GEO_ALLOW = "Share"; public static final String GEO_DENY = "Don't share"; public static final String OFFLINE_MESSAGE = "to store data on your device for offline use"; public static final String OFFLINE_ALLOW = "Allow"; public static final String OFFLINE_DENY = "Don't allow"; - public static final String LOGIN_MESSAGE = "Save password"; - public static final String LOGIN_ALLOW = "Save"; - public static final String LOGIN_DENY = "Don't save"; + public static final String LOGIN_MESSAGE = "Would you like " + BRAND_NAME + " to remember this login?"; + public static final String LOGIN_ALLOW = "Remember"; + public static final String LOGIN_DENY = "Never"; public static final String POPUP_MESSAGE = "prevented this site from opening"; public static final String POPUP_ALLOW = "Show"; public static final String POPUP_DENY = "Don't show"; // Strings used as content description, e.g. for ImageButtons public static final String CONTENT_DESCRIPTION_READER_MODE_BUTTON = "Enter Reader View"; }
rename from mobile/android/base/tests/robocop_login.html rename to mobile/android/base/tests/robocop_login_01.html --- a/mobile/android/base/tests/robocop_login.html +++ b/mobile/android/base/tests/robocop_login_01.html @@ -1,13 +1,13 @@ <html> <script> function login(){ -document.login.username.value="Test"; -document.login.password.value="Test"; +document.login.username.value="Test1"; +document.login.password.value="Test2"; document.getElementById('submit').click(); } </script> <head> <title>Robocop Login</title> <meta charset="utf-8"> </head> <body onload="login()">
copy from mobile/android/base/tests/robocop_login.html copy to mobile/android/base/tests/robocop_login_02.html --- a/mobile/android/base/tests/robocop_login.html +++ b/mobile/android/base/tests/robocop_login_02.html @@ -1,21 +1,21 @@ <html> <script> function login(){ -document.login.username.value="Test"; -document.login.password.value="Test"; +document.login.username.value="Test2"; +document.login.password.value="Test2"; document.getElementById('submit').click(); } </script> <head> <title>Robocop Login</title> <meta charset="utf-8"> </head> <body onload="login()"> <h2>User Login </h2> - <form name="login" method="post" action="robocop_blank_01.html"> + <form name="login" method="post" action="robocop_blank_02.html"> Username: <input type="text" name="username" id="username"><br> Password: <input type="password" name="password" id="password"><br> <input type="submit" id="submit" name="submit" value="Login!"> </form> </body> </html>
--- a/mobile/android/base/tests/testClearPrivateData.java +++ b/mobile/android/base/tests/testClearPrivateData.java @@ -73,22 +73,22 @@ public class testClearPrivateData extend checkOption(shareStrings[1], "Clear"); checkOption(shareStrings[3], "Cancel"); loadCheckDismiss(shareStrings[2], url, shareStrings[0]); checkOption(shareStrings[2], "Cancel"); checkDevice(titleGeolocation, url); } public void clearPassword(){ - String passwordStrings[] = {"Save password", "Save", "Don't save"}; + String passwordStrings[] = { StringHelper.LOGIN_MESSAGE, StringHelper.LOGIN_ALLOW, StringHelper.LOGIN_DENY }; String title = StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE; - String loginUrl = getAbsoluteUrl(StringHelper.ROBOCOP_LOGIN_URL); + String loginUrl = getAbsoluteUrl(StringHelper.ROBOCOP_LOGIN_01_URL); loadCheckDismiss(passwordStrings[1], loginUrl, passwordStrings[0]); - checkOption(passwordStrings[1], "Clear"); + checkOption(StringHelper.CONTEXT_MENU_SITE_SETTINGS_SAVE_PASSWORD, "Clear"); loadCheckDismiss(passwordStrings[2], loginUrl, passwordStrings[0]); checkDevice(title, getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL)); } // clear private data and verify the device type because for phone there is an extra back action to exit the settings menu public void checkDevice(final String title, final String url) { clearPrivateData(); if (mDevice.type.equals("phone")) {
--- a/mobile/android/base/tests/testDoorHanger.java +++ b/mobile/android/base/tests/testDoorHanger.java @@ -19,17 +19,16 @@ import org.mozilla.gecko.Actions; * offline storage permission doorhangers - allowing and not allowing offline storage dismisses the doorhanger * Password Manager doorhangers - Remember and Not Now options dismiss the doorhanger */ public class testDoorHanger extends BaseTest { public void testDoorHanger() { String GEO_URL = getAbsoluteUrl(StringHelper.ROBOCOP_GEOLOCATION_URL); String BLANK_URL = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL); String OFFLINE_STORAGE_URL = getAbsoluteUrl(StringHelper.ROBOCOP_OFFLINE_STORAGE_URL); - String LOGIN_URL = getAbsoluteUrl(StringHelper.ROBOCOP_LOGIN_URL); blockForGeckoReady(); // Test geolocation notification loadUrlAndWait(GEO_URL); waitForText(StringHelper.GEO_MESSAGE); mAsserter.is(mSolo.searchText(StringHelper.GEO_MESSAGE), true, "Geolocation doorhanger has been displayed"); @@ -58,17 +57,16 @@ public class testDoorHanger extends Base // Add a new tab addTab(BLANK_URL); // Make sure doorhanger is hidden mAsserter.is(mSolo.searchText(GEO_MESSAGE), false, "Geolocation doorhanger notification is hidden when opening a new tab"); */ - boolean offlineAllowedByDefault = true; // Save offline-allow-by-default preferences first final String[] prefNames = { "offline-apps.allow_by_default" }; final int ourRequestId = 0x7357; final Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data"); mActions.sendPreferencesGetEvent(ourRequestId, prefNames); try { JSONObject data = null; @@ -125,35 +123,34 @@ public class testDoorHanger extends Base jsonPref.put("name", "offline-apps.allow_by_default"); jsonPref.put("type", "bool"); jsonPref.put("value", offlineAllowedByDefault); setPreferenceAndWaitForChange(jsonPref); } catch (JSONException e) { mAsserter.ok(false, "exception setting preference", e.toString()); } + // Load new login page + loadUrlAndWait(getAbsoluteUrl(StringHelper.ROBOCOP_LOGIN_01_URL)); + waitForText(StringHelper.LOGIN_MESSAGE); + + // Test doorhanger is dismissed when tapping "Remember". + mSolo.clickOnButton(StringHelper.LOGIN_ALLOW); + waitForTextDismissed(StringHelper.LOGIN_MESSAGE); + mAsserter.is(mSolo.searchText(StringHelper.LOGIN_MESSAGE), false, "Login doorhanger notification is hidden when allowing saving password"); // Load login page - loadUrlAndWait(LOGIN_URL); + loadUrlAndWait(getAbsoluteUrl(StringHelper.ROBOCOP_LOGIN_02_URL)); waitForText(StringHelper.LOGIN_MESSAGE); - // Test doorhanger is dismissed when tapping "Don't save" + // Test doorhanger is dismissed when tapping "Never". mSolo.clickOnButton(StringHelper.LOGIN_DENY); waitForTextDismissed(StringHelper.LOGIN_MESSAGE); mAsserter.is(mSolo.searchText(StringHelper.LOGIN_MESSAGE), false, "Login doorhanger notification is hidden when denying saving password"); - // Load login page - loadUrlAndWait(LOGIN_URL); - waitForText(StringHelper.LOGIN_MESSAGE); - - // Test doorhanger is dismissed when tapping "Save" and is no longer triggered - mSolo.clickOnButton(StringHelper.LOGIN_ALLOW); - waitForTextDismissed(StringHelper.LOGIN_MESSAGE); - mAsserter.is(mSolo.searchText(StringHelper.LOGIN_MESSAGE), false, "Login doorhanger notification is hidden when allowing saving password"); - testPopupBlocking(); } private void testPopupBlocking() { String POPUP_URL = getAbsoluteUrl(StringHelper.ROBOCOP_POPUP_URL); try { JSONObject jsonPref = new JSONObject();
--- a/mobile/android/base/tests/testMasterPassword.java +++ b/mobile/android/base/tests/testMasterPassword.java @@ -175,17 +175,17 @@ public class testMasterPassword extends } waitForText(StringHelper.SETTINGS_LABEL); mActions.sendSpecialKey(Actions.SpecialKey.BACK);// Close the Settings Menu // Make sure the settings menu has been closed. mAsserter.ok(mSolo.waitForText("Browser Blank Page 01"), "Waiting for blank browser page after exiting settings", "Blank browser page present"); } public void verifyLoginPage(String password, String badPassword) { - String LOGIN_URL = getAbsoluteUrl("/robocop/robocop_login.html"); + String LOGIN_URL = getAbsoluteUrl(StringHelper.ROBOCOP_LOGIN_01_URL); String option [] = {"Save", "Don't save"}; doorhangerDisplayed(LOGIN_URL);// Check that the doorhanger is displayed // TODO: Remove this hack -- see bug 915449 mSolo.sleep(2000); for (String item:option) {
--- a/mobile/android/base/toolbar/SiteIdentityPopup.java +++ b/mobile/android/base/toolbar/SiteIdentityPopup.java @@ -22,16 +22,17 @@ import org.json.JSONObject; import android.content.Context; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; +import org.mozilla.gecko.widget.DoorhangerConfig; /** * SiteIdentityPopup is a singleton class that displays site identity data in * an arrow panel popup hanging from the lock icon in the browser toolbar. */ public class SiteIdentityPopup extends AnchoredPopup { private static final String LOGTAG = "GeckoSiteIdentityPopup"; @@ -48,16 +49,18 @@ public class SiteIdentityPopup extends A private LinearLayout mIdentityKnownContainer; private LinearLayout mIdentityUnknownContainer; private TextView mHost; private TextView mOwnerLabel; private TextView mOwner; private TextView mVerifier; + private View mDivider; + private DoorHanger mMixedContentNotification; private DoorHanger mTrackingContentNotification; private final OnButtonClickListener mButtonClickListener; public SiteIdentityPopup(Context context) { super(context); @@ -80,16 +83,17 @@ public class SiteIdentityPopup extends A (LinearLayout) mIdentity.findViewById(R.id.site_identity_known_container); mIdentityUnknownContainer = (LinearLayout) mIdentity.findViewById(R.id.site_identity_unknown_container); mHost = (TextView) mIdentityKnownContainer.findViewById(R.id.host); mOwnerLabel = (TextView) mIdentityKnownContainer.findViewById(R.id.owner_label); mOwner = (TextView) mIdentityKnownContainer.findViewById(R.id.owner); mVerifier = (TextView) mIdentityKnownContainer.findViewById(R.id.verifier); + mDivider = mIdentity.findViewById(R.id.divider_doorhanger); } private void updateIdentity(final SiteIdentity siteIdentity) { if (!mInflated) { init(); } final boolean isIdentityKnown = (siteIdentity.getSecurityMode() != SecurityMode.UNKNOWN); @@ -132,79 +136,84 @@ public class SiteIdentityPopup extends A final String verifier = siteIdentity.getVerifier(); final String encrypted = siteIdentity.getEncrypted(); mVerifier.setText(verifier + "\n" + encrypted); } private void addMixedContentNotification(boolean blocked) { // Remove any existing mixed content notification. removeMixedContentNotification(); - mMixedContentNotification = new DoorHanger(mContext, DoorHanger.Theme.DARK); + final DoorhangerConfig config = new DoorhangerConfig(); int icon; - String message; if (blocked) { icon = R.drawable.shield_enabled_doorhanger; - message = mContext.getString(R.string.blocked_mixed_content_message_top) + "\n\n" + - mContext.getString(R.string.blocked_mixed_content_message_bottom); + config.setMessage(mContext.getString(R.string.blocked_mixed_content_message_top) + "\n\n" + + mContext.getString(R.string.blocked_mixed_content_message_bottom)); } else { icon = R.drawable.shield_disabled_doorhanger; - message = mContext.getString(R.string.loaded_mixed_content_message); + config.setMessage(mContext.getString(R.string.loaded_mixed_content_message)); } + config.setLink(mContext.getString(R.string.learn_more), MIXED_CONTENT_SUPPORT_URL, "\n\n"); + config.setType(DoorHanger.Type.SITE); + mMixedContentNotification = DoorHanger.Get(mContext, config); mMixedContentNotification.setIcon(icon); - mMixedContentNotification.setMessage(message); - mMixedContentNotification.addLink(mContext.getString(R.string.learn_more), MIXED_CONTENT_SUPPORT_URL, "\n\n"); addNotificationButtons(mMixedContentNotification, blocked); mContent.addView(mMixedContentNotification); + mDivider.setVisibility(View.VISIBLE); } private void removeMixedContentNotification() { if (mMixedContentNotification != null) { mContent.removeView(mMixedContentNotification); mMixedContentNotification = null; } } private void addTrackingContentNotification(boolean blocked) { // Remove any existing tracking content notification. removeTrackingContentNotification(); - mTrackingContentNotification = new DoorHanger(mContext, DoorHanger.Theme.DARK); + + final DoorhangerConfig config = new DoorhangerConfig(); int icon; - String message; if (blocked) { icon = R.drawable.shield_enabled_doorhanger; - message = mContext.getString(R.string.blocked_tracking_content_message_top) + "\n\n" + - mContext.getString(R.string.blocked_tracking_content_message_bottom); + config.setMessage(mContext.getString(R.string.blocked_tracking_content_message_top) + "\n\n" + + mContext.getString(R.string.blocked_tracking_content_message_bottom)); } else { icon = R.drawable.shield_disabled_doorhanger; - message = mContext.getString(R.string.loaded_tracking_content_message_top) + "\n\n" + - mContext.getString(R.string.loaded_tracking_content_message_bottom); + config.setMessage(mContext.getString(R.string.loaded_tracking_content_message_top) + "\n\n" + + mContext.getString(R.string.loaded_tracking_content_message_bottom)); } + config.setLink(mContext.getString(R.string.learn_more), TRACKING_CONTENT_SUPPORT_URL, "\n\n"); + config.setType(DoorHanger.Type.SITE); + mTrackingContentNotification = DoorHanger.Get(mContext, config); + mTrackingContentNotification.setIcon(icon); - mTrackingContentNotification.setMessage(message); - mTrackingContentNotification.addLink(mContext.getString(R.string.learn_more), TRACKING_CONTENT_SUPPORT_URL, "\n\n"); addNotificationButtons(mTrackingContentNotification, blocked); mContent.addView(mTrackingContentNotification); + mDivider.setVisibility(View.VISIBLE); } private void removeTrackingContentNotification() { if (mTrackingContentNotification != null) { mContent.removeView(mTrackingContentNotification); mTrackingContentNotification = null; } } private void addNotificationButtons(DoorHanger dh, boolean blocked) { + // TODO: Add support for buttons in DoorHangerConfig. if (blocked) { dh.addButton(mContext.getString(R.string.disable_protection), "disable", mButtonClickListener); dh.addButton(mContext.getString(R.string.keep_blocking), "keepBlocking", mButtonClickListener); } else { dh.addButton(mContext.getString(R.string.enable_protection), "enable", mButtonClickListener); } } @@ -271,16 +280,17 @@ public class SiteIdentityPopup extends A } } @Override public void dismiss() { super.dismiss(); removeMixedContentNotification(); removeTrackingContentNotification(); + mDivider.setVisibility(View.GONE); } private class PopupButtonListener implements OnButtonClickListener { @Override public void onButtonClick(DoorHanger dh, String tag) { try { JSONObject data = new JSONObject(); data.put("allowContent", tag.equals("disable"));
copy from mobile/android/base/widget/DoorHanger.java copy to mobile/android/base/widget/DefaultDoorHanger.java --- a/mobile/android/base/widget/DoorHanger.java +++ b/mobile/android/base/widget/DefaultDoorHanger.java @@ -1,244 +1,87 @@ /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- * 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/. */ package org.mozilla.gecko.widget; import org.mozilla.gecko.R; -import org.mozilla.gecko.Tabs; import org.mozilla.gecko.prompts.PromptInput; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.content.Context; import android.content.res.Resources; -import android.graphics.Rect; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.Html; import android.text.TextUtils; -import android.text.method.LinkMovementMethod; -import android.text.style.ForegroundColorSpan; -import android.text.style.URLSpan; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.Spinner; -import android.widget.SpinnerAdapter; -import android.widget.TextView; import java.util.ArrayList; import java.util.List; -public class DoorHanger extends LinearLayout { - private static final String LOGTAG = "GeckoDoorHanger"; - - private static int sInputPadding = -1; - private static int sSpinnerTextColor = -1; - private static int sSpinnerTextSize = -1; - - private static final LayoutParams sButtonParams; - static { - sButtonParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1.0f); - } - - private final TextView mTextView; - private final ImageView mIcon; - private final LinearLayout mChoicesLayout; - - // Divider between doorhangers. - private final View mDivider; - - // The tab associated with this notification. - private final int mTabId; - - // Value used to identify the notification. - private final String mValue; +public class DefaultDoorHanger extends DoorHanger { + private static final String LOGTAG = "GeckoDefaultDoorHanger"; private final Resources mResources; + private static int sSpinnerTextColor = -1; private List<PromptInput> mInputs; private CheckBox mCheckBox; - private int mPersistence; - private boolean mPersistWhileVisible; - private long mTimeout; - - // Color used for dividers above and between buttons. - private int mDividerColor; - - public static enum Theme { - LIGHT, - DARK + public DefaultDoorHanger(Context context, DoorhangerConfig config) { + this(context, config, Type.DEFAULT); } - public interface OnButtonClickListener { - public void onButtonClick(DoorHanger dh, String tag); - } - - public DoorHanger(Context context, Theme theme) { - this(context, 0, null, theme); - } + public DefaultDoorHanger(Context context, DoorhangerConfig config, Type type) { + super(context, config, type); - public DoorHanger(Context context, int tabId, String value) { - this(context, tabId, value, Theme.LIGHT); - } - - private DoorHanger(Context context, int tabId, String value, Theme theme) { - super(context); - - mTabId = tabId; - mValue = value; mResources = getResources(); - if (sInputPadding == -1) { - sInputPadding = mResources.getDimensionPixelSize(R.dimen.doorhanger_padding); + if (sSpinnerTextColor == -1) { + sSpinnerTextColor = getResources().getColor(R.color.text_color_primary_disable_only); } - if (sSpinnerTextColor == -1) { - sSpinnerTextColor = mResources.getColor(R.color.text_color_primary_disable_only); - } - if (sSpinnerTextSize == -1) { - sSpinnerTextSize = mResources.getDimensionPixelSize(R.dimen.doorhanger_spinner_textsize); + loadConfig(config); + } + + @Override + protected void loadConfig(DoorhangerConfig config) { + final String message = config.getMessage(); + if (message != null) { + setMessage(message); } - setOrientation(VERTICAL); - - LayoutInflater.from(context).inflate(R.layout.doorhanger, this); - mTextView = (TextView) findViewById(R.id.doorhanger_title); - mIcon = (ImageView) findViewById(R.id.doorhanger_icon); - mChoicesLayout = (LinearLayout) findViewById(R.id.doorhanger_choices); - mDivider = findViewById(R.id.divider_doorhanger); - - setTheme(theme); - } + final JSONObject options = config.getOptions(); + if (options != null) { + setOptions(options); + } - private void setTheme(Theme theme) { - if (theme == Theme.LIGHT) { - // The default styles declared in doorhanger.xml are light-themed, so we just - // need to set the divider color that we'll use in addButton. - mDividerColor = mResources.getColor(R.color.divider_light); - - } else if (theme == Theme.DARK) { - mDividerColor = mResources.getColor(R.color.divider_dark); - - // Set a dark background, and use a smaller text size for dark-themed DoorHangers. - setBackgroundColor(mResources.getColor(R.color.doorhanger_background_dark)); - mTextView.setTextAppearance(getContext(), R.style.TextAppearance_Widget_DoorHanger_Small); - - // Set the inter-doorhanger divider color - mDivider.setBackgroundColor(mDividerColor); + final DoorhangerConfig.Link link = config.getLink(); + if (link != null) { + addLink(link.label, link.url, link.delimiter); } } - public int getTabId() { - return mTabId; - } - - public String getValue() { - return mValue; - } - + @Override public List<PromptInput> getInputs() { return mInputs; } + @Override public CheckBox getCheckBox() { return mCheckBox; } - public void showDivider() { - mDivider.setVisibility(View.VISIBLE); - } - - public void hideDivider() { - mDivider.setVisibility(View.GONE); - } - - public void setMessage(String message) { - Spanned markupMessage = Html.fromHtml(message); - mTextView.setMovementMethod(LinkMovementMethod.getInstance()); // Necessary for clickable links - mTextView.setText(markupMessage); - } - - public void setIcon(int resId) { - mIcon.setImageResource(resId); - mIcon.setVisibility(View.VISIBLE); - } - - public void addLink(String label, String url, String delimiter) { - String title = mTextView.getText().toString(); - SpannableString titleWithLink = new SpannableString(title + delimiter + label); - URLSpan linkSpan = new URLSpan(url) { - @Override - public void onClick(View view) { - Tabs.getInstance().loadUrlInTab(getURL()); - } - }; - - // Prevent text outside the link from flashing when clicked. - ForegroundColorSpan colorSpan = new ForegroundColorSpan(mTextView.getCurrentTextColor()); - titleWithLink.setSpan(colorSpan, 0, title.length(), 0); - - titleWithLink.setSpan(linkSpan, title.length() + 1, titleWithLink.length(), 0); - mTextView.setText(titleWithLink); - mTextView.setMovementMethod(LinkMovementMethod.getInstance()); - } - - public void addButton(final String text, final String tag, final OnButtonClickListener listener) { - final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null); - button.setText(text); - button.setTag(tag); - - button.setOnClickListener(new Button.OnClickListener() { - @Override - public void onClick(View v) { - listener.onButtonClick(DoorHanger.this, tag); - } - }); - - if (mChoicesLayout.getChildCount() == 0) { - // If this is the first button we're adding, make the choices layout visible. - mChoicesLayout.setVisibility(View.VISIBLE); - // Make the divider above the buttons visible. - View divider = findViewById(R.id.divider_choices); - divider.setVisibility(View.VISIBLE); - divider.setBackgroundColor(mDividerColor); - } else { - // Add a vertical divider between additional buttons. - Divider divider = new Divider(getContext(), null); - divider.setOrientation(Divider.Orientation.VERTICAL); - divider.setBackgroundColor(mDividerColor); - mChoicesLayout.addView(divider); - } - - mChoicesLayout.addView(button, sButtonParams); - } - + @Override public void setOptions(final JSONObject options) { - final int persistence = options.optInt("persistence"); - if (persistence > 0) { - mPersistence = persistence; - } - - mPersistWhileVisible = options.optBoolean("persistWhileVisible"); - - final long timeout = options.optLong("timeout"); - if (timeout > 0) { - mTimeout = timeout; - } - + super.setOptions(options); final JSONObject link = options.optJSONObject("link"); if (link != null) { try { final String linkLabel = link.getString("label"); final String linkUrl = link.getString("url"); addLink(linkLabel, linkUrl, " "); } catch (JSONException e) { } } @@ -250,104 +93,40 @@ public class DoorHanger extends LinearLa final ViewGroup group = (ViewGroup) findViewById(R.id.doorhanger_inputs); group.setVisibility(VISIBLE); for (int i = 0; i < inputs.length(); i++) { try { PromptInput input = PromptInput.getInput(inputs.getJSONObject(i)); mInputs.add(input); + final int padding = mResources.getDimensionPixelSize(R.dimen.doorhanger_padding); View v = input.getView(getContext()); styleInput(input, v); + v.setPadding(0, 0, 0, padding); group.addView(v); } catch(JSONException ex) { } } } final String checkBoxText = options.optString("checkbox"); if (!TextUtils.isEmpty(checkBoxText)) { mCheckBox = (CheckBox) findViewById(R.id.doorhanger_checkbox); mCheckBox.setText(checkBoxText); mCheckBox.setVisibility(VISIBLE); } } private void styleInput(PromptInput input, View view) { if (input instanceof PromptInput.MenulistInput) { - styleSpinner(input, view); - } else { - // add some top and bottom padding to separate inputs - view.setPadding(0, sInputPadding, - 0, sInputPadding); + styleDropdownInputs(input, view); } + view.setPadding(0, 0, 0, mResources.getDimensionPixelSize(R.dimen.doorhanger_padding)); } - private void styleSpinner(PromptInput input, View view) { + private void styleDropdownInputs(PromptInput input, View view) { PromptInput.MenulistInput spinInput = (PromptInput.MenulistInput) input; - /* Spinners have some intrinsic padding. To force the spinner's text to line up with - * the doorhanger text, we have to take that padding into account. - * - * |-----A-------| <-- Normal doorhanger message - * |-B-|---C+D---| <-- (optional) Spinner Label - * |-B-|-C-|--D--| <-- Spinner - * - * A - Desired padding (sInputPadding) - * B - Final padding applied to input element (sInputPadding - rect.left - textPadding). - * C - Spinner background drawable padding (rect.left). - * D - Spinner inner TextView padding (textPadding). - */ - - // First get the padding of the selected view inside the spinner. Since the spinner - // hasn't been shown yet, we get this view directly from the adapter. - Spinner spinner = spinInput.spinner; - SpinnerAdapter adapter = spinner.getAdapter(); - View dropView = adapter.getView(0, null, spinner); - int textPadding = 0; - if (dropView != null) { - textPadding = dropView.getPaddingLeft(); - } - - // Then get the intrinsic padding built into the background image of the spinner. - Rect rect = new Rect(); - spinner.getBackground().getPadding(rect); - - // Set the difference in padding to the spinner view to align it with doorhanger text. - view.setPadding(sInputPadding - rect.left - textPadding, 0, rect.right, sInputPadding); - if (spinInput.textView != null) { spinInput.textView.setTextColor(sSpinnerTextColor); - spinInput.textView.setTextSize(sSpinnerTextSize); - - // If this spinner has a label, offset it to also be aligned with the doorhanger text. - spinInput.textView.setPadding(rect.left + textPadding, 0, 0, 0); } } - - - /* - * Checks with persistence and timeout options to see if it's okay to remove a doorhanger. - * - * @param isShowing Whether or not this doorhanger is currently visible to the user. - * (e.g. the DoorHanger view might be VISIBLE, but its parent could be hidden) - */ - public boolean shouldRemove(boolean isShowing) { - if (mPersistWhileVisible && isShowing) { - // We still want to decrement mPersistence, even if the popup is showing - if (mPersistence != 0) - mPersistence--; - return false; - } - - // If persistence is set to -1, the doorhanger will never be - // automatically removed. - if (mPersistence != 0) { - mPersistence--; - return false; - } - - if (System.currentTimeMillis() <= mTimeout) { - return false; - } - - return true; - } }
--- a/mobile/android/base/widget/DoorHanger.java +++ b/mobile/android/base/widget/DoorHanger.java @@ -1,353 +1,235 @@ /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- * 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/. */ package org.mozilla.gecko.widget; -import org.mozilla.gecko.R; -import org.mozilla.gecko.Tabs; -import org.mozilla.gecko.prompts.PromptInput; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import android.content.Context; -import android.content.res.Resources; -import android.graphics.Rect; +import android.text.Html; import android.text.SpannableString; import android.text.Spanned; -import android.text.Html; -import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.style.ForegroundColorSpan; import android.text.style.URLSpan; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.widget.Button; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.Spinner; -import android.widget.SpinnerAdapter; import android.widget.TextView; +import org.json.JSONObject; +import org.mozilla.gecko.R; +import org.mozilla.gecko.Tabs; +import org.mozilla.gecko.prompts.PromptInput; -import java.util.ArrayList; import java.util.List; -public class DoorHanger extends LinearLayout { - private static final String LOGTAG = "GeckoDoorHanger"; +public abstract class DoorHanger extends LinearLayout { - private static int sInputPadding = -1; - private static int sSpinnerTextColor = -1; - private static int sSpinnerTextSize = -1; + public static DoorHanger Get(Context context, DoorhangerConfig config) { + final Type type = config.getType(); + if (type != null) { + switch (type) { + case LOGIN: + return new LoginDoorHanger(context, config); + case SITE: + return new DefaultDoorHanger(context, config, type); + } + } + + return new DefaultDoorHanger(context, config); + } + + public static enum Type { DEFAULT, LOGIN, SITE } + + public interface OnButtonClickListener { + public void onButtonClick(DoorHanger dh, String tag); + } private static final LayoutParams sButtonParams; static { sButtonParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1.0f); } - private final TextView mTextView; - private final ImageView mIcon; - private final LinearLayout mChoicesLayout; + private static final String LOGTAG = "GeckoDoorHanger"; // Divider between doorhangers. private final View mDivider; - // The tab associated with this notification. + private final LinearLayout mButtonsContainer; + + // The tab this doorhanger is associated with. private final int mTabId; - // Value used to identify the notification. - private final String mValue; + // DoorHanger identifier. + private final String mIdentifier; - private final Resources mResources; + private final ImageView mIcon; + private final TextView mMessage; + + protected Context mContext; + + protected int mDividerColor; - private List<PromptInput> mInputs; - private CheckBox mCheckBox; + protected boolean mPersistWhileVisible; + protected int mPersistenceCount; + protected long mTimeout; - private int mPersistence; - private boolean mPersistWhileVisible; - private long mTimeout; - - // Color used for dividers above and between buttons. - private int mDividerColor; + protected DoorHanger(Context context, DoorhangerConfig config, Type type) { + super(context); + mContext = context; + mTabId = config.getTabId(); + mIdentifier = config.getId(); - public static enum Theme { - LIGHT, - DARK - } + int resource; + switch (type) { + case LOGIN: + resource = R.layout.login_doorhanger; + break; + default: + resource = R.layout.doorhanger; + } - public interface OnButtonClickListener { - public void onButtonClick(DoorHanger dh, String tag); - } + LayoutInflater.from(context).inflate(resource, this); + mDivider = findViewById(R.id.divider_doorhanger); + mIcon = (ImageView) findViewById(R.id.doorhanger_icon); + mMessage = (TextView) findViewById(R.id.doorhanger_message); + if (type == Type.SITE) { + mMessage.setTextAppearance(getContext(), R.style.TextAppearance_DoorHanger_Small); + } + mButtonsContainer = (LinearLayout) findViewById(R.id.doorhanger_buttons); - public DoorHanger(Context context, Theme theme) { - this(context, 0, null, theme); - } - - public DoorHanger(Context context, int tabId, String value) { - this(context, tabId, value, Theme.LIGHT); + mDividerColor = getResources().getColor(R.color.divider_light); + setOrientation(VERTICAL); } - private DoorHanger(Context context, int tabId, String value, Theme theme) { - super(context); - - mTabId = tabId; - mValue = value; - mResources = getResources(); + abstract protected void loadConfig(DoorhangerConfig config); - if (sInputPadding == -1) { - sInputPadding = mResources.getDimensionPixelSize(R.dimen.doorhanger_padding); - } - if (sSpinnerTextColor == -1) { - sSpinnerTextColor = mResources.getColor(R.color.text_color_primary_disable_only); - } - if (sSpinnerTextSize == -1) { - sSpinnerTextSize = mResources.getDimensionPixelSize(R.dimen.doorhanger_spinner_textsize); + protected void setOptions(final JSONObject options) { + final int persistence = options.optInt("persistence"); + if (persistence > 0) { + mPersistenceCount = persistence; } - setOrientation(VERTICAL); - - LayoutInflater.from(context).inflate(R.layout.doorhanger, this); - mTextView = (TextView) findViewById(R.id.doorhanger_title); - mIcon = (ImageView) findViewById(R.id.doorhanger_icon); - mChoicesLayout = (LinearLayout) findViewById(R.id.doorhanger_choices); - mDivider = findViewById(R.id.divider_doorhanger); - - setTheme(theme); - } + mPersistWhileVisible = options.optBoolean("persistWhileVisible"); - private void setTheme(Theme theme) { - if (theme == Theme.LIGHT) { - // The default styles declared in doorhanger.xml are light-themed, so we just - // need to set the divider color that we'll use in addButton. - mDividerColor = mResources.getColor(R.color.divider_light); - - } else if (theme == Theme.DARK) { - mDividerColor = mResources.getColor(R.color.divider_dark); - - // Set a dark background, and use a smaller text size for dark-themed DoorHangers. - setBackgroundColor(mResources.getColor(R.color.doorhanger_background_dark)); - mTextView.setTextAppearance(getContext(), R.style.TextAppearance_Widget_DoorHanger_Small); - - // Set the inter-doorhanger divider color - mDivider.setBackgroundColor(mDividerColor); + final long timeout = options.optLong("timeout"); + if (timeout > 0) { + mTimeout = timeout; } - } + } public int getTabId() { return mTabId; } - public String getValue() { - return mValue; - } - - public List<PromptInput> getInputs() { - return mInputs; - } - - public CheckBox getCheckBox() { - return mCheckBox; + public String getIdentifier() { + return mIdentifier; } public void showDivider() { mDivider.setVisibility(View.VISIBLE); } public void hideDivider() { mDivider.setVisibility(View.GONE); } - public void setMessage(String message) { - Spanned markupMessage = Html.fromHtml(message); - mTextView.setMovementMethod(LinkMovementMethod.getInstance()); // Necessary for clickable links - mTextView.setText(markupMessage); - } - public void setIcon(int resId) { mIcon.setImageResource(resId); mIcon.setVisibility(View.VISIBLE); } - public void addLink(String label, String url, String delimiter) { - String title = mTextView.getText().toString(); + protected void setMessage(String message) { + Spanned markupMessage = Html.fromHtml(message); + mMessage.setText(markupMessage); + } + + protected void addLink(String label, String url, String delimiter) { + String title = mMessage.getText().toString(); SpannableString titleWithLink = new SpannableString(title + delimiter + label); URLSpan linkSpan = new URLSpan(url) { @Override public void onClick(View view) { Tabs.getInstance().loadUrlInTab(getURL()); } }; // Prevent text outside the link from flashing when clicked. - ForegroundColorSpan colorSpan = new ForegroundColorSpan(mTextView.getCurrentTextColor()); + ForegroundColorSpan colorSpan = new ForegroundColorSpan(mMessage.getCurrentTextColor()); titleWithLink.setSpan(colorSpan, 0, title.length(), 0); titleWithLink.setSpan(linkSpan, title.length() + 1, titleWithLink.length(), 0); - mTextView.setText(titleWithLink); - mTextView.setMovementMethod(LinkMovementMethod.getInstance()); + mMessage.setText(titleWithLink); + mMessage.setMovementMethod(LinkMovementMethod.getInstance()); } public void addButton(final String text, final String tag, final OnButtonClickListener listener) { final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null); button.setText(text); button.setTag(tag); button.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { listener.onButtonClick(DoorHanger.this, tag); } }); - if (mChoicesLayout.getChildCount() == 0) { + if (mButtonsContainer.getChildCount() == 0) { // If this is the first button we're adding, make the choices layout visible. - mChoicesLayout.setVisibility(View.VISIBLE); + mButtonsContainer.setVisibility(View.VISIBLE); // Make the divider above the buttons visible. - View divider = findViewById(R.id.divider_choices); + View divider = findViewById(R.id.divider_buttons); divider.setVisibility(View.VISIBLE); - divider.setBackgroundColor(mDividerColor); } else { // Add a vertical divider between additional buttons. Divider divider = new Divider(getContext(), null); divider.setOrientation(Divider.Orientation.VERTICAL); divider.setBackgroundColor(mDividerColor); - mChoicesLayout.addView(divider); - } - - mChoicesLayout.addView(button, sButtonParams); - } - - public void setOptions(final JSONObject options) { - final int persistence = options.optInt("persistence"); - if (persistence > 0) { - mPersistence = persistence; - } - - mPersistWhileVisible = options.optBoolean("persistWhileVisible"); - - final long timeout = options.optLong("timeout"); - if (timeout > 0) { - mTimeout = timeout; - } - - final JSONObject link = options.optJSONObject("link"); - if (link != null) { - try { - final String linkLabel = link.getString("label"); - final String linkUrl = link.getString("url"); - addLink(linkLabel, linkUrl, " "); - } catch (JSONException e) { } - } - - final JSONArray inputs = options.optJSONArray("inputs"); - if (inputs != null) { - mInputs = new ArrayList<PromptInput>(); - - final ViewGroup group = (ViewGroup) findViewById(R.id.doorhanger_inputs); - group.setVisibility(VISIBLE); - - for (int i = 0; i < inputs.length(); i++) { - try { - PromptInput input = PromptInput.getInput(inputs.getJSONObject(i)); - mInputs.add(input); - - View v = input.getView(getContext()); - styleInput(input, v); - group.addView(v); - } catch(JSONException ex) { } - } + mButtonsContainer.addView(divider); } - final String checkBoxText = options.optString("checkbox"); - if (!TextUtils.isEmpty(checkBoxText)) { - mCheckBox = (CheckBox) findViewById(R.id.doorhanger_checkbox); - mCheckBox.setText(checkBoxText); - mCheckBox.setVisibility(VISIBLE); - } - } - - private void styleInput(PromptInput input, View view) { - if (input instanceof PromptInput.MenulistInput) { - styleSpinner(input, view); - } else { - // add some top and bottom padding to separate inputs - view.setPadding(0, sInputPadding, - 0, sInputPadding); - } + mButtonsContainer.addView(button, sButtonParams); } - private void styleSpinner(PromptInput input, View view) { - PromptInput.MenulistInput spinInput = (PromptInput.MenulistInput) input; - - /* Spinners have some intrinsic padding. To force the spinner's text to line up with - * the doorhanger text, we have to take that padding into account. - * - * |-----A-------| <-- Normal doorhanger message - * |-B-|---C+D---| <-- (optional) Spinner Label - * |-B-|-C-|--D--| <-- Spinner - * - * A - Desired padding (sInputPadding) - * B - Final padding applied to input element (sInputPadding - rect.left - textPadding). - * C - Spinner background drawable padding (rect.left). - * D - Spinner inner TextView padding (textPadding). - */ - - // First get the padding of the selected view inside the spinner. Since the spinner - // hasn't been shown yet, we get this view directly from the adapter. - Spinner spinner = spinInput.spinner; - SpinnerAdapter adapter = spinner.getAdapter(); - View dropView = adapter.getView(0, null, spinner); - int textPadding = 0; - if (dropView != null) { - textPadding = dropView.getPaddingLeft(); - } - - // Then get the intrinsic padding built into the background image of the spinner. - Rect rect = new Rect(); - spinner.getBackground().getPadding(rect); - - // Set the difference in padding to the spinner view to align it with doorhanger text. - view.setPadding(sInputPadding - rect.left - textPadding, 0, rect.right, sInputPadding); - - if (spinInput.textView != null) { - spinInput.textView.setTextColor(sSpinnerTextColor); - spinInput.textView.setTextSize(sSpinnerTextSize); - - // If this spinner has a label, offset it to also be aligned with the doorhanger text. - spinInput.textView.setPadding(rect.left + textPadding, 0, 0, 0); - } - } - - /* * Checks with persistence and timeout options to see if it's okay to remove a doorhanger. * * @param isShowing Whether or not this doorhanger is currently visible to the user. * (e.g. the DoorHanger view might be VISIBLE, but its parent could be hidden) */ public boolean shouldRemove(boolean isShowing) { if (mPersistWhileVisible && isShowing) { // We still want to decrement mPersistence, even if the popup is showing - if (mPersistence != 0) - mPersistence--; + if (mPersistenceCount != 0) + mPersistenceCount--; return false; } // If persistence is set to -1, the doorhanger will never be // automatically removed. - if (mPersistence != 0) { - mPersistence--; + if (mPersistenceCount != 0) { + mPersistenceCount--; return false; } if (System.currentTimeMillis() <= mTimeout) { return false; } return true; } + + // TODO: remove and expose through instance Button Handler. + public List<PromptInput> getInputs() { + return null; + } + + public CheckBox getCheckBox() { + return null; + } + }
new file mode 100644 --- /dev/null +++ b/mobile/android/base/widget/DoorhangerConfig.java @@ -0,0 +1,93 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * 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/. */ + +package org.mozilla.gecko.widget; + +import org.json.JSONArray; +import org.json.JSONObject; + +import org.mozilla.gecko.widget.DoorHanger.Type; + +public class DoorhangerConfig { + + public static class Link { + public final String label; + public final String url; + public final String delimiter; + + private Link(String label, String url, String delimiter) { + this.label = label; + this.url = url; + this.delimiter = delimiter; + } + } + + private final int tabId; + private final String id; + private DoorHanger.Type type; + private String message; + private JSONObject options; + private Link link; + private JSONArray buttons; + + public DoorhangerConfig() { + // XXX: This should only be used by SiteIdentityPopup doorhangers which + // don't need tab or id references, until bug 1141904 unifies doorhangers. + this(-1, null); + } + + public DoorhangerConfig(int tabId, String id) { + this.tabId = tabId; + this.id = id; + } + + public int getTabId() { + return tabId; + } + + public String getId() { + return id; + } + + public void setType(Type type) { + this.type = type; + } + + public Type getType() { + return type; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setOptions(JSONObject options) { + this.options = options; + } + + public JSONObject getOptions() { + return options; + } + + public void setButtons(JSONArray buttons) { + this.buttons = buttons; + } + + public JSONArray getButtons() { + return buttons; + } + + public void setLink(String label, String url, String delimiter) { + this.link = new Link(label, url, delimiter); + } + + public Link getLink() { + return link; + } +}
new file mode 100644 --- /dev/null +++ b/mobile/android/base/widget/LoginDoorHanger.java @@ -0,0 +1,79 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * 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/. */ + +package org.mozilla.gecko.widget; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.util.Log; +import android.view.View; +import android.widget.TextView; +import ch.boye.httpclientandroidlib.util.TextUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.mozilla.gecko.R; +import org.mozilla.gecko.favicons.Favicons; +import org.mozilla.gecko.favicons.OnFaviconLoadedListener; + +public class LoginDoorHanger extends DoorHanger { + private static final String LOGTAG = "LoginDoorHanger"; + + final TextView mTitle; + final TextView mLogin; + + public LoginDoorHanger(Context context, DoorhangerConfig config) { + super(context, config, Type.LOGIN); + + mTitle = (TextView) findViewById(R.id.doorhanger_title); + mLogin = (TextView) findViewById(R.id.doorhanger_login); + + loadConfig(config); + } + + @Override + protected void loadConfig(DoorhangerConfig config) { + setOptions(config.getOptions()); + setMessage(config.getMessage()); + + } + + @Override + protected void setOptions(final JSONObject options) { + super.setOptions(options); + + final JSONObject titleObj = options.optJSONObject("title"); + if (titleObj != null) { + + try { + final String text = titleObj.getString("text"); + mTitle.setText(text); + } catch (JSONException e) { + Log.e(LOGTAG, "Error loading title from options JSON"); + } + + final String resource = titleObj.optString("resource"); + if (resource != null) { + Favicons.getSizedFaviconForPageFromLocal(mContext, resource, 32, new OnFaviconLoadedListener() { + @Override + public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) { + if (favicon != null) { + mTitle.setCompoundDrawablesWithIntrinsicBounds(new BitmapDrawable(mContext.getResources(), favicon), null, null, null); + mTitle.setCompoundDrawablePadding((int) mContext.getResources().getDimension(R.dimen.doorhanger_drawable_padding)); + } + } + }); + } + } + + final String subtext = options.optString("subtext"); + if (!TextUtils.isEmpty(subtext)) { + mLogin.setText(subtext); + mLogin.setVisibility(View.VISIBLE); + } else { + mLogin.setVisibility(View.GONE); + } + } +}
--- a/mobile/android/chrome/content/aboutPasswords.js +++ b/mobile/android/chrome/content/aboutPasswords.js @@ -131,32 +131,48 @@ let Passwords = { loginItem.addEventListener("click", () => { let prompt = new Prompt({ window: window, }); let menuItems = [ { label: gStringBundle.GetStringFromName("passwordsMenu.copyPassword") }, { label: gStringBundle.GetStringFromName("passwordsMenu.copyUsername") }, - { label: gStringBundle.GetStringFromName("passwordsMenu.details") } ]; + { label: gStringBundle.GetStringFromName("passwordsMenu.details") }, + { label: gStringBundle.GetStringFromName("passwordsMenu.delete") } ]; prompt.setSingleChoiceItems(menuItems); prompt.show((data) => { // Switch on indices of buttons, as they were added when creating login item. switch (data.button) { case 0: copyStringAndToast(login.password, gStringBundle.GetStringFromName("passwordsDetails.passwordCopied")); break; case 1: copyStringAndToast(login.username, gStringBundle.GetStringFromName("passwordsDetails.usernameCopied")); break; case 2: this._showDetails(loginItem); history.pushState({ id: login.guid }, document.title); break; + case 3: + let confirmPrompt = new Prompt({ + window: window, + message: gStringBundle.GetStringFromName("passwordsDialog.confirmDelete"), + buttons: [ + gStringBundle.GetStringFromName("passwordsDialog.confirm"), + gStringBundle.GetStringFromName("passwordsDialog.cancel") ] + }); + confirmPrompt.show((data) => { + switch (data.button) { + case 0: + // Corresponds to "confirm" button. + Services.logins.removeLogin(login); + } + }); } }); }, true); // Create item icon. let img = document.createElement("div"); img.className = "icon";
--- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -2208,21 +2208,34 @@ var NativeWindow = { * persistence: An integer. The notification will not automatically * dismiss for this many page loads. If persistence is set * to -1, the doorhanger will never automatically dismiss. * persistWhileVisible: * A boolean. If true, a visible notification will always * persist across location changes. * timeout: A time in milliseconds. The notification will not * automatically dismiss before this time. + * * checkbox: A string to appear next to a checkbox under the notification * message. The button callback functions will be called with * the checked state as an argument. + * + * title: An object that specifies text to display as the title, and + * optionally a resource, such as a favicon cache url that can be + * used to fetch a favicon from the FaviconCache. (This can be + * generalized to other resources if the situation arises.) + * { text: <title>, + * resource: <resource_url> } + * + * subtext: A string to appear below the doorhanger message. + * + * @param aCategory + * Doorhanger type to display (e.g., LOGIN) */ - show: function(aMessage, aValue, aButtons, aTabID, aOptions) { + show: function(aMessage, aValue, aButtons, aTabID, aOptions, aCategory) { if (aButtons == null) { aButtons = []; } aButtons.forEach((function(aButton) { this._callbacks[this._callbacksId] = { cb: aButton.callback, prompt: this._promptId }; aButton.callback = this._callbacksId; this._callbacksId++; @@ -2231,17 +2244,18 @@ var NativeWindow = { this._promptId++; let json = { type: "Doorhanger:Add", message: aMessage, value: aValue, buttons: aButtons, // use the current tab if none is provided tabID: aTabID || BrowserApp.selectedTab.id, - options: aOptions || {} + options: aOptions || {}, + category: aCategory }; Messaging.sendRequest(json); }, hide: function(aValue, aTabID) { Messaging.sendRequest({ type: "Doorhanger:Remove", value: aValue,
--- a/mobile/android/components/LoginManagerPrompter.js +++ b/mobile/android/components/LoginManagerPrompter.js @@ -58,18 +58,22 @@ LoginManagerPrompter.prototype = { return this.__promptService; }, __strBundle : null, // String bundle for L10N get _strBundle() { if (!this.__strBundle) { var bunService = Cc["@mozilla.org/intl/stringbundle;1"]. getService(Ci.nsIStringBundleService); - this.__strBundle = bunService.createBundle( - "chrome://passwordmgr/locale/passwordmgr.properties"); + this.__strBundle = { + pwmgr : bunService.createBundle( + "chrome://passwordmgr/locale/passwordmgr.properties"), + brand : bunService.createBundle("chrome://branding/locale/brand.properties") + }; + if (!this.__strBundle) throw "String bundle for Login Manager not present!"; } return this.__strBundle; }, @@ -131,86 +135,99 @@ LoginManagerPrompter.prototype = { Services.telemetry.getHistogramById("PWMGR_PROMPT_REMEMBER_ACTION").add(PROMPT_DISPLAYED); }, /* * _showLoginNotification * * Displays a notification doorhanger. + * @param aName + * Name of notification + * @param aTitle + * Object with title and optional resource to display with the title, such as a favicon key + * @param aBody + * String message to be displayed in the doorhanger + * @param aButtons + * Buttons to display with the doorhanger + * @param aSubtext + * String to be displayed below the aBody message * */ - _showLoginNotification : function (aName, aText, aButtons) { + _showLoginNotification : function (aName, aTitle, aBody, aButtons, aSubtext) { this.log("Adding new " + aName + " notification bar"); let notifyWin = this._window.top; let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject; let browser = chromeWin.BrowserApp.getBrowserForWindow(notifyWin); let tabID = chromeWin.BrowserApp.getTabForBrowser(browser).id; // The page we're going to hasn't loaded yet, so we want to persist // across the first location change. // Sites like Gmail perform a funky redirect dance before you end up // at the post-authentication page. I don't see a good way to // heuristically determine when to ignore such location changes, so // we'll try ignoring location changes based on a time interval. let options = { persistWhileVisible: true, - timeout: Date.now() + 10000 + timeout: Date.now() + 10000, + title: aTitle, + subtext: aSubtext } var nativeWindow = this._getNativeWindow(); if (nativeWindow) - nativeWindow.doorhanger.show(aText, aName, aButtons, tabID, options); + nativeWindow.doorhanger.show(aBody, aName, aButtons, tabID, options, "LOGIN"); }, /* * _showSaveLoginNotification * * Displays a notification doorhanger (rather than a popup), to allow the user to * save the specified login. This allows the user to see the results of * their login, and only save a login which they know worked. * */ _showSaveLoginNotification : function (aLogin) { - var displayHost = this._getShortDisplayHost(aLogin.hostname); - var notificationText; + let brandShortName = this._strBundle.brand.GetStringFromName("brandShortName"); + let notificationText = this._getLocalizedString("saveLogin", [brandShortName]); + + let displayHost = this._getShortDisplayHost(aLogin.hostname); + let title = { text: displayHost, resource: aLogin.hostname }; + let subtext = null; + if (aLogin.username) { - var displayUser = this._sanitizeUsername(aLogin.username); - notificationText = this._getLocalizedString("savePassword", [displayUser, displayHost]); - } else { - notificationText = this._getLocalizedString("savePasswordNoUser", [displayHost]); + subtext = this._sanitizeUsername(aLogin.username); } - // The callbacks in |buttons| have a closure to access the variables // in scope here; set one to |this._pwmgr| so we can get back to pwmgr // without a getService() call. var pwmgr = this._pwmgr; let promptHistogram = Services.telemetry.getHistogramById("PWMGR_PROMPT_REMEMBER_ACTION"); var buttons = [ { - label: this._getLocalizedString("saveButton"), + label: this._getLocalizedString("neverButton"), + callback: function() { + promptHistogram.add(PROMPT_NEVER); + pwmgr.setLoginSavingEnabled(aLogin.hostname, false); + } + }, + { + label: this._getLocalizedString("rememberButton"), callback: function() { pwmgr.addLogin(aLogin); promptHistogram.add(PROMPT_ADD); } - }, - { - label: this._getLocalizedString("dontSaveButton"), - callback: function() { - promptHistogram.add(PROMPT_NOTNOW); - // Don't set a permanent exception - } } ]; - this._showLoginNotification("password-save", notificationText, buttons); + this._showLoginNotification("password-save", title, notificationText, buttons, subtext); }, /* * promptToChangePassword * * Called when we think we detect a password change for an existing * login, when the form being submitted contains multiple password * fields. @@ -231,40 +248,43 @@ LoginManagerPrompter.prototype = { var notificationText; if (aOldLogin.username) { let displayUser = this._sanitizeUsername(aOldLogin.username); notificationText = this._getLocalizedString("updatePassword", [displayUser]); } else { notificationText = this._getLocalizedString("updatePasswordNoUser"); } + let displayHost = this._getShortDisplayHost(aOldLogin.hostname); + let title = { text: displayHost, resource: aOldLogin.hostname }; + // The callbacks in |buttons| have a closure to access the variables // in scope here; set one to |this._pwmgr| so we can get back to pwmgr // without a getService() call. var self = this; let promptHistogram = Services.telemetry.getHistogramById("PWMGR_PROMPT_UPDATE_ACTION"); var buttons = [ { + label: this._getLocalizedString("dontUpdateButton"), + callback: function() { + promptHistogram.add(PROMPT_NOTNOW); + // do nothing + } + }, + { label: this._getLocalizedString("updateButton"), callback: function() { self._updateLogin(aOldLogin, aNewPassword); promptHistogram.add(PROMPT_UPDATE); } - }, - { - label: this._getLocalizedString("dontUpdateButton"), - callback: function() { - promptHistogram.add(PROMPT_NOTNOW); - // do nothing - } } ]; - this._showLoginNotification("password-change", notificationText, buttons); + this._showLoginNotification("password-change", title, notificationText, buttons); }, /* * promptToChangePasswordWithUsernames * * Called when we detect a password change in a form submission, but we * don't know which existing login (username) it's for. Asks the user @@ -372,20 +392,20 @@ LoginManagerPrompter.prototype = { * (etc) * * Returns the localized string for the specified key, * formatted if required. * */ _getLocalizedString : function (key, formatArgs) { if (formatArgs) - return this._strBundle.formatStringFromName( + return this._strBundle.pwmgr.formatStringFromName( key, formatArgs, formatArgs.length); else - return this._strBundle.GetStringFromName(key); + return this._strBundle.pwmgr.GetStringFromName(key); }, /* * _sanitizeUsername * * Sanitizes the specified username, by stripping quotes and truncating if * it's too long. This helps prevent an evil site from messing with the
--- a/mobile/android/components/SessionStore.js +++ b/mobile/android/components/SessionStore.js @@ -83,16 +83,17 @@ SessionStore.prototype = { observerService.addObserver(this, "domwindowclosed", true); observerService.addObserver(this, "browser:purge-session-history", true); observerService.addObserver(this, "Session:Restore", true); observerService.addObserver(this, "application-background", true); observerService.addObserver(this, "ClosedTabs:StartNotifications", true); observerService.addObserver(this, "ClosedTabs:StopNotifications", true); observerService.addObserver(this, "last-pb-context-exited", true); observerService.addObserver(this, "Session:RestoreRecentTabs", true); + observerService.addObserver(this, "Tabs:OpenMultiple", true); break; case "final-ui-startup": observerService.removeObserver(this, "final-ui-startup"); this.init(); break; case "domwindowopened": { let window = aSubject; window.addEventListener("load", function() { @@ -152,16 +153,21 @@ SessionStore.prototype = { let data = JSON.parse(aData); this.restoreLastSession(data.sessionString); } else { // Not doing a restore; just send restore message Services.obs.notifyObservers(null, "sessionstore-windows-restored", ""); } break; } + case "Tabs:OpenMultiple": { + let data = JSON.parse(aData); + this._openTabs(data); + break; + } case "application-background": // We receive this notification when Android's onPause callback is // executed. After onPause, the application may be terminated at any // point without notice; therefore, we must synchronously write out any // pending save state to ensure that this data does not get lost. this.flushPendingState(); break; case "ClosedTabs:StartNotifications": @@ -905,16 +911,31 @@ SessionStore.prototype = { shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap, childDocIdents), i); } } return shEntry; }, + // This function iterates through a list of urls opening a new tab for each. + _openTabs: function ss_openTabs(aData) { + let window = Services.wm.getMostRecentWindow("navigator:browser"); + for (let i = 0; i < aData.urls.length; i++) { + let url = aData.urls[i]; + let params = { + selected: (i == aData.urls.length - 1), + isPrivate: false, + desktopMode: false, + }; + + let tab = window.BrowserApp.addTab(url, params); + } + }, + // This function iterates through a list of tab data restoring session for each of them. _restoreTabs: function ss_restoreTabs(aData) { let window = Services.wm.getMostRecentWindow("navigator:browser"); for (let i = 0; i < aData.tabs.length; i++) { let tabData = JSON.parse(aData.tabs[i]); let params = { selected: (i == aData.tabs.length - 1), isPrivate: tabData.isPrivate,
--- a/mobile/android/locales/en-US/chrome/aboutPasswords.properties +++ b/mobile/android/locales/en-US/chrome/aboutPasswords.properties @@ -1,13 +1,18 @@ # 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/. passwordsMenu.copyPassword=Copy password passwordsMenu.copyUsername=Copy username passwordsMenu.details=Details +passwordsMenu.delete=Delete + +passwordsDialog.confirmDelete=Delete this login? +passwordsDialog.confirm=OK +passwordsDialog.cancel=Cancel passwordsDetails.age=Age: %S days passwordsDetails.copyFailed=Copy failed passwordsDetails.passwordCopied=Password copied passwordsDetails.usernameCopied=Username copied
--- a/mobile/android/themes/core/aboutReader.css +++ b/mobile/android/themes/core/aboutReader.css @@ -192,28 +192,27 @@ body { } /* Images marked to be shown edge-to-edge with an optional captio ntext */ .content p > img:only-child, .content p > a:only-child > img:only-child, .content .wp-caption img, .content figure img { - max-width: none !important; - height: auto !important; - display: block !important; - margin-top: 0px !important; - margin-bottom: 32px !important; + display: block; + margin-left: auto; + margin-right: auto; } -/* If image is place inside one of these blocks - there's no need to add margin at the bottom */ -.content .wp-caption img, -.content figure img { - margin-bottom: 0px !important; +/* Account for body padding to make image full width */ +.content img[moz-reader-full-width] { + width: calc(100% + 40px); + margin-left: -20px; + margin-right: -20px; + max-width: none !important; } /* Image caption text */ .content .caption, .content .wp-caption-text, .content figcaption { font-family: sans-serif; margin: 0px !important;
--- a/mobile/locales/en-US/overrides/passwordmgr.properties +++ b/mobile/locales/en-US/overrides/passwordmgr.properties @@ -1,19 +1,16 @@ # 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/. -# 1st string is the username for the login, 2nd is the login's hostname. -# Note that long usernames may be truncated. -savePassword=Save password for "%1$S" on %2$S? -# String is the login's hostname -savePasswordNoUser=Save password on %S? -saveButton=Save -dontSaveButton=Don't save +# String will be replaced by brandShortName. +saveLogin=Would you like %S to remember this login? +rememberButton=Remember +neverButton=Never # String is the login's hostname updatePassword=Update saved password for %S? updatePasswordNoUser=Update saved password for this login? updateButton=Update dontUpdateButton=Don't update userSelectText=Please confirm which user you are changing the password for
--- a/testing/docker/tester/Dockerfile +++ b/testing/docker/tester/Dockerfile @@ -5,16 +5,17 @@ MAINTAINER Jonas Finnemann Jensen <jo COPY b2g-desktop-config.py /home/worker/b2g-desktop-config.py COPY dot-config /home/worker/.config COPY dot-pulse /home/worker/.pulse COPY bin /home/worker/bin COPY mozharness_configs /home/worker/mozharness_configs COPY buildprops.json /home/worker/buildprops.json ADD https://s3-us-west-2.amazonaws.com/test-caching/packages/linux64-stackwalk /usr/local/bin/linux64-minidump_stackwalk ADD https://raw.githubusercontent.com/taskcluster/buildbot-step/master/buildbot_step /home/worker/bin/buildbot_step +COPY tc-vcs-config.yml /etc/taskcluster-vcs.yml # Run test setup script RUN chmod u+x /home/worker/bin/buildbot_step RUN chmod u+x /usr/local/bin/linux64-minidump_stackwalk RUN apt-get install -y python-pip && pip install virtualenv; RUN mkdir Documents; mkdir Pictures; mkdir Music; mkdir Videos; mkdir artifacts RUN chown -R worker:worker /home/worker/* /home/worker/.*
--- a/testing/docker/tester/VERSION +++ b/testing/docker/tester/VERSION @@ -1,1 +1,1 @@ -0.2.9 +0.2.10
new file mode 100644 --- /dev/null +++ b/testing/docker/tester/tc-vcs-config.yml @@ -0,0 +1,40 @@ +# Default configuration used by the tc-vs tools these can be overridden by +# passing the config you wish to use over the command line... +git: git +hg: hg + +repoCache: + # Repo url to clone when running repo init.. + repoUrl: https://git.mozilla.org/external/google/gerrit/git-repo.git + # Version of repo to utilize... + repoRevision: master + # The root where all downloaded cache files are stored on the local machine... + cacheDir: '{{env.HOME}}/.tc-vcs-repo/' + # Name/prefixed used as part of the base url. + cacheName: sources/{{name}}.tar.gz + # Command used to upload the tarball + uploadTar: "curl --header 'Content-Type: application/x-tar' --header 'Content-Encoding: gzip' -X PUT --data-binary @'{{source}}' '{{url}}'" + # Large http get requests are often slower using nodes built in http layer so + # we utilize a subprocess which is responsible for fetching... + get: curl --connect-timeout 30 --speed-limit 500000 -L -o {{dest}} {{url}} + # Used to create clone tarball + compress: tar -czf {{dest}} {{source}} + # All cache urls use tar + gz this is the command used to extract those files + # downloaded by the "get" command. + extract: tar -x -z -C {{dest}} -f {{source}} + +cloneCache: + # The root where all downloaded cache files are stored on the local machine... + cacheDir: '{{env.HOME}}/.tc-vcs/' + # Command used to upload the tarball + uploadTar: "curl --header 'Content-Type: application/x-tar' --header 'Content-Encoding: gzip' -X PUT --data-binary @'{{source}}' '{{url}}'" + # Large http get requests are often slower using nodes built in http layer so + # we utilize a subprocess which is responsible for fetching... + get: curl --connect-timeout 30 --speed-limit 500000 -L -o {{dest}} {{url}} + # Used to create clone tarball + compress: tar -czf {{dest}} {{source}} + # All cache urls use tar + gz this is the command used to extract those files + # downloaded by the "get" command. + extract: tar -x -z --strip-components 1 -C {{dest}} -f {{source}} + # Name/prefixed used as part of the base url. + cacheName: clones/{{name}}.tar.gz
new file mode 100755 --- /dev/null +++ b/testing/taskcluster/scripts/builder/build-emulator-x86.sh @@ -0,0 +1,71 @@ +#! /bin/bash -vex + +# Ensure all the scripts in this dir are on the path.... +DIRNAME=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +PATH=$DIRNAME:$PATH + +WORKSPACE=$1 + +### Check that require variables are defined +test -d $WORKSPACE +test $GECKO_HEAD_REPOSITORY # Should be an hg repository url to pull from +test $GECKO_BASE_REPOSITORY # Should be an hg repository url to clone from +test $GECKO_HEAD_REV # Should be an hg revision to pull down +test $MOZHARNESS_REPOSITORY # mozharness repository +test $MOZHARNESS_REV # mozharness revision +test $TARGET + +. setup-ccache.sh + +# First check if the mozharness directory is available. This is intended to be +# used locally in development to test mozharness changes: +# +# $ docker -v your_mozharness:/home/worker/mozharness ... +# +if [ ! -d mozharness ]; then + tc-vcs checkout mozharness $MOZHARNESS_REPOSITORY $MOZHARNESS_REPOSITORY $MOZHARNESS_REV +fi + +# Figure out where the remote manifest is so we can use caches for it. + +if [ -z "$MANIFEST" ]; then + MANIFEST="$WORKSPACE/gecko/b2g/config/$TARGET/sources.xml" +fi + +tc-vcs repo-checkout $WORKSPACE/B2G https://git.mozilla.org/b2g/B2G.git $MANIFEST + +# Ensure symlink has been created to gecko... +rm -f $WORKSPACE/B2G/gecko +ln -s $WORKSPACE/gecko $WORKSPACE/B2G/gecko + +debug_flag="" +if [ 0$B2G_DEBUG -ne 0 ]; then + debug_flag='--debug' +fi + +./mozharness/scripts/b2g_build.py \ + --config b2g/taskcluster-emulator.py \ + "$debug_flag" \ + --disable-mock \ + --work-dir=$WORKSPACE/B2G \ + --log-level=debug \ + --target=$TARGET \ + --b2g-config-dir=$TARGET \ + --checkout-revision=$GECKO_HEAD_REV \ + --base-repo=$GECKO_BASE_REPOSITORY \ + --repo=$GECKO_HEAD_REPOSITORY + +# Move files into artifact locations! +mkdir -p $HOME/artifacts + +ls -lah $WORKSPACE/B2G/out +ls -lah $WORKSPACE/B2G/objdir-gecko/dist/ + +mv $WORKSPACE/B2G/sources.xml $HOME/artifacts/sources.xml +mv $WORKSPACE/B2G/out/target/product/generic_x86/tests/gaia-tests.zip $HOME/artifacts/gaia-tests.zip +mv $WORKSPACE/B2G/out/target/product/generic_x86/tests/b2g-*.zip $HOME/artifacts/b2g-tests.zip +mv $WORKSPACE/B2G/out/emulator.tar.gz $HOME/artifacts/emulator.tar.gz +mv $WORKSPACE/B2G/objdir-gecko/dist/b2g-*.crashreporter-symbols.zip $HOME/artifacts/b2g-crashreporter-symbols.zip + +ccache -s +
--- a/testing/taskcluster/scripts/builder/build-emulator.sh +++ b/testing/taskcluster/scripts/builder/build-emulator.sh @@ -23,17 +23,21 @@ test $TARGET # # $ docker -v your_mozharness:/home/worker/mozharness ... # if [ ! -d mozharness ]; then tc-vcs checkout mozharness $MOZHARNESS_REPOSITORY $MOZHARNESS_REPOSITORY $MOZHARNESS_REV $MOZHARNESS_REF fi # Figure out where the remote manifest is so we can use caches for it. -MANIFEST=$(repository-url.py $GECKO_HEAD_REPOSITORY $GECKO_HEAD_REV b2g/config/$TARGET/sources.xml) + +if [ -z "$MANIFEST" ]; then + MANIFEST="$WORKSPACE/gecko/b2g/config/$TARGET/sources.xml" +fi + tc-vcs repo-checkout $WORKSPACE/B2G https://git.mozilla.org/b2g/B2G.git $MANIFEST # Ensure symlink has been created to gecko... rm -f $WORKSPACE/B2G/gecko ln -s $WORKSPACE/gecko $WORKSPACE/B2G/gecko debug_flag="" if [ 0$B2G_DEBUG -ne 0 ]; then
--- a/testing/taskcluster/tasks/branches/base_job_flags.yml +++ b/testing/taskcluster/tasks/branches/base_job_flags.yml @@ -4,16 +4,17 @@ flags: aliases: mochitests: mochitest builds: - emulator - emulator-jb - emulator-kk + - emulator-x86-kk - emulator-l - linux32_gecko # b2g desktop linux 32 bit - linux64_gecko # b2g desktop linux 64 bit - linux64-mulet # Firefox desktop - b2g gecko linux 64 bit - macosx64_gecko # b2g desktop osx 64 bit - win32_gecko # b2g desktop win 32 bit - flame-kk # b2g flame kitkat - flame-kk-eng # b2g flame eng build
--- a/testing/taskcluster/tasks/branches/try/job_flags.yml +++ b/testing/taskcluster/tasks/branches/try/job_flags.yml @@ -31,16 +31,28 @@ builds: emulator-kk: platfoms: - b2g types: opt: task: tasks/builds/b2g_emulator_kk_opt.yml debug: task: tasks/builds/b2g_emulator_kk_debug.yml + emulator-x86-l: + platfoms: + - b2g + types: + opt: + task: tasks/builds/b2g_emulator_x86_l_opt.yml + emulator-x86-kk: + platfoms: + - b2g + types: + opt: + task: tasks/builds/b2g_emulator_x86_kk_opt.yml emulator-jb: platfoms: - b2g types: opt: task: tasks/builds/b2g_emulator_jb_opt.yml debug: task: tasks/builds/b2g_emulator_jb_debug.yml @@ -129,38 +141,48 @@ tests: tasks/builds/b2g_emulator_ics_opt.yml: task: tasks/tests/b2g_emulator_marionette_webapi.yml mochitest: allowed_build_tasks: tasks/builds/b2g_emulator_kk_debug.yml: task: tasks/tests/b2g_emulator_mochitest.yml tasks/builds/b2g_emulator_kk_opt.yml: task: tasks/tests/b2g_emulator_mochitest.yml + tasks/builds/b2g_emulator_x86_l_opt.yml: + task: tasks/tests/b2g_emulator_mochitest.yml + tasks/builds/b2g_emulator_x86_kk_opt.yml: + task: tasks/tests/b2g_emulator_mochitest.yml tasks/builds/b2g_emulator_ics_opt.yml: task: tasks/tests/b2g_emulator_mochitest.yml tasks/builds/b2g_emulator_ics_debug.yml: task: tasks/tests/b2g_emulator_mochitest.yml tasks/builds/mulet_linux.yml: task: tasks/tests/mulet_mochitests.yml mochitest-oop: allowed_build_tasks: tasks/builds/b2g_desktop_opt.yml: task: tasks/tests/b2g_mochitest_oop.yml reftest: allowed_build_tasks: tasks/builds/mulet_linux.yml: task: tasks/tests/mulet_reftests.yml tasks/builds/b2g_emulator_kk_opt.yml: task: tasks/tests/b2g_emulator_reftest.yml + tasks/builds/b2g_emulator_x86_l_opt.yml: + task: tasks/tests/b2g_emulator_reftest.yml + tasks/builds/b2g_emulator_x86_kk_opt.yml: + task: tasks/tests/b2g_emulator_reftest.yml tasks/builds/b2g_emulator_ics_opt.yml: task: tasks/tests/b2g_emulator_reftest.yml reftest-sanity-oop: allowed_build_tasks: tasks/builds/b2g_desktop_opt.yml: task: tasks/tests/b2g_reftests_sanity_oop.yml xpcshell: allowed_build_tasks: + tasks/builds/b2g_emulator_x86_kk_opt.yml: + task: tasks/tests/b2g_emulator_xpcshell_chunked.yml tasks/builds/b2g_emulator_kk_opt.yml: task: tasks/tests/b2g_emulator_xpcshell_chunked.yml tasks/builds/b2g_emulator_ics_opt.yml: task: tasks/tests/b2g_emulator_xpcshell_chunked.yml tasks/builds/b2g_emulator_ics_debug.yml: task: tasks/tests/b2g_emulator_xpcshell_chunked.yml
new file mode 100644 --- /dev/null +++ b/testing/taskcluster/tasks/builds/b2g_emulator_x86_base.yml @@ -0,0 +1,34 @@ +$inherits: + from: 'tasks/build.yml' +task: + metadata: + description: | + Android emulators + b2g environment used in full stack testing. + payload: + env: + TARGET: 'emulator' + B2G_DEBUG: 0 + + # Emulators can take a very long time to build! + maxRunTime: 14400 + + command: + - /bin/bash + - -c + - > + checkout-gecko workspace && + cd ./workspace/gecko/testing/taskcluster/scripts/builder && + buildbot_step 'Build' ./build-emulator-x86.sh $HOME/workspace + + extra: + treeherder: + groupSymbol: x86 + # Rather then enforcing particular conventions we require that all build + # tasks provide the "build" extra field to specify where the build and tests + # files are located. + locations: + build: 'public/build/emulator.tar.gz' + tests: 'public/build/b2g-tests.zip' + symbols: 'public/build/b2g-crashreporter-symbols.zip' + sources: 'public/build/sources.xml' +
new file mode 100644 --- /dev/null +++ b/testing/taskcluster/tasks/builds/b2g_emulator_x86_kk_opt.yml @@ -0,0 +1,22 @@ +$inherits: + from: 'tasks/builds/b2g_emulator_x86_base.yml' +task: + workerType: emualtor-x86-kk + scopes: + - 'docker-worker:cache:workspace-emulator-kk-x86-opt' + metadata: + name: '[TC] B2G KK X86 Emulator (Opt)' + + extra: + treeherderEnv: + - staging + treeherder: + # Disable "TC" prefix... + machine: + platform: b2g-emu-kk + + payload: + cache: + workspace-emulator-kk-x86-opt: /home/worker/workspace + env: + TARGET: 'emulator-x86-kk'
new file mode 100644 --- /dev/null +++ b/testing/taskcluster/tasks/builds/b2g_emulator_x86_l_opt.yml @@ -0,0 +1,34 @@ +$inherits: + from: 'tasks/builds/b2g_emulator_base.yml' +task: + workerType: emulator-l + scopes: + - 'docker-worker:cache:workspace-emulator-l-x86-opt' + metadata: + name: '[TC] B2G X86 L Emulator (Opt)' + + extra: + treeherderEnv: + - staging + treeherder: + # Disable "TC" prefix... + groupSymbol: "X86" + machine: + platform: b2g-emu-l + + payload: + command: + - /bin/bash + - -c + - > + checkout-gecko workspace && + cd ./workspace/gecko/testing/taskcluster/scripts/builder && + buildbot_step 'Build' ./build-emulator-x86.sh $HOME/workspace + + cache: + workspace-emulator-l-x86-opt: /home/worker/workspace + env: + TARGET: 'emulator-x86-l' + + +
--- a/testing/taskcluster/tasks/builds/b2g_flame_kk_eng.yml +++ b/testing/taskcluster/tasks/builds/b2g_flame_kk_eng.yml @@ -9,27 +9,25 @@ task: payload: cache: build-flame-kk-eng: /home/worker/workspace env: TARGET: 'flame-kk' DEBUG: 0 VARIANT: eng + GAIA_OPTIMIZE: '1' + B2G_SYSTEM_APPS: '1' + B2G_UPDATER: '1' command: - > checkout-gecko workspace && cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder && buildbot_step 'Build' ./build-phone.sh $HOME/workspace extra: treeherder: symbol: Be groupSymbol: Flame-KK groupName: Flame KitKat Device Image machine: platform: b2g-device-image locations: img: 'private/build/flame-kk.zip' - - GAIA_OPTIMIZE: '1' - B2G_SYSTEM_APPS: '1' - B2G_UPDATER: '1' -
--- a/testing/taskcluster/tasks/job_flags.yml +++ b/testing/taskcluster/tasks/job_flags.yml @@ -3,16 +3,17 @@ # List of all possible flags for each category of tests used in the case where # "all" is specified. flags: builds: - emulator - emulator-jb - emulator-kk + - emulator-x86-kk - linux32_gecko # b2g desktop linux 32 bit - linux64_gecko # b2g desktop linux 64 bit - linux64-mulet # Firefox desktop - b2g gecko linux 64 bit - macosx64_gecko # b2g desktop osx 64 bit - win32_gecko # b2g desktop win 32 bit tests: - cppunit
--- a/testing/taskcluster/tasks/tests/b2g_build_unit.yml +++ b/testing/taskcluster/tasks/tests/b2g_build_unit.yml @@ -1,11 +1,12 @@ --- $inherits: from: 'tasks/test.yml' +reruns: 2 task: metadata: name: '[TC] - Gaia Build Unit Test' description: Gaia Build Unit Test payload: command: - entrypoint
--- a/testing/taskcluster/tasks/tests/b2g_gaia_js_integration_tests.yml +++ b/testing/taskcluster/tasks/tests/b2g_gaia_js_integration_tests.yml @@ -1,11 +1,12 @@ --- $inherits: from: 'tasks/test.yml' +reruns: 2 task: metadata: name: '[TC] - Gaia JS Integration Test' description: Gaia JS Integration Test run {{chunk}} payload: command: - entrypoint # entrypoint ensures we are running in xvfb
--- a/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_accessibility.yml +++ b/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_accessibility.yml @@ -1,11 +1,12 @@ --- $inherits: from: 'tasks/test.yml' +reruns: 2 task: metadata: name: '[TC] - Gaia Python Accessibility Integration Tests' description: Gaia Python Accessibility Integration Tests run {{chunk}} payload: command: - entrypoint # entrypoint ensures we are running in xvfb
--- a/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_functional.yml +++ b/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_functional.yml @@ -1,11 +1,12 @@ --- $inherits: from: 'tasks/test.yml' +reruns: 2 task: metadata: name: '[TC] - Gaia Python Functional Integration Tests' description: Gaia Python Functional Integration Tests run {{chunk}} payload: command: - entrypoint # entrypoint ensures we are running in xvfb
--- a/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_unit.yml +++ b/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_unit.yml @@ -1,11 +1,12 @@ --- $inherits: from: 'tasks/test.yml' +reruns: 2 task: metadata: name: '[TC] - Gaia Python Integration Unit Tests' description: Gaia Python Integration Unit Tests run {{chunk}} payload: command: - entrypoint # entrypoint ensures we are running in xvfb
--- a/testing/taskcluster/tasks/tests/b2g_gaia_unit.yml +++ b/testing/taskcluster/tasks/tests/b2g_gaia_unit.yml @@ -1,11 +1,12 @@ --- $inherits: from: 'tasks/test.yml' +reruns: 2 task: metadata: name: '[TC] Gaia Unit Test' description: Gaia Unit Test payload: command: - entrypoint
--- a/testing/taskcluster/tasks/tests/b2g_gaia_unit_oop.yml +++ b/testing/taskcluster/tasks/tests/b2g_gaia_unit_oop.yml @@ -1,11 +1,12 @@ --- $inherits: from: 'tasks/test.yml' +reruns: 2 task: metadata: name: '[TC] Gaia Unit Test OOP' description: Gaia Unit Test OOP payload: command: - entrypoint
--- a/testing/taskcluster/tasks/tests/b2g_gip_oop.yml +++ b/testing/taskcluster/tasks/tests/b2g_gip_oop.yml @@ -1,11 +1,12 @@ --- $inherits: from: 'tasks/test.yml' +reruns: 2 task: metadata: name: '[TC] Gaia Python Integration Tests OOP' description: Gaia Python Functional Integration Tests OOP test run payload: command: - entrypoint # entrypoint ensures we are running in xvfb
--- a/testing/taskcluster/tasks/tests/mulet_gaia_js_integration_tests.yml +++ b/testing/taskcluster/tasks/tests/mulet_gaia_js_integration_tests.yml @@ -1,11 +1,12 @@ --- $inherits: from: 'tasks/test.yml' +reruns: 2 task: metadata: name: '[TC] Mulet Gaia JS Integration Test' description: Mulet Gaia JS Integration Test run {{chunk}} payload: command: - entrypoint # entrypoint ensures we are running in xvfb
--- a/toolkit/components/passwordmgr/content/passwordManager.js +++ b/toolkit/components/passwordmgr/content/passwordManager.js @@ -1,10 +1,8 @@ -// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - /* 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/. */ /*** =================== SAVED SIGNONS CODE =================== ***/ var kSignonBundle; var showingPasswords = false; @@ -14,16 +12,20 @@ var dateAndTimeFormatter = new Intl.Date { day: "numeric", month: "short", year: "numeric", hour: "numeric", minute: "numeric" }); function SignonsStartup() { kSignonBundle = document.getElementById("signonBundle"); document.getElementById("togglePasswords").label = kSignonBundle.getString("showPasswords"); document.getElementById("togglePasswords").accessKey = kSignonBundle.getString("showPasswordsAccessKey"); document.getElementById("signonsIntro").textContent = kSignonBundle.getString("loginsSpielAll"); + + let treecols = document.getElementsByTagName("treecols")[0]; + treecols.addEventListener("click", HandleTreeColumnClick.bind(null, SignonColumnSort)); + LoadSignons(); // filter the table if requested by caller if (window.arguments && window.arguments[0] && window.arguments[0].filterString) setFilter(window.arguments[0].filterString);
--- a/toolkit/components/passwordmgr/content/passwordManager.xul +++ b/toolkit/components/passwordmgr/content/passwordManager.xul @@ -58,41 +58,41 @@ <tree id="signonsTree" flex="1" width="750" style="height: 20em;" onkeypress="HandleSignonKeyPress(event)" onselect="SignonSelected();" context="signonsTreeContextMenu"> <treecols> <treecol id="siteCol" label="&treehead.site.label;" flex="40" - onclick="SignonColumnSort('hostname');" persist="width" + data-field-name="hostname" persist="width" ignoreincolumnpicker="true" sortDirection="ascending"/> <splitter class="tree-splitter"/> <treecol id="userCol" label="&treehead.username.label;" flex="25" ignoreincolumnpicker="true" - onclick="SignonColumnSort('username');" persist="width"/> + data-field-name="username" persist="width"/> <splitter class="tree-splitter"/> <treecol id="passwordCol" label="&treehead.password.label;" flex="15" ignoreincolumnpicker="true" - onclick="SignonColumnSort('password');" persist="width" + data-field-name="password" persist="width" hidden="true"/> <splitter class="tree-splitter"/> <treecol id="timeCreatedCol" label="&treehead.timeCreated.label;" flex="10" - onclick="SignonColumnSort('timeCreated');" persist="width hidden" + data-field-name="timeCreated" persist="width hidden" hidden="true"/> <splitter class="tree-splitter"/> <treecol id="timeLastUsedCol" label="&treehead.timeLastUsed.label;" flex="20" - onclick="SignonColumnSort('timeLastUsed');" persist="width hidden"/> + data-field-name="timeLastUsed" persist="width hidden"/> <splitter class="tree-splitter"/> <treecol id="timePasswordChangedCol" label="&treehead.timePasswordChanged.label;" flex="10" - onclick="SignonColumnSort('timePasswordChanged');" persist="width hidden"/> + data-field-name="timePasswordChanged" persist="width hidden"/> <splitter class="tree-splitter"/> <treecol id="timesUsedCol" label="&treehead.timesUsed.label;" flex="1" - onclick="SignonColumnSort('timesUsed');" persist="width hidden" + data-field-name="timesUsed" persist="width hidden" hidden="true"/> <splitter class="tree-splitter"/> </treecols> <treechildren/> </tree> <separator class="thin"/> <hbox id="SignonViewerButtons"> <button id="removeSignon" disabled="true" icon="remove"
--- a/toolkit/components/passwordmgr/content/passwordManagerCommon.js +++ b/toolkit/components/passwordmgr/content/passwordManagerCommon.js @@ -1,10 +1,8 @@ -// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - /* 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/. */ /*** =================== INITIALISATION CODE =================== ***/ var kObserverService; @@ -155,16 +153,29 @@ function GetTreeSelections(tree) { selections[selections.length] = k; } } } } return selections; } +function HandleTreeColumnClick(sortFunction, event) { + if (event.target.nodeName != "treecol" || event.button != 0) { + return; + } + + let sortField = event.target.getAttribute("data-field-name"); + if (!sortField) { + return; + } + + sortFunction(sortField); +} + function SortTree(tree, view, table, column, lastSortColumn, lastSortAscending, updateSelection) { // remember which item was selected so we can restore it after the sort var selections = GetTreeSelections(tree); var selectedNumber = selections.length ? table[selections[0]].number : -1; // determine if sort is to be ascending or descending var ascending = (column == lastSortColumn) ? !lastSortAscending : true;
--- a/toolkit/components/passwordmgr/content/passwordManagerExceptions.js +++ b/toolkit/components/passwordmgr/content/passwordManagerExceptions.js @@ -1,18 +1,19 @@ -// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - /* 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/. */ /*** =================== REJECTED SIGNONS CODE =================== ***/ function RejectsStartup() { LoadRejects(); + + let treecols = document.getElementsByTagName("treecols")[0]; + treecols.addEventListener("click", HandleTreeColumnClick.bind(null, RejectColumnSort)); } var rejectsTreeView = { rowCount : 0, setTree : function(tree){}, getImageSrc : function(row,column) {}, getProgressMode : function(row,column) {}, getCellValue : function(row,column) {},
--- a/toolkit/components/passwordmgr/content/passwordManagerExceptions.xul +++ b/toolkit/components/passwordmgr/content/passwordManagerExceptions.xul @@ -25,17 +25,17 @@ <vbox id="rejectedsites" flex="1"> <description control="rejectsTree">&loginsSpielExceptions.label;</description> <separator class="thin"/> <tree id="rejectsTree" flex="1" style="height: 10em;" hidecolumnpicker="true" onkeypress="HandleRejectKeyPress(event)" onselect="RejectSelected();"> <treecols> <treecol id="rejectCol" label="&treehead.site.label;" flex="5" - onclick="RejectColumnSort('host');" sortDirection="ascending"/> + data-field-name="host" sortDirection="ascending"/> </treecols> <treechildren/> </tree> <separator class="thin"/> <hbox> <button id="removeReject" disabled="true" icon="remove" accesskey="&remove.accesskey;" label="&remove.label;" oncommand="DeleteReject();"/>
--- a/toolkit/components/places/PlacesUtils.jsm +++ b/toolkit/components/places/PlacesUtils.jsm @@ -927,18 +927,24 @@ this.PlacesUtils = { stmt.finalize(); } }, /** * Get the URI (and any associated POST data) for a given keyword. * @param aKeyword string keyword * @returns an array containing a string URL and a string of POST data + * + * @deprecated */ getURLAndPostDataForKeyword(aKeyword) { + Deprecated.warning("getURLAndPostDataForKeyword() is deprecated, please " + + "use PlacesUtils.keywords.fetch() instead", + "https://bugzilla.mozilla.org/show_bug.cgi?id=1100294"); + let stmt = PlacesUtils.history.DBConnection.createStatement( `SELECT h.url, k.post_data FROM moz_keywords k JOIN moz_places h ON h.id = k.place_id WHERE k.keyword = :keyword`); stmt.params.keyword = aKeyword; try { if (!stmt.executeStep()) @@ -2014,17 +2020,17 @@ let Keywords = { * * @param keyword * The keyword to fetch. * @return {Promise} * @resolves to an object in the form: { keyword, url, postData }, * or null if a keyword was not found. */ fetch(keyword) { - if (!keyword || typeof(keyword) != "string") + if (typeof(keyword) != "string") throw new Error("Invalid keyword"); keyword = keyword.trim().toLowerCase(); return gKeywordsCachePromise.then(cache => cache.get(keyword) || null); }, /** * Adds a new keyword and postData for the given URL. *
--- a/toolkit/components/places/UnifiedComplete.js +++ b/toolkit/components/places/UnifiedComplete.js @@ -41,35 +41,34 @@ const PREF_SUGGEST_HISTORY_ONLYTYPED = [ const MATCH_ANYWHERE = Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE; const MATCH_BOUNDARY_ANYWHERE = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY_ANYWHERE; const MATCH_BOUNDARY = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY; const MATCH_BEGINNING = Ci.mozIPlacesAutoComplete.MATCH_BEGINNING; const MATCH_BEGINNING_CASE_SENSITIVE = Ci.mozIPlacesAutoComplete.MATCH_BEGINNING_CASE_SENSITIVE; // AutoComplete query type constants. // Describes the various types of queries that we can process rows for. -const QUERYTYPE_KEYWORD = 0; -const QUERYTYPE_FILTERED = 1; -const QUERYTYPE_AUTOFILL_HOST = 2; -const QUERYTYPE_AUTOFILL_URL = 3; -const QUERYTYPE_AUTOFILL_PREDICTURL = 4; +const QUERYTYPE_FILTERED = 0; +const QUERYTYPE_AUTOFILL_HOST = 1; +const QUERYTYPE_AUTOFILL_URL = 2; +const QUERYTYPE_AUTOFILL_PREDICTURL = 3; // This separator is used as an RTL-friendly way to split the title and tags. // It can also be used by an nsIAutoCompleteResult consumer to re-split the // "comment" back into the title and the tag. const TITLE_TAGS_SEPARATOR = " \u2013 "; // This separator identifies the search engine name in the title. const TITLE_SEARCH_ENGINE_SEPARATOR = " \u00B7\u2013\u00B7 "; // Telemetry probes. const TELEMETRY_1ST_RESULT = "PLACES_AUTOCOMPLETE_1ST_RESULT_TIME_MS"; const TELEMETRY_6_FIRST_RESULTS = "PLACES_AUTOCOMPLETE_6_FIRST_RESULTS_TIME_MS"; // The default frecency value used when inserting matches with unknown frecency. -const FRECENCY_SEARCHENGINES_DEFAULT = 1000; +const FRECENCY_DEFAULT = 1000; // Sqlite result row index constants. const QUERYINDEX_QUERYTYPE = 0; const QUERYINDEX_URL = 1; const QUERYINDEX_TITLE = 2; const QUERYINDEX_ICONURL = 3; const QUERYINDEX_BOOKMARKED = 4; const QUERYINDEX_BOOKMARKTITLE = 5; @@ -144,33 +143,16 @@ const SQL_ADAPTIVE_QUERY = LEFT JOIN moz_openpages_temp t ON t.url = h.url WHERE AUTOCOMPLETE_MATCH(NULL, h.url, IFNULL(btitle, h.title), tags, h.visit_count, h.typed, bookmarked, t.open_count, :matchBehavior, :searchBehavior) ORDER BY rank DESC, h.frecency DESC`; -const SQL_KEYWORD_QUERY = - `/* do not warn (bug 487787) */ - SELECT :query_type, - REPLACE(h.url, '%s', :query_string) AS search_url, h.title, - IFNULL(f.url, (SELECT f.url - FROM moz_places - JOIN moz_favicons f ON f.id = favicon_id - WHERE rev_host = h.rev_host - ORDER BY frecency DESC - LIMIT 1) - ), - 1, NULL, NULL, h.visit_count, h.typed, h.id, t.open_count, h.frecency - FROM moz_keywords k - JOIN moz_places h ON k.place_id = h.id - LEFT JOIN moz_favicons f ON f.id = h.favicon_id - LEFT JOIN moz_openpages_temp t ON t.url = search_url - WHERE k.keyword = LOWER(:keyword)`; function hostQuery(conditions = "") { let query = `/* do not warn (bug NA): not worth to index on (typed, frecency) */ SELECT :query_type, host || '/', IFNULL(prefix, '') || host || '/', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, frecency FROM moz_hosts WHERE host BETWEEN :searchString AND :searchString || X'FFFF' @@ -726,24 +708,26 @@ Search.prototype = { TelemetryStopwatch.start(TELEMETRY_1ST_RESULT); if (this._searchString) TelemetryStopwatch.start(TELEMETRY_6_FIRST_RESULTS); // Since we call the synchronous parseSubmissionURL function later, we must // wait for the initialization of PlacesSearchAutocompleteProvider first. yield PlacesSearchAutocompleteProvider.ensureInitialized(); + if (!this.pending) + return; // For any given search, we run many queries/heuristics: // 1) by alias (as defined in SearchService) // 2) inline completion from search engine resultDomains // 3) inline completion for hosts (this._hostQuery) or urls (this._urlQuery) // 4) directly typed in url (ie, can be navigated to as-is) // 5) submission for the current search engine - // 6) keywords (this._keywordQuery) + // 6) Places keywords // 7) adaptive learning (this._adaptiveQuery) // 8) open pages not supported by history (this._switchToTabQuery) // 9) query based on match behavior // // (6) only gets ran if we get any filtered tokens, since if there are no // tokens, there is nothing to match. This is the *first* query we check if // we want to run, but it gets queued to be run later. // @@ -767,25 +751,23 @@ Search.prototype = { // When actions are enabled, we run a series of heuristics to determine what // the first result should be - which is always a special result. // |hasFirstResult| is used to keep track of whether we've obtained such a // result yet, so we can skip further heuristics and not add any additional // special results. let hasFirstResult = false; - if (this._searchTokens.length > 0 && - PlacesUtils.bookmarks.getURIForKeyword(this._searchTokens[0])) { - // This may be a keyword of a bookmark. - queries.unshift(this._keywordQuery); - hasFirstResult = true; + if (this._searchTokens.length > 0) { + // This may be a Places keyword. + hasFirstResult = yield this._matchPlacesKeyword(); } - if (this._enableActions && !hasFirstResult) { - // If it's not a bookmarked keyword, then it may be a search engine + if (this.pending && this._enableActions && !hasFirstResult) { + // If it's not a Places keyword, then it may be a search engine // with an alias - which works like a keyword. hasFirstResult = yield this._matchSearchEngineAlias(); } let shouldAutofill = this._shouldAutofill; if (this.pending && !hasFirstResult && shouldAutofill) { // It may also look like a URL we know from the database. // Here we can only try to predict whether the URL autofill query is @@ -870,16 +852,45 @@ Search.prototype = { yield conn.executeCached(query, params, row => { gotResult = true; this._onResultRow(row); }); return gotResult; }, + _matchPlacesKeyword: function* () { + // The first word could be a keyword, so that's what we'll search. + let keyword = this._searchTokens[0]; + let entry = yield PlacesUtils.keywords.fetch(this._searchTokens[0]); + if (!entry) + return false; + + // Build the url. + let searchString = this._trimmedOriginalSearchString; + let queryString = ""; + let queryIndex = searchString.indexOf(" "); + if (queryIndex != -1) { + queryString = searchString.substring(queryIndex + 1); + } + // We need to escape the parameters as if they were the query in a URL + queryString = encodeURIComponent(queryString).replace(/%20/g, "+"); + let escapedURL = entry.url.href.replace("%s", queryString); + + let style = (this._enableActions ? "action " : "") + "keyword"; + let actionURL = makeActionURL("keyword", { url: escapedURL, + input: this._originalSearchString }); + let value = this._enableActions ? actionURL : escapedURL; + // The title will end up being "host: queryString" + let comment = entry.url.host; + + this._addMatch({ value, comment, style, frecency: FRECENCY_DEFAULT }); + return true; + }, + _matchSearchEngineUrl: function* () { if (!Prefs.autofillSearchEngines) return false; let match = yield PlacesSearchAutocompleteProvider.findMatchByToken( this._searchString); if (!match) return false; @@ -917,17 +928,17 @@ Search.prototype = { this._result.setDefaultIndex(0); this._addMatch({ value: value, comment: match.engineName, icon: match.iconUrl, style: "priority-search", finalCompleteValue: match.url, - frecency: FRECENCY_SEARCHENGINES_DEFAULT + frecency: FRECENCY_DEFAULT }); return true; }, _matchSearchEngineAlias: function* () { if (this._searchTokens.length < 2) return false; @@ -964,17 +975,17 @@ Search.prototype = { } let value = makeActionURL("searchengine", actionURLParams); this._addMatch({ value: value, comment: match.engineName, icon: match.iconUrl, style: "action searchengine", - frecency: FRECENCY_SEARCHENGINES_DEFAULT, + frecency: FRECENCY_DEFAULT, }); }, // These are separated out so we can run them in two distinct cases: // (1) We didn't match on anything that we know about // (2) Our predictive query for URL autofill thought we may get a result, // but we didn't. _matchHeuristicFallback: function* () { @@ -1047,17 +1058,16 @@ Search.prototype = { case QUERYTYPE_AUTOFILL_PREDICTURL: match = this._processHostRow(row); break; case QUERYTYPE_AUTOFILL_URL: this._result.setDefaultIndex(0); match = this._processUrlRow(row); break; case QUERYTYPE_FILTERED: - case QUERYTYPE_KEYWORD: match = this._processRow(row); break; } this._addMatch(match); // If the search has been canceled by the user or by _addMatch reaching the // maximum number of results, we can stop the underlying Sqlite query. if (!this.pending) throw StopIteration; @@ -1231,27 +1241,16 @@ Search.prototype = { if (this._enableActions && openPageCount > 0 && this.hasBehavior("openpage")) { url = makeActionURL("switchtab", {url: escapedURL}); action = "switchtab"; } // Always prefer the bookmark title unless it is empty let title = bookmarkTitle || historyTitle; - if (queryType == QUERYTYPE_KEYWORD) { - match.style = "keyword"; - if (this._enableActions) { - url = makeActionURL("keyword", { - url: escapedURL, - input: this._originalSearchString, - }); - action = "keyword"; - } - } - // We will always prefer to show tags if we have them. let showTags = !!tags; // However, we'll act as if a page is not bookmarked if the user wants // only history and not bookmarks and there are no tags. if (this.hasBehavior("history") && !this.hasBehavior("bookmark") && !showTags) { showTags = false; @@ -1348,47 +1347,16 @@ Search.prototype = { // Limit the query to the the maximum number of desired results. // This way we can avoid doing more work than needed. maxResults: Prefs.maxRichResults } ]; }, /** - * Obtains the query to search for keywords. - * - * @return an array consisting of the correctly optimized query to search the - * database with and an object containing the params to bound. - */ - get _keywordQuery() { - // The keyword is the first word in the search string, with the parameters - // following it. - let searchString = this._trimmedOriginalSearchString; - let queryString = ""; - let queryIndex = searchString.indexOf(" "); - if (queryIndex != -1) { - queryString = searchString.substring(queryIndex + 1); - } - // We need to escape the parameters as if they were the query in a URL - queryString = encodeURIComponent(queryString).replace(/%20/g, "+"); - - // The first word could be a keyword, so that's what we'll search. - let keyword = this._searchTokens[0]; - - return [ - SQL_KEYWORD_QUERY, - { - keyword: keyword, - query_string: queryString, - query_type: QUERYTYPE_KEYWORD - } - ]; - }, - - /** * Obtains the query to search for switch-to-tab entries. * * @return an array consisting of the correctly optimized query to search the * database with and an object containing the params to bound. */ get _switchToTabQuery() [ SQL_SWITCHTAB_QUERY, {
--- a/toolkit/components/places/nsPlacesAutoComplete.js +++ b/toolkit/components/places/nsPlacesAutoComplete.js @@ -7,16 +7,18 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); //////////////////////////////////////////////////////////////////////////////// //// Constants const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; @@ -1163,26 +1165,18 @@ nsPlacesAutoComplete.prototype = { aRow.getResultByIndex(kQueryIndexBookmarkTitle) : null; let entryTags = aRow.getResultByIndex(kQueryIndexTags) || ""; // Always prefer the bookmark title unless it is empty let title = entryBookmarkTitle || entryTitle; let style; if (aRow.getResultByIndex(kQueryIndexQueryType) == kQueryTypeKeyword) { - // If we do not have a title, then we must have a keyword, so let the UI - // know it is a keyword. Otherwise, we found an exact page match, so just - // show the page like a regular result. Because the page title is likely - // going to be more specific than the bookmark title (keyword title). - if (!entryTitle) { - style = "keyword"; - } - else { - title = entryTitle; - } + style = "keyword"; + title = NetUtil.newURI(escapedEntryURL).host; } // We will always prefer to show tags if we have them. let showTags = !!entryTags; // However, we'll act as if a page is not bookmarked if the user wants // only history and not bookmarks and there are no tags. if (this._hasBehavior("history") && !this._hasBehavior("bookmark") && @@ -1427,16 +1421,18 @@ urlInlineComplete.prototype = { startSearch: function UIC_startSearch(aSearchString, aSearchParam, aPreviousResult, aListener) { // Stop the search in case the controller has not taken care of it. if (this._pendingQuery) { this.stopSearch(); } + let pendingSearch = this._pendingSearch = {}; + // We want to store the original string with no leading or trailing // whitespace for case sensitive searches. this._originalSearchString = aSearchString; this._currentSearchString = fixupSearchText(this._originalSearchString.toLowerCase()); // The protocol and the host are lowercased by nsIURI, so it's fine to // lowercase the typed prefix to add it back to the results later. this._strippedPrefix = this._originalSearchString.slice( @@ -1445,84 +1441,92 @@ urlInlineComplete.prototype = { this._result = Cc["@mozilla.org/autocomplete/simple-result;1"]. createInstance(Ci.nsIAutoCompleteSimpleResult); this._result.setSearchString(aSearchString); this._result.setTypeAheadResult(true); this._listener = aListener; - // Don't autoFill if the search term is recognized as a keyword, otherwise - // it will override default keywords behavior. Note that keywords are - // hashed on first use, so while the first query may delay a little bit, - // next ones will just hit the memory hash. - if (this._currentSearchString.length == 0 || !this._db || - PlacesUtils.bookmarks.getURIForKeyword(this._currentSearchString)) { - this._finishSearch(); - return; - } + Task.spawn(function* () { + // Don't autoFill if the search term is recognized as a keyword, otherwise + // it will override default keywords behavior. Note that keywords are + // hashed on first use, so while the first query may delay a little bit, + // next ones will just hit the memory hash. + let dontAutoFill = this._currentSearchString.length == 0 || !this._db || + (yield PlacesUtils.keywords.fetch(this._currentSearchString)); + if (this._pendingSearch != pendingSearch) + return; + if (dontAutoFill) { + this._finishSearch(); + return; + } - // Don't try to autofill if the search term includes any whitespace. - // This may confuse completeDefaultIndex cause the AUTOCOMPLETE_MATCH - // tokenizer ends up trimming the search string and returning a value - // that doesn't match it, or is even shorter. - if (/\s/.test(this._currentSearchString)) { - this._finishSearch(); - return; - } + // Don't try to autofill if the search term includes any whitespace. + // This may confuse completeDefaultIndex cause the AUTOCOMPLETE_MATCH + // tokenizer ends up trimming the search string and returning a value + // that doesn't match it, or is even shorter. + if (/\s/.test(this._currentSearchString)) { + this._finishSearch(); + return; + } - // Hosts have no "/" in them. - let lastSlashIndex = this._currentSearchString.lastIndexOf("/"); + // Hosts have no "/" in them. + let lastSlashIndex = this._currentSearchString.lastIndexOf("/"); - // Search only URLs if there's a slash in the search string... - if (lastSlashIndex != -1) { - // ...but not if it's exactly at the end of the search string. - if (lastSlashIndex < this._currentSearchString.length - 1) - this._queryURL(); - else - this._finishSearch(); - return; - } + // Search only URLs if there's a slash in the search string... + if (lastSlashIndex != -1) { + // ...but not if it's exactly at the end of the search string. + if (lastSlashIndex < this._currentSearchString.length - 1) + this._queryURL(); + else + this._finishSearch(); + return; + } - // Do a synchronous search on the table of hosts. - let query = this._hostQuery; - query.params.search_string = this._currentSearchString.toLowerCase(); - // This is just to measure the delay to reach the UI, not the query time. - TelemetryStopwatch.start(DOMAIN_QUERY_TELEMETRY); - let ac = this; - let wrapper = new AutoCompleteStatementCallbackWrapper(this, { - handleResult: function (aResultSet) { - let row = aResultSet.getNextRow(); - let trimmedHost = row.getResultByIndex(0); - let untrimmedHost = row.getResultByIndex(1); - // If the untrimmed value doesn't preserve the user's input just - // ignore it and complete to the found host. - if (untrimmedHost && - !untrimmedHost.toLowerCase().contains(ac._originalSearchString.toLowerCase())) { - untrimmedHost = null; - } + // Do a synchronous search on the table of hosts. + let query = this._hostQuery; + query.params.search_string = this._currentSearchString.toLowerCase(); + // This is just to measure the delay to reach the UI, not the query time. + TelemetryStopwatch.start(DOMAIN_QUERY_TELEMETRY); + let wrapper = new AutoCompleteStatementCallbackWrapper(this, { + handleResult: aResultSet => { + if (this._pendingSearch != pendingSearch) + return; + let row = aResultSet.getNextRow(); + let trimmedHost = row.getResultByIndex(0); + let untrimmedHost = row.getResultByIndex(1); + // If the untrimmed value doesn't preserve the user's input just + // ignore it and complete to the found host. + if (untrimmedHost && + !untrimmedHost.toLowerCase().contains(this._originalSearchString.toLowerCase())) { + untrimmedHost = null; + } - ac._result.appendMatch(ac._strippedPrefix + trimmedHost, "", "", "", untrimmedHost); + this._result.appendMatch(this._strippedPrefix + trimmedHost, "", "", "", untrimmedHost); - // handleCompletion() will cause the result listener to be called, and - // will display the result in the UI. - }, + // handleCompletion() will cause the result listener to be called, and + // will display the result in the UI. + }, - handleError: function (aError) { - Components.utils.reportError( - "URL Inline Complete: An async statement encountered an " + - "error: " + aError.result + ", '" + aError.message + "'"); - }, + handleError: aError => { + Components.utils.reportError( + "URL Inline Complete: An async statement encountered an " + + "error: " + aError.result + ", '" + aError.message + "'"); + }, - handleCompletion: function (aReason) { - TelemetryStopwatch.finish(DOMAIN_QUERY_TELEMETRY); - ac._finishSearch(); - } - }, this._db); - this._pendingQuery = wrapper.executeAsync([query]); + handleCompletion: aReason => { + if (this._pendingSearch != pendingSearch) + return; + TelemetryStopwatch.finish(DOMAIN_QUERY_TELEMETRY); + this._finishSearch(); + } + }, this._db); + this._pendingQuery = wrapper.executeAsync([query]); + }.bind(this)); }, /** * Execute an asynchronous search through places, and complete * up to the next URL separator. */ _queryURL: function UIC__queryURL() { @@ -1541,70 +1545,70 @@ urlInlineComplete.prototype = { let params = query.params; params.matchBehavior = MATCH_BEGINNING_CASE_SENSITIVE; params.searchBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY | Ci.mozIPlacesAutoComplete.BEHAVIOR_TYPED | Ci.mozIPlacesAutoComplete.BEHAVIOR_URL; params.searchString = this._currentSearchString; // Execute the query. - let ac = this; let wrapper = new AutoCompleteStatementCallbackWrapper(this, { - handleResult: function(aResultSet) { + handleResult: aResultSet => { let row = aResultSet.getNextRow(); let value = row.getResultByIndex(0); let url = fixupSearchText(value); let prefix = value.slice(0, value.length - stripPrefix(value).length); // We must complete the URL up to the next separator (which is /, ? or #). - let separatorIndex = url.slice(ac._currentSearchString.length) + let separatorIndex = url.slice(this._currentSearchString.length) .search(/[\/\?\#]/); if (separatorIndex != -1) { - separatorIndex += ac._currentSearchString.length; + separatorIndex += this._currentSearchString.length; if (url[separatorIndex] == "/") { separatorIndex++; // Include the "/" separator } url = url.slice(0, separatorIndex); } // Add the result. // If the untrimmed value doesn't preserve the user's input just // ignore it and complete to the found url. let untrimmedURL = prefix + url; if (untrimmedURL && - !untrimmedURL.toLowerCase().contains(ac._originalSearchString.toLowerCase())) { + !untrimmedURL.toLowerCase().contains(this._originalSearchString.toLowerCase())) { untrimmedURL = null; } - ac._result.appendMatch(ac._strippedPrefix + url, "", "", "", untrimmedURL); + this._result.appendMatch(this._strippedPrefix + url, "", "", "", untrimmedURL); // handleCompletion() will cause the result listener to be called, and // will display the result in the UI. }, - handleError: function(aError) { + handleError: aError => { Components.utils.reportError( "URL Inline Complete: An async statement encountered an " + "error: " + aError.result + ", '" + aError.message + "'"); }, - handleCompletion: function(aReason) { - ac._finishSearch(); + handleCompletion: aReason => { + this._finishSearch(); } }, this._db); this._pendingQuery = wrapper.executeAsync([query]); }, stopSearch: function UIC_stopSearch() { delete this._originalSearchString; delete this._currentSearchString; delete this._result; delete this._listener; + delete this._pendingSearch; if (this._pendingQuery) { this._pendingQuery.cancel(); delete this._pendingQuery; } }, /**
--- a/toolkit/components/places/tests/autocomplete/test_keyword_search.js +++ b/toolkit/components/places/tests/autocomplete/test_keyword_search.js @@ -33,28 +33,30 @@ let kURIs = [ keyBase, otherBase + "%s", keyBase + "twoKey", otherBase + "twoKey" ]; let kTitles = [ "Generic page title", "Keyword title", + "abc", + "xyz" ]; // Add the keyword bookmark addPageBook(0, 0, 1, [], keyKey); // Add in the "fake pages" for keyword searches -gPages[1] = [1,0]; -gPages[2] = [2,0]; -gPages[3] = [3,0]; -gPages[4] = [4,0]; +gPages[1] = [1,2]; +gPages[2] = [2,2]; +gPages[3] = [3,2]; +gPages[4] = [4,2]; // Add a page into history -addPageBook(5, 0); -gPages[6] = [6,0]; +addPageBook(5, 2); +gPages[6] = [6,2]; // Provide for each test: description; search terms; array of gPages indices of // pages that should match; optional function to be run before the test let gTests = [ ["0: Plain keyword query", keyKey + " term", [1]], ["1: Multi-word keyword query", keyKey + " multi word", [2]],
--- a/toolkit/components/places/tests/inline/head_autocomplete.js +++ b/toolkit/components/places/tests/inline/head_autocomplete.js @@ -117,84 +117,83 @@ function ensure_results(aSearchString, a controller.input = input; let numSearchesStarted = 0; input.onSearchBegin = function() { numSearchesStarted++; do_check_eq(numSearchesStarted, 1); }; - input.onSearchComplete = function() { - // We should be running only one query. - do_check_eq(numSearchesStarted, 1); - // Check the autoFilled result. - do_check_eq(input.textValue, autoFilledValue); + let promise = new Promise(resolve => { + input.onSearchComplete = function() { + // We should be running only one query. + do_check_eq(numSearchesStarted, 1); + + // Check the autoFilled result. + do_check_eq(input.textValue, autoFilledValue); - if (completedValue) { - // Now force completion and check correct casing of the result. - // This ensures the controller is able to do its magic case-preserving - // stuff and correct replacement of the user's casing with result's one. - controller.handleEnter(false); - do_check_eq(input.textValue, completedValue); - } + if (completedValue) { + // Now force completion and check correct casing of the result. + // This ensures the controller is able to do its magic case-preserving + // stuff and correct replacement of the user's casing with result's one. + controller.handleEnter(false); + do_check_eq(input.textValue, completedValue); + } - waitForCleanup(run_next_test); - }; + // Cleanup. + remove_all_bookmarks(); + PlacesTestUtils.clearHistory().then(resolve); + }; + }); do_print("Searching for: '" + aSearchString + "'"); controller.startSearch(aSearchString); + + return promise; } function run_test() { do_register_cleanup(function () { Services.prefs.clearUserPref("browser.urlbar.autocomplete.enabled"); Services.prefs.clearUserPref("browser.urlbar.autoFill"); Services.prefs.clearUserPref("browser.urlbar.autoFill.typed"); }); gAutoCompleteTests.forEach(function (testData) { let [description, searchString, expectedValue, setupFunc] = testData; - add_test(function () { + add_task(function* () { do_print(description); Services.prefs.setBoolPref("browser.urlbar.autocomplete.enabled", true); Services.prefs.setBoolPref("browser.urlbar.autoFill", true); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); if (setupFunc) { - setupFunc(); + yield setupFunc(); } // At this point frecency could still be updating due to latest pages // updates. // This is not a problem in real life, but autocomplete tests should // return reliable resultsets, thus we have to wait. - PlacesTestUtils.promiseAsyncUpdates() - .then(() => ensure_results(searchString, expectedValue)); + yield PlacesTestUtils.promiseAsyncUpdates(); + yield ensure_results(searchString, expectedValue); }) }, this); run_next_test(); } let gAutoCompleteTests = []; function add_autocomplete_test(aTestData) { gAutoCompleteTests.push(aTestData); } -function waitForCleanup(aCallback) { - remove_all_bookmarks(); - PlacesTestUtils.clearHistory().then(aCallback); -} - -function addBookmark(aBookmarkObj) { +function* addBookmark(aBookmarkObj) { do_check_true(!!aBookmarkObj.url); - let parentId = aBookmarkObj.parentId ? aBookmarkObj.parentId - : PlacesUtils.unfiledBookmarksFolderId; - let itemId = PlacesUtils.bookmarks - .insertBookmark(parentId, - NetUtil.newURI(aBookmarkObj.url), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "A bookmark"); + yield PlacesUtils.bookmarks + .insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: aBookmarkObj.url }); if (aBookmarkObj.keyword) { - PlacesUtils.bookmarks.setKeywordForBookmark(itemId, aBookmarkObj.keyword); + yield PlacesUtils.keywords.insert({ keyword: aBookmarkObj.keyword, + url: aBookmarkObj.url }); } }
--- a/toolkit/components/places/tests/inline/test_autocomplete_functional.js +++ b/toolkit/components/places/tests/inline/test_autocomplete_functional.js @@ -3,131 +3,118 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ // Functional tests for inline autocomplete add_autocomplete_test([ "Check disabling autocomplete disables autofill", "vis", "vis", - function () - { + function* () { Services.prefs.setBoolPref("browser.urlbar.autocomplete.enabled", false); - PlacesTestUtils.addVisits({ + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://visit.mozilla.org"), transition: TRANSITION_TYPED }); } ]); add_autocomplete_test([ "Check disabling autofill disables autofill", "vis", "vis", - function () - { + function* () { Services.prefs.setBoolPref("browser.urlbar.autoFill", false); - PlacesTestUtils.addVisits({ + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://visit.mozilla.org"), transition: TRANSITION_TYPED }); } ]); add_autocomplete_test([ "Add urls, check for correct order", "vis", "visit2.mozilla.org/", - function () - { + function* () { let places = [{ uri: NetUtil.newURI("http://visit1.mozilla.org") }, { uri: NetUtil.newURI("http://visit2.mozilla.org"), transition: TRANSITION_TYPED }]; - PlacesTestUtils.addVisits(places); + yield PlacesTestUtils.addVisits(places); } ]); add_autocomplete_test([ "Add urls, make sure www and http are ignored", "visit1", "visit1.mozilla.org/", - function () - { - PlacesTestUtils.addVisits(NetUtil.newURI("http://www.visit1.mozilla.org")); + function* () { + yield PlacesTestUtils.addVisits(NetUtil.newURI("http://www.visit1.mozilla.org")); } ]); add_autocomplete_test([ "Autocompleting after an existing host completes to the url", "visit3.mozilla.org/", "visit3.mozilla.org/", - function () - { - PlacesTestUtils.addVisits(NetUtil.newURI("http://www.visit3.mozilla.org")); + function* () { + yield PlacesTestUtils.addVisits(NetUtil.newURI("http://www.visit3.mozilla.org")); } ]); add_autocomplete_test([ "Searching for www.me should yield www.me.mozilla.org/", "www.me", "www.me.mozilla.org/", - function () - { - PlacesTestUtils.addVisits(NetUtil.newURI("http://www.me.mozilla.org")); + function* () { + yield PlacesTestUtils.addVisits(NetUtil.newURI("http://www.me.mozilla.org")); } ]); add_autocomplete_test([ "With a bookmark and history, the query result should be the bookmark", "bookmark", "bookmark1.mozilla.org/", - function () - { - addBookmark({ url: "http://bookmark1.mozilla.org/", }); - PlacesTestUtils.addVisits(NetUtil.newURI("http://bookmark1.mozilla.org/foo")); + function* () { + yield addBookmark({ url: "http://bookmark1.mozilla.org/", }); + yield PlacesTestUtils.addVisits(NetUtil.newURI("http://bookmark1.mozilla.org/foo")); } ]); add_autocomplete_test([ "Check to make sure we get the proper results with full paths", "smokey", "smokey.mozilla.org/", - function () - { - + function* () { let places = [{ uri: NetUtil.newURI("http://smokey.mozilla.org/foo/bar/baz?bacon=delicious") }, { uri: NetUtil.newURI("http://smokey.mozilla.org/foo/bar/baz?bacon=smokey") }]; - PlacesTestUtils.addVisits(places); + yield PlacesTestUtils.addVisits(places); } ]); add_autocomplete_test([ "Check to make sure we autocomplete to the following '/'", "smokey.mozilla.org/fo", "smokey.mozilla.org/foo/", - function () - { - + function* () { let places = [{ uri: NetUtil.newURI("http://smokey.mozilla.org/foo/bar/baz?bacon=delicious") }, { uri: NetUtil.newURI("http://smokey.mozilla.org/foo/bar/baz?bacon=smokey") }]; - PlacesTestUtils.addVisits(places); + yield PlacesTestUtils.addVisits(places); } ]); add_autocomplete_test([ "Check to make sure we autocomplete after ?", "smokey.mozilla.org/foo?", "smokey.mozilla.org/foo?bacon=delicious", - function () - { - PlacesTestUtils.addVisits(NetUtil.newURI("http://smokey.mozilla.org/foo?bacon=delicious")); + function* () { + yield PlacesTestUtils.addVisits(NetUtil.newURI("http://smokey.mozilla.org/foo?bacon=delicious")); } ]); add_autocomplete_test([ "Check to make sure we autocomplete after #", "smokey.mozilla.org/foo?bacon=delicious#bar", "smokey.mozilla.org/foo?bacon=delicious#bar", - function () - { - PlacesTestUtils.addVisits(NetUtil.newURI("http://smokey.mozilla.org/foo?bacon=delicious#bar")); + function* () { + yield PlacesTestUtils.addVisits(NetUtil.newURI("http://smokey.mozilla.org/foo?bacon=delicious#bar")); } ]);
--- a/toolkit/components/places/tests/inline/test_casing.js +++ b/toolkit/components/places/tests/inline/test_casing.js @@ -1,102 +1,102 @@ /* 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/. */ add_autocomplete_test([ "Searching for cased entry 1", "MOZ", { autoFilled: "MOZilla.org/", completed: "mozilla.org/" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/test/") }); } ]); add_autocomplete_test([ "Searching for cased entry 2", "mozilla.org/T", { autoFilled: "mozilla.org/T", completed: "mozilla.org/T" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/test/") }); } ]); add_autocomplete_test([ "Searching for cased entry 3", "mozilla.org/T", { autoFilled: "mozilla.org/Test/", completed: "http://mozilla.org/Test/" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/") }); } ]); add_autocomplete_test([ "Searching for cased entry 4", "mOzilla.org/t", { autoFilled: "mOzilla.org/t", completed: "mOzilla.org/t" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/") }); }, ]); add_autocomplete_test([ "Searching for cased entry 5", "mOzilla.org/T", { autoFilled: "mOzilla.org/Test/", completed: "http://mozilla.org/Test/" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/") }); }, ]); add_autocomplete_test([ "Searching for untrimmed cased entry", "http://mOz", { autoFilled: "http://mOzilla.org/", completed: "http://mozilla.org/" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/") }); }, ]); add_autocomplete_test([ "Searching for untrimmed cased entry with www", "http://www.mOz", { autoFilled: "http://www.mOzilla.org/", completed: "http://www.mozilla.org/" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://www.mozilla.org/Test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://www.mozilla.org/Test/") }); }, ]); add_autocomplete_test([ "Searching for untrimmed cased entry with path", "http://mOzilla.org/t", { autoFilled: "http://mOzilla.org/t", completed: "http://mOzilla.org/t" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/") }); }, ]); add_autocomplete_test([ "Searching for untrimmed cased entry with path 2", "http://mOzilla.org/T", { autoFilled: "http://mOzilla.org/Test/", completed: "http://mozilla.org/Test/" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/") }); }, ]); add_autocomplete_test([ "Searching for untrimmed cased entry with www and path", "http://www.mOzilla.org/t", { autoFilled: "http://www.mOzilla.org/t", completed: "http://www.mOzilla.org/t" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://www.mozilla.org/Test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://www.mozilla.org/Test/") }); }, ]); add_autocomplete_test([ "Searching for untrimmed cased entry with www and path 2", "http://www.mOzilla.org/T", { autoFilled: "http://www.mOzilla.org/Test/", completed: "http://www.mozilla.org/Test/" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://www.mozilla.org/Test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://www.mozilla.org/Test/") }); }, ]);
--- a/toolkit/components/places/tests/inline/test_do_not_trim.js +++ b/toolkit/components/places/tests/inline/test_do_not_trim.js @@ -4,76 +4,76 @@ // Inline should never return matches shorter than the search string, since // that largely confuses completeDefaultIndex add_autocomplete_test([ "Do not autofill whitespaced entry 1", "mozilla.org ", "mozilla.org ", - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), transition: TRANSITION_TYPED }); } ]); add_autocomplete_test([ "Do not autofill whitespaced entry 2", "mozilla.org/ ", "mozilla.org/ ", - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), transition: TRANSITION_TYPED }); } ]); add_autocomplete_test([ "Do not autofill whitespaced entry 3", "mozilla.org/link ", "mozilla.org/link ", - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), transition: TRANSITION_TYPED }); } ]); add_autocomplete_test([ "Do not autofill whitespaced entry 4", "mozilla.org/link/ ", "mozilla.org/link/ ", - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), transition: TRANSITION_TYPED }); } ]); add_autocomplete_test([ "Do not autofill whitespaced entry 5", "moz illa ", "moz illa ", - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), transition: TRANSITION_TYPED }); } ]); add_autocomplete_test([ "Do not autofill whitespaced entry 6", " mozilla", " mozilla", - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), transition: TRANSITION_TYPED }); } ]);
--- a/toolkit/components/places/tests/inline/test_keywords.js +++ b/toolkit/components/places/tests/inline/test_keywords.js @@ -1,48 +1,48 @@ /* 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/. */ add_autocomplete_test([ "Searching for non-keyworded entry should autoFill it", "moz", "mozilla.org/", - function () { - addBookmark({ url: "http://mozilla.org/test/" }); + function* () { + yield addBookmark({ url: "http://mozilla.org/test/" }); } ]); add_autocomplete_test([ "Searching for keyworded entry should not autoFill it", "moz", "moz", - function () { - addBookmark({ url: "http://mozilla.org/test/", keyword: "moz" }); + function* () { + yield addBookmark({ url: "http://mozilla.org/test/", keyword: "moz" }); } ]); add_autocomplete_test([ "Searching for more than keyworded entry should autoFill it", "mozi", "mozilla.org/", - function () { - addBookmark({ url: "http://mozilla.org/test/", keyword: "moz" }); + function* () { + yield addBookmark({ url: "http://mozilla.org/test/", keyword: "moz" }); } ]); add_autocomplete_test([ "Searching for less than keyworded entry should autoFill it", "mo", "mozilla.org/", - function () { - addBookmark({ url: "http://mozilla.org/test/", keyword: "moz" }); + function* () { + yield addBookmark({ url: "http://mozilla.org/test/", keyword: "moz" }); } ]); add_autocomplete_test([ "Searching for keyworded entry is case-insensitive", "MoZ", "MoZ", - function () { - addBookmark({ url: "http://mozilla.org/test/", keyword: "moz" }); + function* () { + yield addBookmark({ url: "http://mozilla.org/test/", keyword: "moz" }); } ]);
--- a/toolkit/components/places/tests/inline/test_queryurl.js +++ b/toolkit/components/places/tests/inline/test_queryurl.js @@ -1,60 +1,60 @@ /* 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/. */ add_autocomplete_test([ "Searching for host match without slash should match host", "file", "file.org/", - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://file.org/test/"), transition: TRANSITION_TYPED }, { uri: NetUtil.newURI("file:///c:/test.html"), transition: TRANSITION_TYPED }); }, ]); add_autocomplete_test([ "Searching match with slash at the end should do nothing", "file.org/", "file.org/", - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://file.org/test/"), transition: TRANSITION_TYPED }, { uri: NetUtil.newURI("file:///c:/test.html"), transition: TRANSITION_TYPED }); }, ]); add_autocomplete_test([ "Searching match with slash in the middle should match url", "file.org/t", "file.org/test/", - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://file.org/test/"), transition: TRANSITION_TYPED }, { uri: NetUtil.newURI("file:///c:/test.html"), transition: TRANSITION_TYPED }); }, ]); add_autocomplete_test([ "Searching for non-host match without slash should not match url", "file", "file", - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("file:///c:/test.html"), transition: TRANSITION_TYPED }); }, ]);
--- a/toolkit/components/places/tests/inline/test_trimming.js +++ b/toolkit/components/places/tests/inline/test_trimming.js @@ -1,313 +1,306 @@ /* 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/. */ add_autocomplete_test([ "Searching for untrimmed https://www entry", "mo", { autoFilled: "mozilla.org/", completed: "https://www.mozilla.org/" }, - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("https://www.mozilla.org/test/"), transition: TRANSITION_TYPED }); }, ]); add_autocomplete_test([ "Searching for untrimmed https://www entry with path", "mozilla.org/t", { autoFilled: "mozilla.org/test/", completed: "https://www.mozilla.org/test/" }, - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("https://www.mozilla.org/test/"), transition: TRANSITION_TYPED }); }, ]); add_autocomplete_test([ "Searching for untrimmed https:// entry", "mo", { autoFilled: "mozilla.org/", completed: "https://mozilla.org/" }, - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("https://mozilla.org/test/"), transition: TRANSITION_TYPED }); }, ]); add_autocomplete_test([ "Searching for untrimmed https:// entry with path", "mozilla.org/t", { autoFilled: "mozilla.org/test/", completed: "https://mozilla.org/test/" }, - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("https://mozilla.org/test/"), transition: TRANSITION_TYPED }); }, ]); add_autocomplete_test([ "Searching for untrimmed http://www entry", "mo", { autoFilled: "mozilla.org/", completed: "www.mozilla.org/" }, - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://www.mozilla.org/test/"), transition: TRANSITION_TYPED }); }, ]); add_autocomplete_test([ "Searching for untrimmed http://www entry with path", "mozilla.org/t", { autoFilled: "mozilla.org/test/", completed: "http://www.mozilla.org/test/" }, - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://www.mozilla.org/test/"), transition: TRANSITION_TYPED }); }, ]); add_autocomplete_test([ "Searching for untrimmed ftp:// entry", "mo", { autoFilled: "mozilla.org/", completed: "ftp://mozilla.org/" }, - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("ftp://mozilla.org/test/"), transition: TRANSITION_TYPED }); }, ]); add_autocomplete_test([ "Searching for untrimmed ftp:// entry with path", "mozilla.org/t", { autoFilled: "mozilla.org/test/", completed: "ftp://mozilla.org/test/" }, - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("ftp://mozilla.org/test/"), transition: TRANSITION_TYPED }); }, ]); add_autocomplete_test([ "Ensuring correct priority 1", "mo", { autoFilled: "mozilla.org/", completed: "mozilla.org/" }, - function () { - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("https://www.mozilla.org/test/"), - transition: TRANSITION_TYPED - }); - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("https://mozilla.org/test/"), - transition: TRANSITION_TYPED - }); - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("ftp://mozilla.org/test/"), - transition: TRANSITION_TYPED - }); - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("http://www.mozilla.org/test/"), - transition: TRANSITION_TYPED - }); - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("http://mozilla.org/test/"), - transition: TRANSITION_TYPED - }); + function* () { + yield PlacesTestUtils.addVisits([ + { uri: NetUtil.newURI("https://www.mozilla.org/test/"), + transition: TRANSITION_TYPED + }, + { uri: NetUtil.newURI("https://mozilla.org/test/"), + transition: TRANSITION_TYPED + }, + { uri: NetUtil.newURI("ftp://mozilla.org/test/"), + transition: TRANSITION_TYPED + }, + { uri: NetUtil.newURI("http://www.mozilla.org/test/"), + transition: TRANSITION_TYPED + }, + { uri: NetUtil.newURI("http://mozilla.org/test/"), + transition: TRANSITION_TYPED + } + ]); }, ]); add_autocomplete_test([ "Ensuring correct priority 2", "mo", { autoFilled: "mozilla.org/", completed: "mozilla.org/" }, - function () { - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("https://mozilla.org/test/"), - transition: TRANSITION_TYPED - }); - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("ftp://mozilla.org/test/"), - transition: TRANSITION_TYPED - }); - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("http://www.mozilla.org/test/"), - transition: TRANSITION_TYPED - }); - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("http://mozilla.org/test/"), - transition: TRANSITION_TYPED - }); + function* () { + yield PlacesTestUtils.addVisits([ + { uri: NetUtil.newURI("https://mozilla.org/test/"), + transition: TRANSITION_TYPED + }, + { uri: NetUtil.newURI("ftp://mozilla.org/test/"), + transition: TRANSITION_TYPED + }, + { uri: NetUtil.newURI("http://www.mozilla.org/test/"), + transition: TRANSITION_TYPED + }, + { uri: NetUtil.newURI("http://mozilla.org/test/"), + transition: TRANSITION_TYPED + } + ]); }, ]); add_autocomplete_test([ "Ensuring correct priority 3", "mo", { autoFilled: "mozilla.org/", completed: "mozilla.org/" }, - function () { - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("ftp://mozilla.org/test/"), - transition: TRANSITION_TYPED - }); - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("http://www.mozilla.org/test/"), - transition: TRANSITION_TYPED - }); - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("http://mozilla.org/test/"), - transition: TRANSITION_TYPED - }); + function* () { + yield PlacesTestUtils.addVisits([ + { uri: NetUtil.newURI("ftp://mozilla.org/test/"), + transition: TRANSITION_TYPED + }, + { uri: NetUtil.newURI("http://www.mozilla.org/test/"), + transition: TRANSITION_TYPED + }, + { uri: NetUtil.newURI("http://mozilla.org/test/"), + transition: TRANSITION_TYPED + } + ]); }, ]); add_autocomplete_test([ "Ensuring correct priority 4", "mo", { autoFilled: "mozilla.org/", completed: "mozilla.org/" }, - function () { - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("http://www.mozilla.org/test/"), - transition: TRANSITION_TYPED - }); - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("http://mozilla.org/test/"), - transition: TRANSITION_TYPED - }); + function* () { + yield PlacesTestUtils.addVisits([ + { uri: NetUtil.newURI("http://www.mozilla.org/test/"), + transition: TRANSITION_TYPED + }, + { uri: NetUtil.newURI("http://mozilla.org/test/"), + transition: TRANSITION_TYPED + } + ]); }, ]); add_autocomplete_test([ "Ensuring correct priority 5", "mo", { autoFilled: "mozilla.org/", completed: "ftp://mozilla.org/" }, - function () { - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("ftp://mozilla.org/test/"), - transition: TRANSITION_TYPED - }); - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("ftp://www.mozilla.org/test/"), - transition: TRANSITION_TYPED - }); + function* () { + yield PlacesTestUtils.addVisits([ + { uri: NetUtil.newURI("ftp://mozilla.org/test/"), + transition: TRANSITION_TYPED + }, + { uri: NetUtil.newURI("ftp://www.mozilla.org/test/"), + transition: TRANSITION_TYPED + } + ]); }, ]); add_autocomplete_test([ "Ensuring correct priority 6", "mo", { autoFilled: "mozilla.org/", completed: "www.mozilla.org/" }, - function () { - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("http://www.mozilla.org/test1/"), - transition: TRANSITION_TYPED - }); - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("http://www.mozilla.org/test2/"), - transition: TRANSITION_TYPED - }); + function* () { + yield PlacesTestUtils.addVisits([ + { uri: NetUtil.newURI("http://www.mozilla.org/test1/"), + transition: TRANSITION_TYPED + }, + { uri: NetUtil.newURI("http://www.mozilla.org/test2/"), + transition: TRANSITION_TYPED + } + ]); }, ]); add_autocomplete_test([ "Ensuring longer domain can't match", "mo", { autoFilled: "mozilla.co/", completed: "mozilla.co/" }, - function () { + function* () { // The .co should be preferred, but should not get the https from the .com. // The .co domain must be added later to activate the trigger bug. - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("https://mozilla.com/"), - transition: TRANSITION_TYPED - }); - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("http://mozilla.co/"), - transition: TRANSITION_TYPED - }); - PlacesTestUtils.addVisits({ - uri: NetUtil.newURI("http://mozilla.co/"), - transition: TRANSITION_TYPED - }); + yield PlacesTestUtils.addVisits([ + { uri: NetUtil.newURI("https://mozilla.com/"), + transition: TRANSITION_TYPED + }, + { uri: NetUtil.newURI("http://mozilla.co/"), + transition: TRANSITION_TYPED + }, + { uri: NetUtil.newURI("http://mozilla.co/"), + transition: TRANSITION_TYPED + } + ]); }, ]); add_autocomplete_test([ "Searching for URL with characters that are normally escaped", "https://www.mozilla.org/啊-test", { autoFilled: "https://www.mozilla.org/啊-test", completed: "https://www.mozilla.org/啊-test" }, - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("https://www.mozilla.org/啊-test"), transition: TRANSITION_TYPED }); }, ]); add_autocomplete_test([ "Don't return unsecure URL when searching for secure ones", "https://test.moz.org/t", { autoFilled: "https://test.moz.org/test/", completed: "https://test.moz.org/test/" }, - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://test.moz.org/test/"), transition: TRANSITION_TYPED }); }, ]); add_autocomplete_test([ "Don't return unsecure domain when searching for secure ones", "https://test.moz", { autoFilled: "https://test.moz.org/", completed: "https://test.moz.org/" }, - function () { - PlacesTestUtils.addVisits({ + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://test.moz.org/test/"), transition: TRANSITION_TYPED }); }, ]); add_autocomplete_test([ "Untyped is not accounted for www", "mo", { autoFilled: "moz.org/", completed: "moz.org/" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://www.moz.org/test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://www.moz.org/test/") }); }, ]); add_autocomplete_test([ "Untyped is not accounted for ftp", "mo", { autoFilled: "moz.org/", completed: "moz.org/" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("ftp://moz.org/test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("ftp://moz.org/test/") }); }, ]); add_autocomplete_test([ "Untyped is not accounted for https", "mo", { autoFilled: "moz.org/", completed: "moz.org/" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("https://moz.org/test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("https://moz.org/test/") }); }, ]); add_autocomplete_test([ "Untyped is not accounted for https://www", "mo", { autoFilled: "moz.org/", completed: "moz.org/" }, - function () { - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("https://www.moz.org/test/") }); + function* () { + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("https://www.moz.org/test/") }); }, ]);
--- a/toolkit/components/places/tests/inline/test_typed.js +++ b/toolkit/components/places/tests/inline/test_typed.js @@ -4,67 +4,67 @@ // First do searches with typed behavior forced to false, so later tests will // ensure autocomplete is able to dinamically switch behavior. add_autocomplete_test([ "Searching for domain should autoFill it", "moz", "mozilla.org/", - function () { + function* () { Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); - PlacesTestUtils.addVisits(NetUtil.newURI("http://mozilla.org/link/")); + yield PlacesTestUtils.addVisits(NetUtil.newURI("http://mozilla.org/link/")); } ]); add_autocomplete_test([ "Searching for url should autoFill it", "mozilla.org/li", "mozilla.org/link/", - function () { + function* () { Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); - PlacesTestUtils.addVisits(NetUtil.newURI("http://mozilla.org/link/")); + yield PlacesTestUtils.addVisits(NetUtil.newURI("http://mozilla.org/link/")); } ]); // Now do searches with typed behavior forced to true. add_autocomplete_test([ "Searching for non-typed domain should not autoFill it", "moz", "moz", - function () { + function* () { Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", true); - PlacesTestUtils.addVisits(NetUtil.newURI("http://mozilla.org/link/")); + yield PlacesTestUtils.addVisits(NetUtil.newURI("http://mozilla.org/link/")); } ]); add_autocomplete_test([ "Searching for typed domain should autoFill it", "moz", "mozilla.org/", - function () { + function* () { Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", true); - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/typed/"), - transition: TRANSITION_TYPED }); + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/typed/"), + transition: TRANSITION_TYPED }); } ]); add_autocomplete_test([ "Searching for non-typed url should not autoFill it", "mozilla.org/li", "mozilla.org/li", - function () { + function* () { Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", true); - PlacesTestUtils.addVisits(NetUtil.newURI("http://mozilla.org/link/")); + yield PlacesTestUtils.addVisits(NetUtil.newURI("http://mozilla.org/link/")); } ]); add_autocomplete_test([ "Searching for typed url should autoFill it", "mozilla.org/li", "mozilla.org/link/", - function () { + function* () { Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", true); - PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), - transition: TRANSITION_TYPED }); + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), + transition: TRANSITION_TYPED }); } ]);
--- a/toolkit/components/places/tests/inline/test_zero_frecency.js +++ b/toolkit/components/places/tests/inline/test_zero_frecency.js @@ -3,29 +3,29 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ // Ensure inline autocomplete doesn't return zero frecency pages. add_autocomplete_test([ "Searching for zero frecency domain should not autoFill it", "moz", "moz", - function () { + function* () { Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); - PlacesTestUtils.addVisits({ + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/framed_link/"), transition: TRANSITION_FRAMED_LINK }); } ]); add_autocomplete_test([ "Searching for zero frecency url should not autoFill it", "mozilla.org/f", "mozilla.org/f", - function () { + function* () { Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); - PlacesTestUtils.addVisits({ + yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/framed_link/"), transition: TRANSITION_FRAMED_LINK }); } ]);
--- a/toolkit/components/places/tests/migration/test_current_from_v26.js +++ b/toolkit/components/places/tests/migration/test_current_from_v26.js @@ -72,28 +72,27 @@ add_task(function* database_is_valid() { let db = yield PlacesUtils.promiseDBConnection(); Assert.equal((yield db.getSchemaVersion()), CURRENT_SCHEMA_VERSION); }); add_task(function* test_keywords() { // When 2 urls have the same keyword, if one has postData it will be // preferred. - let [ url1, postData1 ] = PlacesUtils.getURLAndPostDataForKeyword("kw1"); - Assert.equal(url1, "http://test2.com/"); - Assert.equal(postData1, "postData1"); - let [ url2, postData2 ] = PlacesUtils.getURLAndPostDataForKeyword("kw2"); - Assert.equal(url2, "http://test2.com/"); - Assert.equal(postData2, "postData2"); - let [ url3, postData3 ] = PlacesUtils.getURLAndPostDataForKeyword("kw3"); - Assert.equal(url3, "http://test1.com/"); - Assert.equal(postData3, null); - let [ url4, postData4 ] = PlacesUtils.getURLAndPostDataForKeyword("kw4"); - Assert.equal(url4, null); - Assert.equal(postData4, null); - let [ url5, postData5 ] = PlacesUtils.getURLAndPostDataForKeyword("kw5"); - Assert.equal(url5, "http://test3.com/"); - Assert.equal(postData5, "postData3"); + let entry1 = yield PlacesUtils.keywords.fetch("kw1"); + Assert.equal(entry1.url.href, "http://test2.com/"); + Assert.equal(entry1.postData, "postData1"); + let entry2 = yield PlacesUtils.keywords.fetch("kw2"); + Assert.equal(entry2.url.href, "http://test2.com/"); + Assert.equal(entry2.postData, "postData2"); + let entry3 = yield PlacesUtils.keywords.fetch("kw3"); + Assert.equal(entry3.url.href, "http://test1.com/"); + Assert.equal(entry3.postData, null); + let entry4 = yield PlacesUtils.keywords.fetch("kw4"); + Assert.equal(entry4, null); + let entry5 = yield PlacesUtils.keywords.fetch("kw5"); + Assert.equal(entry5.url.href, "http://test3.com/"); + Assert.equal(entry5.postData, "postData3"); Assert.equal((yield foreign_count("http://test1.com/")), 5); // 4 bookmark2 + 1 keywords Assert.equal((yield foreign_count("http://test2.com/")), 4); // 2 bookmark2 + 2 keywords Assert.equal((yield foreign_count("http://test3.com/")), 3); // 2 bookmark2 + 1 keywords });
--- a/toolkit/components/places/tests/migration/test_current_from_v27.js +++ b/toolkit/components/places/tests/migration/test_current_from_v27.js @@ -60,18 +60,18 @@ add_task(function* database_is_valid() { let db = yield PlacesUtils.promiseDBConnection(); Assert.equal((yield db.getSchemaVersion()), CURRENT_SCHEMA_VERSION); }); add_task(function* test_keywords() { // When 2 urls have the same keyword, if one has postData it will be // preferred. - let [ url1, postData1 ] = PlacesUtils.getURLAndPostDataForKeyword("kw1"); - Assert.equal(url1, "http://test2.com/"); - Assert.equal(postData1, "postData1"); - let [ url2, postData2 ] = PlacesUtils.getURLAndPostDataForKeyword("kw2"); - Assert.equal(url2, "http://test2.com/"); - Assert.equal(postData2, "postData2"); - let [ url3, postData3 ] = PlacesUtils.getURLAndPostDataForKeyword("kw3"); - Assert.equal(url3, "http://test1.com/"); - Assert.equal(postData3, null); + let entry1 = yield PlacesUtils.keywords.fetch("kw1"); + Assert.equal(entry1.url.href, "http://test2.com/"); + Assert.equal(entry1.postData, "postData1"); + let entry2 = yield PlacesUtils.keywords.fetch("kw2"); + Assert.equal(entry2.url.href, "http://test2.com/"); + Assert.equal(entry2.postData, "postData2"); + let entry3 = yield PlacesUtils.keywords.fetch("kw3"); + Assert.equal(entry3.url.href, "http://test1.com/"); + Assert.equal(entry3.postData, null); });
--- a/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js +++ b/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js @@ -221,17 +221,18 @@ let addBookmark = Task.async(function* ( let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: (yield PlacesUtils.promiseItemGuid(parentId)), title: aBookmarkObj.title || "A bookmark", url: aBookmarkObj.uri }); let itemId = yield PlacesUtils.promiseItemId(bm.guid); if (aBookmarkObj.keyword) { - PlacesUtils.bookmarks.setKeywordForBookmark(itemId, aBookmarkObj.keyword); + yield PlacesUtils.keywords.insert({ keyword: aBookmarkObj.keyword, + url: aBookmarkObj.uri.spec }); } if (aBookmarkObj.tags) { PlacesUtils.tagging.tagURI(aBookmarkObj.uri, aBookmarkObj.tags); } }); function addOpenPages(aUri, aCount=1) {
--- a/toolkit/components/places/tests/unifiedcomplete/test_keyword_search.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_keyword_search.js @@ -19,49 +19,49 @@ add_task(function* test_keyword_searc() { uri: uri1, title: "Generic page title" }, { uri: uri2, title: "Generic page title" } ]); yield addBookmark({ uri: uri1, title: "Bookmark title", keyword: "key"}); do_print("Plain keyword query"); yield check_autocomplete({ search: "key term", - matches: [ { uri: NetUtil.newURI("http://abc/?search=term"), title: "Generic page title", style: ["keyword"] } ] + matches: [ { uri: NetUtil.newURI("http://abc/?search=term"), title: "abc", style: ["keyword"] } ] }); do_print("Multi-word keyword query"); yield check_autocomplete({ search: "key multi word", - matches: [ { uri: NetUtil.newURI("http://abc/?search=multi+word"), title: "Generic page title", style: ["keyword"] } ] + matches: [ { uri: NetUtil.newURI("http://abc/?search=multi+word"), title: "abc", style: ["keyword"] } ] }); do_print("Keyword query with +"); yield check_autocomplete({ search: "key blocking+", - matches: [ { uri: NetUtil.newURI("http://abc/?search=blocking%2B"), title: "Generic page title", style: ["keyword"] } ] + matches: [ { uri: NetUtil.newURI("http://abc/?search=blocking%2B"), title: "abc", style: ["keyword"] } ] }); do_print("Unescaped term in query"); yield check_autocomplete({ search: "key ユニコード", - matches: [ { uri: NetUtil.newURI("http://abc/?search=ユニコード"), title: "Generic page title", style: ["keyword"] } ] + matches: [ { uri: NetUtil.newURI("http://abc/?search=ユニコード"), title: "abc", style: ["keyword"] } ] }); do_print("Keyword that happens to match a page"); yield check_autocomplete({ search: "key ThisPageIsInHistory", - matches: [ { uri: NetUtil.newURI("http://abc/?search=ThisPageIsInHistory"), title: "Generic page title", style: ["keyword"] } ] + matches: [ { uri: NetUtil.newURI("http://abc/?search=ThisPageIsInHistory"), title: "abc", style: ["keyword"] } ] }); do_print("Keyword without query (without space)"); yield check_autocomplete({ search: "key", - matches: [ { uri: NetUtil.newURI("http://abc/?search="), title: "Generic page title", style: ["keyword"] } ] + matches: [ { uri: NetUtil.newURI("http://abc/?search="), title: "abc", style: ["keyword"] } ] }); do_print("Keyword without query (with space)"); yield check_autocomplete({ search: "key ", - matches: [ { uri: NetUtil.newURI("http://abc/?search="), title: "Generic page title", style: ["keyword"] } ] + matches: [ { uri: NetUtil.newURI("http://abc/?search="), title: "abc", style: ["keyword"] } ] }); yield cleanup(); });
--- a/toolkit/components/places/tests/unifiedcomplete/test_keyword_search_actions.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_keyword_search_actions.js @@ -20,55 +20,55 @@ add_task(function* test_keyword_search() { uri: uri2, title: "Generic page title" } ]); yield addBookmark({ uri: uri1, title: "Bookmark title", keyword: "key"}); do_print("Plain keyword query"); yield check_autocomplete({ search: "key term", searchParam: "enable-actions", - matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=term", input: "key term"}), title: "Generic page title", style: [ "action", "keyword" ] } ] + matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=term", input: "key term"}), title: "abc", style: [ "action", "keyword" ] } ] }); do_print("Multi-word keyword query"); yield check_autocomplete({ search: "key multi word", searchParam: "enable-actions", - matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=multi+word", input: "key multi word"}), title: "Generic page title", style: [ "action", "keyword" ] } ] + matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=multi+word", input: "key multi word"}), title: "abc", style: [ "action", "keyword" ] } ] }); do_print("Keyword query with +"); yield check_autocomplete({ search: "key blocking+", searchParam: "enable-actions", - matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=blocking%2B", input: "key blocking+"}), title: "Generic page title", style: [ "action", "keyword" ] } ] + matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=blocking%2B", input: "key blocking+"}), title: "abc", style: [ "action", "keyword" ] } ] }); do_print("Unescaped term in query"); yield check_autocomplete({ search: "key ユニコード", searchParam: "enable-actions", - matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=ユニコード", input: "key ユニコード"}), title: "Generic page title", style: [ "action", "keyword" ] } ] + matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=ユニコード", input: "key ユニコード"}), title: "abc", style: [ "action", "keyword" ] } ] }); do_print("Keyword that happens to match a page"); yield check_autocomplete({ search: "key ThisPageIsInHistory", searchParam: "enable-actions", - matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=ThisPageIsInHistory", input: "key ThisPageIsInHistory"}), title: "Generic page title", style: [ "action", "keyword" ] } ] + matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=ThisPageIsInHistory", input: "key ThisPageIsInHistory"}), title: "abc", style: [ "action", "keyword" ] } ] }); do_print("Keyword without query (without space)"); yield check_autocomplete({ search: "key", searchParam: "enable-actions", - matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=", input: "key"}), title: "Generic page title", style: [ "action", "keyword" ] } ] + matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=", input: "key"}), title: "abc", style: [ "action", "keyword" ] } ] }); do_print("Keyword without query (with space)"); yield check_autocomplete({ search: "key ", searchParam: "enable-actions", - matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=", input: "key "}), title: "Generic page title", style: [ "action", "keyword" ] } ] + matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=", input: "key "}), title: "abc", style: [ "action", "keyword" ] } ] }); yield cleanup(); });
deleted file mode 100644 --- a/toolkit/components/places/tests/unit/test_398914.js +++ /dev/null @@ -1,30 +0,0 @@ -function run_test() { - var testURI = uri("http://foo.com"); - - /* - 1. Create a bookmark for a URI, with a keyword and post data. - 2. Create a bookmark for the same URI, with a different keyword and different post data. - 3. Confirm that our method for getting a URI+postdata retains bookmark affinity. - */ - var bm1 = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId, testURI, -1, "blah"); - PlacesUtils.bookmarks.setKeywordForBookmark(bm1, "foo"); - PlacesUtils.setPostDataForBookmark(bm1, "pdata1"); - var bm2 = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId, testURI, -1, "blah"); - PlacesUtils.bookmarks.setKeywordForBookmark(bm2, "bar"); - PlacesUtils.setPostDataForBookmark(bm2, "pdata2"); - - // check kw, pd for bookmark 1 - var url, postdata; - [url, postdata] = PlacesUtils.getURLAndPostDataForKeyword("foo"); - do_check_eq(testURI.spec, url); - do_check_eq(postdata, "pdata1"); - - // check kw, pd for bookmark 2 - [url, postdata] = PlacesUtils.getURLAndPostDataForKeyword("bar"); - do_check_eq(testURI.spec, url); - do_check_eq(postdata, "pdata2"); - - // cleanup - PlacesUtils.bookmarks.removeItem(bm1); - PlacesUtils.bookmarks.removeItem(bm2); -}
--- a/toolkit/components/places/tests/unit/test_keywords.js +++ b/toolkit/components/places/tests/unit/test_keywords.js @@ -69,17 +69,17 @@ function expectBookmarkNotifications() { }); PlacesUtils.bookmarks.addObserver(observer, false); return observer; } add_task(function* test_invalid_input() { Assert.throws(() => PlacesUtils.keywords.fetch(null), /Invalid keyword/); - Assert.throws(() => PlacesUtils.keywords.fetch(""), + Assert.throws(() => PlacesUtils.keywords.fetch({}), /Invalid keyword/); Assert.throws(() => PlacesUtils.keywords.fetch(5), /Invalid keyword/); Assert.throws(() => PlacesUtils.keywords.insert(null), /Input should be a valid object/); Assert.throws(() => PlacesUtils.keywords.insert("test"), /Input should be a valid object/);
--- a/toolkit/components/places/tests/unit/test_placesTxn.js +++ b/toolkit/components/places/tests/unit/test_placesTxn.js @@ -712,35 +712,55 @@ add_test(function test_sort_folder_by_na do_check_eq(0, bmsvc.getItemIndex(b1)); do_check_eq(1, bmsvc.getItemIndex(b2)); do_check_eq(2, bmsvc.getItemIndex(b3)); run_next_test(); }); add_test(function test_edit_postData() { - const POST_DATA_ANNO = "bookmarkProperties/POSTData"; - let postData = "post-test_edit_postData"; - let testURI = NetUtil.newURI("http://test_edit_postData.com"); - let testBkmId = bmsvc.insertBookmark(root, testURI, bmsvc.DEFAULT_INDEX, "Test edit Post Data"); - PlacesUtils.bookmarks.setKeywordForBookmark(testBkmId, "kw"); - let txn = new PlacesEditBookmarkPostDataTransaction(testBkmId, postData); + function* promiseKeyword(keyword, href, postData) { + while (true) { + let entry = yield PlacesUtils.keywords.fetch(keyword); + if (entry && entry.url.href == href && entry.postData == postData) { + break; + } + + yield new Promise(resolve => do_timeout(100, resolve)); + } + } + + Task.spawn(function* () { + const POST_DATA_ANNO = "bookmarkProperties/POSTData"; + let postData = "post-test_edit_postData"; + let testURI = NetUtil.newURI("http://test_edit_postData.com"); - txn.doTransaction(); - let [url, post_data] = PlacesUtils.getURLAndPostDataForKeyword("kw"); - Assert.equal(url, testURI.spec); - Assert.equal(postData, post_data); + let testBkm = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "http://test_edit_postData.com", + title: "Test edit Post Data" + }); + + yield PlacesUtils.keywords.insert({ + keyword: "kw", + url: "http://test_edit_postData.com" + }); - txn.undoTransaction(); - [url, post_data] = PlacesUtils.getURLAndPostDataForKeyword("kw"); - Assert.equal(url, testURI.spec); - // We don't allow anymore to set a null post data. - //Assert.equal(null, post_data); + let testBkmId = yield PlacesUtils.promiseItemId(testBkm.guid); + let txn = new PlacesEditBookmarkPostDataTransaction(testBkmId, postData); + + txn.doTransaction(); + yield promiseKeyword("kw", testURI.spec, postData); - run_next_test(); + txn.undoTransaction(); + entry = yield PlacesUtils.keywords.fetch("kw"); + Assert.equal(entry.url.href, testURI.spec); + // We don't allow anymore to set a null post data. + //Assert.equal(null, post_data); + }).then(run_next_test); }); add_test(function test_tagURI_untagURI() { const TAG_1 = "tag-test_tagURI_untagURI-bar"; const TAG_2 = "tag-test_tagURI_untagURI-foo"; let tagURI = NetUtil.newURI("http://test_tagURI_untagURI.com"); // Test tagURI
--- a/toolkit/components/places/tests/unit/xpcshell.ini +++ b/toolkit/components/places/tests/unit/xpcshell.ini @@ -18,17 +18,16 @@ support-files = [test_317472.js] # Bug 676989: test hangs consistently on Android skip-if = os == "android" [test_331487.js] [test_384370.js] [test_385397.js] # Bug 676989: test fails consistently on Android fail-if = os == "android" -[test_398914.js] [test_399264_query_to_string.js] [test_399264_string_to_query.js] [test_399266.js] # Bug 676989: test fails consistently on Android fail-if = os == "android" # Bug 821781: test fails intermittently on Linux skip-if = os == "linux" [test_402799.js]
--- a/toolkit/components/reader/AboutReader.jsm +++ b/toolkit/components/reader/AboutReader.jsm @@ -567,17 +567,22 @@ AboutReader.prototype = { } if (this._windowUnloaded) { return; } if (article && article.url == url) { this._showContent(article); + } else if (this._articlePromise) { + // If we were promised an article, show an error message if there's a failure. + this._showError(); } else { + // Otherwise, just load the original URL. We can encounter this case when + // loading an about:reader URL directly (e.g. opening a reading list item). this._win.location.href = url; } }), _getArticle: function(url) { return new Promise((resolve, reject) => { let listener = (message) => { this._mm.removeMessageListener("Reader:ArticleData", listener); @@ -608,49 +613,39 @@ AboutReader.prototype = { link.rel = 'shortcut icon'; link.href = faviconUrl; doc.getElementsByTagName('head')[0].appendChild(link); }, _updateImageMargins: function Reader_updateImageMargins() { let windowWidth = this._win.innerWidth; - let contentWidth = this._contentElement.offsetWidth; - let maxWidthStyle = windowWidth + "px !important"; + let bodyWidth = this._doc.body.clientWidth; let setImageMargins = function(img) { - if (!img._originalWidth) - img._originalWidth = img.offsetWidth; - - let imgWidth = img._originalWidth; - - // If the image is taking more than half of the screen, just make - // it fill edge-to-edge. - if (imgWidth < contentWidth && imgWidth > windowWidth * 0.55) - imgWidth = windowWidth; + // If the image is at least as wide as the window, make it fill edge-to-edge on mobile. + if (img.naturalWidth >= windowWidth) { + img.setAttribute("moz-reader-full-width", true); + } else { + img.removeAttribute("moz-reader-full-width"); + } - let sideMargin = Math.max((contentWidth - windowWidth) / 2, - (contentWidth - imgWidth) / 2); - - let imageStyle = sideMargin + "px !important"; - let widthStyle = imgWidth + "px !important"; - - let cssText = "max-width: " + maxWidthStyle + ";" + - "width: " + widthStyle + ";" + - "margin-left: " + imageStyle + ";" + - "margin-right: " + imageStyle + ";"; - - img.style.cssText = cssText; + // If the image is at least half as wide as the body, center it on desktop. + if (img.naturalWidth >= bodyWidth/2) { + img.setAttribute("moz-reader-center", true); + } else { + img.removeAttribute("moz-reader-center"); + } } let imgs = this._doc.querySelectorAll(this._BLOCK_IMAGES_SELECTOR); for (let i = imgs.length; --i >= 0;) { let img = imgs[i]; - if (img.width > 0) { + if (img.naturalWidth > 0) { setImageMargins(img); } else { img.onload = function() { setImageMargins(img); } } } }, @@ -659,24 +654,27 @@ AboutReader.prototype = { if(!article.dir) return; //Set "dir" attribute on content this._contentElement.setAttribute("dir", article.dir); this._headerElement.setAttribute("dir", article.dir); }, - _showError: function Reader_showError(error) { + _showError: function() { this._headerElement.style.display = "none"; this._contentElement.style.display = "none"; - this._messageElement.innerHTML = error; + let errorMessage = gStrings.GetStringFromName("aboutReader.loadError"); + this._messageElement.textContent = errorMessage; this._messageElement.style.display = "block"; - this._doc.title = error; + this._doc.title = errorMessage; + + this._error = true; }, // This function is the JS version of Java's StringUtils.stripCommonSubdomains. _stripHost: function Reader_stripHost(host) { if (!host) return host; let start = 0; @@ -725,25 +723,26 @@ AboutReader.prototype = { _hideContent: function Reader_hideContent() { this._headerElement.style.display = "none"; this._contentElement.style.display = "none"; }, _showProgressDelayed: function Reader_showProgressDelayed() { this._win.setTimeout(function() { // No need to show progress if the article has been loaded, - // or if the window has been unloaded. - if (this._article || this._windowUnloaded) { + // if the window has been unloaded, or if there was an error + // trying to load the article. + if (this._article || this._windowUnloaded || this._error) { return; } this._headerElement.style.display = "none"; this._contentElement.style.display = "none"; - this._messageElement.innerHTML = gStrings.GetStringFromName("aboutReader.loading"); + this._messageElement.textContent = gStrings.GetStringFromName("aboutReader.loading"); this._messageElement.style.display = "block"; }.bind(this), 300); }, /** * Returns the original article URL for this about:reader view. */ _getOriginalUrl: function() {
--- a/toolkit/components/telemetry/TelemetrySession.jsm +++ b/toolkit/components/telemetry/TelemetrySession.jsm @@ -722,16 +722,17 @@ this.TelemetrySession = Object.freeze({ * Used only for testing purposes. */ reset: function() { Impl._sessionId = null; Impl._subsessionId = null; Impl._previousSubsessionId = null; Impl._subsessionCounter = 0; Impl._profileSubsessionCounter = 0; + Impl._subsessionStartActiveTicks = 0; this.uninstall(); return this.setup(); }, /** * Used only for testing purposes. * @param {Boolean} [aForceSavePending=true] If true, always saves the ping whether Telemetry * can send pings or not, which is used for testing. */ @@ -799,32 +800,36 @@ let Impl = { // null on first run. _previousSubsessionId: null, // The running no. of subsessions since the start of the browser session _subsessionCounter: 0, // The running no. of all subsessions for the whole profile life time _profileSubsessionCounter: 0, // Date of the last session split _subsessionStartDate: null, + // The active ticks counted when the subsession starts + _subsessionStartActiveTicks: 0, // A task performing delayed initialization of the chrome process _delayedInitTask: null, // The deferred promise resolved when the initialization task completes. _delayedInitTaskDeferred: null, // Used to serialize session state writes to disk. _stateSaveSerializer: new SaveSerializer(), // Used to serialize aborted session ping writes to disk. _abortedSessionSerializer: new SaveSerializer(), /** * Gets a series of simple measurements (counters). At the moment, this * only returns startup data from nsIAppStartup.getStartupInfo(). + * @param {Boolean} isSubsession True if this is a subsession, false otherwise. + * @param {Boolean} clearSubsession True if a new subsession is being started, false otherwise. * * @return simple measurements as a dictionary. */ - getSimpleMeasurements: function getSimpleMeasurements(forSavedSession) { + getSimpleMeasurements: function getSimpleMeasurements(forSavedSession, isSubsession, clearSubsession) { this._log.trace("getSimpleMeasurements"); let si = Services.startup.getStartupInfo(); // Measurements common to chrome and content processes. let elapsedTime = Date.now() - si.process; var ret = { totalTime: Math.round(elapsedTime / 1000), // totalTime, in seconds @@ -904,17 +909,26 @@ let Impl = { ret.activeTicks = -1; if ("@mozilla.org/datareporting/service;1" in Cc) { let drs = Cc["@mozilla.org/datareporting/service;1"] .getService(Ci.nsISupports) .wrappedJSObject; let sr = drs.getSessionRecorder(); if (sr) { - ret.activeTicks = sr.activeTicks; + let activeTicks = sr.activeTicks; + if (isSubsession) { + activeTicks = sr.activeTicks - this._subsessionStartActiveTicks; + } + + if (clearSubsession) { + this._subsessionStartActiveTicks = activeTicks; + } + + ret.activeTicks = activeTicks; } } ret.pingsOverdue = TelemetryFile.pingsOverdue; ret.pingsDiscarded = TelemetryFile.pingsDiscarded; return ret; }, @@ -1335,19 +1349,23 @@ let Impl = { this._subsessionCounter++; this._profileSubsessionCounter++; }, getSessionPayload: function getSessionPayload(reason, clearSubsession) { this._log.trace("getSessionPayload - reason: " + reason + ", clearSubsession: " + clearSubsession); #if defined(MOZ_WIDGET_GONK) || defined(MOZ_WIDGET_ANDROID) clearSubsession = false; + const isSubsession = false; +#else + const isSubsession = !this._isClassicReason(reason); #endif - let measurements = this.getSimpleMeasurements(reason == REASON_SAVED_SESSION); + let measurements = + this.getSimpleMeasurements(reason == REASON_SAVED_SESSION, isSubsession, clearSubsession); let info = !IS_CONTENT_PROCESS ? this.getMetadata(reason) : null; let payload = this.assemblePayloadWithMeasurements(measurements, info, reason, clearSubsession); if (!IS_CONTENT_PROCESS && clearSubsession) { this.startNewSubsession(); // Persist session data to disk (don't wait until it completes). let sessionData = this._getSessionDataObject(); this._stateSaveSerializer.enqueueTask(() => this._saveSessionData(sessionData));
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js @@ -659,17 +659,17 @@ add_task(function* test_saveLoadPing() { } else { checkPingFormat(ping1, PING_TYPE_SAVED_SESSION, true, true); checkPayload(ping1.payload, REASON_SAVED_SESSION, 1); checkPingFormat(ping2, PING_TYPE_MAIN, true, true); checkPayload(ping2.payload, REASON_TEST_PING, 1); } }); -add_task(function* test_checkSubsession() { +add_task(function* test_checkSubsessionHistograms() { if (gIsAndroid) { // We don't support subsessions yet on Android. return; } let now = new Date(2020, 1, 1, 12, 0, 0); let expectedDate = new Date(2020, 1, 1, 0, 0, 0); fakeNow(now); @@ -847,16 +847,65 @@ add_task(function* test_checkSubsession( Assert.ok(KEYED_ID in classic.keyedHistograms); Assert.ok(KEYED_ID in subsession.keyedHistograms); Assert.equal(classic.keyedHistograms[KEYED_ID]["a"].sum, 2); Assert.equal(classic.keyedHistograms[KEYED_ID]["b"].sum, 2); Assert.equal(subsession.keyedHistograms[KEYED_ID]["a"].sum, 1); Assert.equal(subsession.keyedHistograms[KEYED_ID]["b"].sum, 1); }); +add_task(function* test_checkSubsessionData() { + if (gIsAndroid || !SESSION_RECORDER_EXPECTED) { + // We don't support subsessions yet on Android. Also bail out if we + // can't use the session recorder. + return; + } + + // Keep track of the active ticks count if the session recorder is available. + let sessionRecorder = gDatareportingService.getSessionRecorder(); + let activeTicksAtSubsessionStart = sessionRecorder.activeTicks; + let expectedActiveTicks = activeTicksAtSubsessionStart; + + incrementActiveTicks = () => { + sessionRecorder.incrementActiveTicks(); + ++expectedActiveTicks; + } + + yield TelemetrySession.reset(); + + // Both classic and subsession payload data should be the same on the first subsession. + incrementActiveTicks(); + let classic = TelemetrySession.getPayload(); + let subsession = TelemetrySession.getPayload("environment-change"); + Assert.equal(classic.simpleMeasurements.activeTicks, expectedActiveTicks, + "Classic pings must count active ticks since the beginning of the session."); + Assert.equal(subsession.simpleMeasurements.activeTicks, expectedActiveTicks, + "Subsessions must count active ticks as classic pings on the first subsession."); + + // Start a new subsession and check that the active ticks are correctly reported. + incrementActiveTicks(); + activeTicksAtSubsessionStart = sessionRecorder.activeTicks; + classic = TelemetrySession.getPayload(); + subsession = TelemetrySession.getPayload("environment-change", true); + Assert.equal(classic.simpleMeasurements.activeTicks, expectedActiveTicks, + "Classic pings must count active ticks since the beginning of the session."); + Assert.equal(subsession.simpleMeasurements.activeTicks, expectedActiveTicks, + "Pings must not loose the tick count when starting a new subsession."); + + // Get a new subsession payload without clearing the subsession. + incrementActiveTicks(); + classic = TelemetrySession.getPayload(); + subsession = TelemetrySession.getPayload("environment-change"); + Assert.equal(classic.simpleMeasurements.activeTicks, expectedActiveTicks, + "Classic pings must count active ticks since the beginning of the session."); + Assert.equal(subsession.simpleMeasurements.activeTicks, + expectedActiveTicks - activeTicksAtSubsessionStart, + "Subsessions must count active ticks since the last new subsession."); +}); + add_task(function* test_dailyCollection() { if (gIsAndroid) { // We don't do daily collections yet on Android. return; } let now = new Date(2030, 1, 1, 12, 0, 0); let nowDay = new Date(2030, 1, 1, 0, 0, 0);
--- a/toolkit/devtools/server/actors/common.js +++ b/toolkit/devtools/server/actors/common.js @@ -379,21 +379,22 @@ exports.OriginalLocation = OriginalLocat * * @param SourceActor actor * A SourceActor representing a generated source. * @param Number line * A line within the given source. * @param Number column * A column within the given line. */ -function GeneratedLocation(actor, line, column) { +function GeneratedLocation(actor, line, column, lastColumn) { this._connection = actor ? actor.conn : null; this._actorID = actor ? actor.actorID : undefined; this._line = line; this._column = column; + this._lastColumn = (lastColumn !== undefined) ? lastColumn : column + 1; } GeneratedLocation.fromOriginalLocation = function (originalLocation) { return new GeneratedLocation( originalLocation.originalSourceActor, originalLocation.originalLine, originalLocation.originalColumn ); @@ -425,16 +426,34 @@ GeneratedLocation.prototype = { }, get generatedLine() { return this._line; }, get generatedColumn() { return this._column; + }, + + get generatedLastColumn() { + return this._lastColumn; + }, + + equals: function (other) { + return this.generatedSourceActor.url == other.generatedSourceActor.url && + this.generatedLine === other.originalLine; + }, + + toJSON: function () { + return { + source: this.generatedSourceActor.form(), + line: this.generatedLine, + column: this.generatedColumn, + lastColumn: this.generatedLastColumn + }; } }; exports.GeneratedLocation = GeneratedLocation; // TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when it is // implemented. exports.getOffsetColumn = function getOffsetColumn(aOffset, aScript) {
--- a/toolkit/devtools/server/actors/script.js +++ b/toolkit/devtools/server/actors/script.js @@ -2816,17 +2816,17 @@ SourceActor.prototype = { * * @param BreakpointActor actor * The BreakpointActor to be set as a breakpoint handler. * * @returns A Promise that resolves to the given BreakpointActor. */ _setBreakpoint: function (actor) { let { originalLocation } = actor; - let { originalColumn } = originalLocation; + let { originalSourceActor, originalLine, originalColumn } = originalLocation; return this._setBreakpointAtOriginalLocation(actor, originalLocation) .then((actualLocation) => { if (actualLocation) { return actualLocation; } if (!this.isSourceMapped) { @@ -2862,32 +2862,26 @@ SourceActor.prototype = { entryPoints = []; lineToEntryPointsMap[line] = entryPoints; } entryPoints.push({ script, offsets }); } } } - let { - originalSourceActor, - originalLine, - originalColumn - } = originalLocation; - // Now that we have a map from line numbers to a list of entry points // for each line, we can use it to perform breakpoint sliding. Start // at the original line of the breakpoint actor, and keep incrementing // it by one, until either we find a line that has at least one entry // point, or we go past the last line in the map. // // Note that by computing the entire map up front, and implementing it // as a sparse array, we can easily tell when we went past the last line // in the map. - let actualLine = originalLine; + let actualLine = originalLine + 1; while (actualLine < lineToEntryPointsMap.length) { let entryPoints = lineToEntryPointsMap[actualLine]; if (entryPoints) { setBreakpointAtEntryPoints(actor, entryPoints); break; } ++actualLine; } @@ -2903,25 +2897,51 @@ SourceActor.prototype = { originalSourceActor, actualLine ); } else { // TODO: Implement breakpoint sliding for column breakpoints return originalLocation; } } else { - // TODO: Refactor breakpoint sliding for source mapped sources. - return this.threadActor.sources.getGeneratedLocation(originalLocation) - .then((generatedLocation) => { - return generatedLocation.generatedSourceActor - ._setBreakpointAtLocationWithSliding( - actor, - generatedLocation - ); - }); + if (originalColumn === undefined) { + let loop = (actualLocation) => { + let { + originalLine: actualLine, + originalColumn: actualColumn + } = actualLocation; + + return this.threadActor.sources.getAllGeneratedLocations(actualLocation) + .then((generatedLocations) => { + // Because getAllGeneratedLocations will always return the list of + // generated locations for the closest line that is greater than + // the one we are searching for if no exact match can be found, if + // the list of generated locations is empty, we've reached the end + // of the original source, and breakpoint sliding failed. + if (generatedLocations.length === 0) { + return originalLocation; + } + + // If at least one script has an offset that matches one of the + // generated locations in the list, then breakpoint sliding + // succeeded. + if (this._setBreakpointAtAllGeneratedLocations(actor, generatedLocations)) { + return this.threadActor.sources.getOriginalLocation(generatedLocations[0]); + }