Merge m-c to inbound. a=merge

CLOSED TREE
This commit is contained in:
Ryan VanderMeulen 2015-03-27 11:17:04 -04:00
commit 43bcefb0f7
142 changed files with 2875 additions and 1603 deletions

View File

@ -15,7 +15,7 @@
<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"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="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"/>
@ -115,10 +115,10 @@
<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"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="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"/>

View File

@ -17,10 +17,10 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="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 -->
@ -117,10 +117,10 @@
<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"/>

View File

@ -15,7 +15,7 @@
<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"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="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"/>
@ -115,10 +115,10 @@
<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"/>

View File

@ -15,7 +15,7 @@
<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"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="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"/>
@ -127,9 +127,9 @@
<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"/>

View File

@ -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
}

View File

@ -0,0 +1,9 @@
[
{
"size": 80458572,
"digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
"algorithm": "sha512",
"filename": "gcc.tar.xz",
"unpack": "True"
}
]

View File

@ -0,0 +1 @@
../emulator-kk/sources.xml

View File

@ -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
}

View File

@ -0,0 +1,9 @@
[
{
"size": 80458572,
"digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
"algorithm": "sha512",
"filename": "gcc.tar.xz",
"unpack": "True"
}
]

View File

@ -0,0 +1 @@
../emulator-l/sources.xml

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="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"/>

View File

@ -15,7 +15,7 @@
<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"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="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"/>
@ -109,9 +109,9 @@
<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"/>

View File

@ -17,10 +17,10 @@
</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 -->

View File

@ -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"
}

View File

@ -17,10 +17,10 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="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 -->
@ -117,10 +117,10 @@
<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"/>

View File

@ -15,7 +15,7 @@
<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"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="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"/>
@ -127,9 +127,9 @@
<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"/>

View File

@ -1871,11 +1871,6 @@ pref("dom.ipc.reportProcessHangs", false);
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);

View File

@ -2100,53 +2100,81 @@ function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy)
} 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);
/**
* 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");
}
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;
}
return Task.spawn(function* () {
let mayInheritPrincipal = false;
let postData = null;
let shortcutURL = null;
let keyword = url;
let param = "";
[shortcutURL, postData] =
PlacesUtils.getURLAndPostDataForKeyword(keyword);
let offset = url.indexOf(" ");
if (offset > 0) {
keyword = url.substr(0, offset);
param = url.substr(offset + 1);
}
if (!shortcutURL) {
aCallback({ postData: postData, url: aURL,
mayInheritPrincipal: mayInheritPrincipal });
return;
}
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 escapedPostData = "";
if (postData)
escapedPostData = unescape(postData);
let entry = yield PlacesUtils.keywords.fetch(keyword);
if (entry) {
shortcutURL = entry.url.href;
postData = entry.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 (!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);
}
}
let continueOperation = function () {
// 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
@ -2169,40 +2197,29 @@ function getShortcutOrURIAndPostData(aURL, aCallback) {
// document's principal.
mayInheritPrincipal = true;
aCallback({ postData: postData, url: shortcutURL,
mayInheritPrincipal: mayInheritPrincipal });
return { postData, url: shortcutURL, 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;
if (param) {
// This keyword doesn't take a parameter, but one was provided. Just return
// the original URL.
postData = null;
return { postData, url, mayInheritPrincipal };
}
aCallback({ postData: postData, url: aURL,
mayInheritPrincipal: mayInheritPrincipal });
} else {
// 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) {
@ -3252,7 +3269,7 @@ var newTabButtonObserver = {
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);
@ -3272,7 +3289,7 @@ var newWindowButtonObserver = {
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);
@ -5608,7 +5625,7 @@ function middleMousePaste(event) {
lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
}
getShortcutOrURIAndPostData(clipboard, data => {
getShortcutOrURIAndPostData(clipboard).then(data => {
try {
makeURI(data.url);
} catch (ex) {
@ -5645,7 +5662,7 @@ 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);

View File

@ -190,9 +190,9 @@ let gGrid = {
// 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;
}

View File

@ -13,34 +13,34 @@ function* promise_first_result(inputText) {
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.
@ -50,9 +50,9 @@ add_task(function*() {
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];

View File

@ -1,19 +1,19 @@
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 itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
NetUtil.newURI("http://example.com/?q=%s"),
PlacesUtils.bookmarks.DEFAULT_INDEX,
"test");
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword");
let ucpref = Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete");
Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true);
registerCleanupFunction(() => {
PlacesUtils.bookmarks.removeItem(itemId);
Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", ucpref);
});
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(function* () {
yield PlacesUtils.bookmarks.remove(bm);
});
yield promiseAutocompleteResultPopup("keyword search");
@ -21,12 +21,12 @@ add_task(function*() {
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" });

View File

@ -3,10 +3,11 @@
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}");

View File

@ -4,7 +4,12 @@
**/
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",
@ -20,7 +25,6 @@ add_task(function* () {
Services.search.currentEngine = originalEngine;
let engine = Services.search.getEngineByName("MozSearch");
Services.search.removeEngine(engine);
Services.prefs.clearUserPref("browser.urlbar.unifiedcomplete");
try {
gBrowser.removeTab(tab);

View File

@ -3,10 +3,11 @@
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);

View File

@ -10,12 +10,15 @@ function is_selected(index) {
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 => {

View File

@ -2,17 +2,16 @@ 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));

View File

@ -3,10 +3,11 @@
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);

View File

@ -15,8 +15,7 @@ add_task(function*() {
return;
}
registerCleanupFunction(() => PlacesTestUtils.clearHistory());
yield PlacesTestUtils.clearHistory();
let visits = [];
repeat(10, i => {
visits.push({
@ -25,13 +24,18 @@ add_task(function*() {
});
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");

View File

@ -4,10 +4,11 @@
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";

View File

@ -7,29 +7,32 @@ function is_selected(index) {
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);

View File

@ -100,8 +100,7 @@ add_task(function* test_getshortcutoruri() {
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);

View File

@ -16,7 +16,9 @@ function is_selected(index) {
}
add_task(function*() {
registerCleanupFunction(() => PlacesTestUtils.clearHistory());
registerCleanupFunction(function* () {
yield PlacesTestUtils.clearHistory();
});
yield PlacesTestUtils.clearHistory();
let tabCount = gBrowser.tabs.length;

View File

@ -450,7 +450,7 @@
}
}
getShortcutOrURIAndPostData(url, data => {
getShortcutOrURIAndPostData(url).then(data => {
aCallback([data.url, data.postData, data.mayInheritPrincipal]);
});
]]></body>

View File

@ -1661,7 +1661,7 @@ BrowserGlue.prototype = {
},
_migrateUI: function BG__migrateUI() {
const UI_VERSION = 28;
const UI_VERSION = 29;
const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
let currentUIVersion = 0;
try {
@ -2016,6 +2016,21 @@ BrowserGlue.prototype = {
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);
},

View File

@ -1,4 +1,3 @@
/* -*- 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/. */
@ -232,6 +231,20 @@ var gPermissionManager = {
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);

View File

@ -53,10 +53,10 @@
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>

View File

@ -33,6 +33,12 @@ XPCOMUtils.defineLazyModuleGetter(this, 'ReadingList',
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"];
@ -75,6 +81,7 @@ function InternalScheduler(readingList = null) {
"browserwindow.syncui",
"FirefoxAccounts",
"readinglist.api",
"readinglist.scheduler",
"readinglist.serverclient",
"readinglist.sync",
];
@ -175,6 +182,8 @@ InternalScheduler.prototype = {
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
@ -285,10 +294,24 @@ InternalScheduler.prototype = {
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;

View File

@ -278,8 +278,13 @@ SyncImpl.prototype = {
}
// 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;
}
@ -404,7 +409,13 @@ SyncImpl.prototype = {
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});
}
}
}),
@ -442,7 +453,12 @@ SyncImpl.prototype = {
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});
}
}),
/**
@ -458,7 +474,12 @@ SyncImpl.prototype = {
// 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
@ -466,7 +487,12 @@ SyncImpl.prototype = {
// 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});
}
}),
/**

View File

@ -57,8 +57,11 @@ 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;
@ -89,14 +92,19 @@ let DirectoryLinksProvider = {
_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,
@ -201,11 +209,11 @@ let DirectoryLinksProvider = {
}
},
_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);
}
},
@ -325,6 +333,23 @@ let DirectoryLinksProvider = {
* @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 {
@ -413,9 +438,10 @@ let DirectoryLinksProvider = {
*/
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
@ -435,16 +461,22 @@ let DirectoryLinksProvider = {
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 [];
@ -476,32 +508,32 @@ let DirectoryLinksProvider = {
},
_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;
@ -517,7 +549,7 @@ let DirectoryLinksProvider = {
// Make sure NewTabUtils.links handles the notification first.
setTimeout(() => {
if (this._handleLinkChanged(aLink)) {
this._updateRelatedTile();
this._updateSuggestedTile();
}
}, 0);
},
@ -530,12 +562,27 @@ let DirectoryLinksProvider = {
},
/**
* Chooses and returns a related tile based on a user's top sites
* that we have an available related tile for.
*
* @return the chosen related tile, or undefined if there isn't one
* 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
*/
_updateRelatedTile: function() {
_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 suggested tile, or undefined if there isn't one
*/
_updateSuggestedTile: function() {
let sortedLinks = NewTabUtils.getProviderLinks(this);
if (!sortedLinks) {
@ -544,67 +591,78 @@ let DirectoryLinksProvider = {
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;
},
/**
@ -624,11 +682,11 @@ let DirectoryLinksProvider = {
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);
}

View File

@ -26,6 +26,7 @@ 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);
@ -61,7 +62,7 @@ const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
let gLastRequestPath;
let relatedTile1 = {
let suggestedTile1 = {
url: "http://turbotax.com",
type: "affiliate",
lastVisitDate: 3,
@ -72,7 +73,7 @@ let relatedTile1 = {
"taxslayer.com"
]
};
let relatedTile2 = {
let suggestedTile2 = {
url: "http://irs.gov",
type: "affiliate",
lastVisitDate: 2,
@ -83,7 +84,7 @@ let relatedTile2 = {
"taxslayer.com"
]
};
let relatedTile3 = {
let suggestedTile3 = {
url: "http://hrblock.com",
type: "affiliate",
lastVisitDate: 1,
@ -94,7 +95,7 @@ let relatedTile3 = {
"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;
@ -214,11 +215,11 @@ function run_test() {
});
}
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();
@ -237,49 +238,49 @@ add_task(function test_updateRelatedTile() {
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) => {
@ -287,32 +288,32 @@ add_task(function test_updateRelatedTile() {
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");
@ -325,10 +326,10 @@ add_task(function test_updateRelatedTile() {
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;
@ -339,38 +340,38 @@ add_task(function test_updateRelatedTile() {
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) {
@ -383,45 +384,181 @@ add_task(function test_topSitesWithRelatedLinks() {
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;

View File

@ -228,6 +228,7 @@ BluetoothHfpManager::Cleanup()
void
BluetoothHfpManager::Reset()
{
mFirstCKPD = false;
// Phone & Device CIND
ResetCallArray();
// Clear Sco state
@ -1286,6 +1287,8 @@ 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.
@ -1613,7 +1616,7 @@ BluetoothHfpManager::KeyPressedNotification(const nsAString& aBdAddress)
// 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
@ -1622,13 +1625,22 @@ BluetoothHfpManager::KeyPressedNotification(const nsAString& aBdAddress)
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
@ -1636,6 +1648,7 @@ BluetoothHfpManager::KeyPressedNotification(const nsAString& aBdAddress)
*/
NotifyDialer(NS_LITERAL_STRING("CHUP"));
}
mFirstCKPD = false;
} else {
// BLDN
mDialingRequestProcessed = false;

View File

@ -209,6 +209,7 @@ private:
int mCurrentVgm;
bool mReceiveVgsFlag;
bool mDialingRequestProcessed;
bool mFirstCKPD;
PhoneType mPhoneType;
nsString mDeviceAddress;
nsString mMsisdn;

View File

@ -5,20 +5,6 @@
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;
@ -35,6 +21,7 @@ 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;
@ -57,18 +44,18 @@ 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;
@ -139,6 +126,20 @@ 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,
@ -905,6 +906,19 @@ public class BrowserApp extends GeckoApp
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();
@ -923,6 +937,8 @@ public class BrowserApp extends GeckoApp
EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener)this,
"Prompt:ShowTop");
processTabQueue();
}
@Override
@ -3380,6 +3396,7 @@ public class BrowserApp extends GeckoApp
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.
@ -3408,6 +3425,17 @@ public class BrowserApp extends GeckoApp
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;
}

View File

@ -22,6 +22,7 @@ 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,
@ -76,16 +77,12 @@ public class DoorHangerPopup extends AnchoredPopup
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")) {
@ -109,6 +106,22 @@ public class DoorHangerPopup extends AnchoredPopup
}
}
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) {
@ -149,15 +162,15 @@ public class DoorHangerPopup extends AnchoredPopup
*
* 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);
}
@ -166,10 +179,9 @@ public class DoorHangerPopup extends AnchoredPopup
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);
@ -230,7 +242,7 @@ public class DoorHangerPopup extends AnchoredPopup
*/
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;
}

View File

@ -27,6 +27,7 @@ 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;
@ -153,7 +154,7 @@ public final class GeckoProfile {
final String args;
if (context instanceof Activity) {
args = ((Activity) context).getIntent().getStringExtra("args");
args = ContextUtils.getStringExtra(((Activity) context).getIntent(), "args");
} else {
args = null;
}
@ -679,6 +680,14 @@ public final class GeckoProfile {
}
}
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) {

View File

@ -489,8 +489,10 @@ gbjar.sources += [
'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',
@ -503,6 +505,7 @@ gbjar.sources += [
'widget/GeckoSwipeRefreshLayout.java',
'widget/GeckoViewFlipper.java',
'widget/IconTabWidget.java',
'widget/LoginDoorHanger.java',
'widget/ResizablePathDrawable.java',
'widget/SquaredImageView.java',
'widget/SwipeDismissListViewTouchListener.java',

View File

@ -266,15 +266,17 @@ public class LocalReadingListStorage implements ReadingListStorage {
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;

View File

@ -482,7 +482,7 @@ public class ReadingListClient {
if (ReadingListConstants.DEBUG) {
Logger.info(LOG_TAG, "Patching record " + guid + ": " + body.toJSONString());
}
r.post(body);
r.patch(body);
}
/**

View File

@ -10,7 +10,7 @@ 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";

View File

@ -248,11 +248,10 @@ public class ReadingListSynchronizer {
}
if (failures == 0) {
try {
next.next();
} catch (Exception e) {
}
next.next();
return;
}
next.fail();
}
}
@ -305,6 +304,149 @@ public class ReadingListSynchronizer {
}
}
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<>();
@ -335,8 +477,11 @@ public class ReadingListSynchronizer {
// 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.
@ -368,6 +513,8 @@ public class ReadingListSynchronizer {
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();
@ -420,9 +567,79 @@ public class ReadingListSynchronizer {
}
}
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) {

View File

@ -28,4 +28,8 @@ public class ServerReadingListRecord extends ReadingListRecord {
public String getAddedBy() {
return this.fields.getString("added_by");
}
}
public String getExcerpt() {
return this.fields.getString("excerpt");
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -16,11 +16,11 @@
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>
@ -29,6 +29,7 @@
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"
@ -38,21 +39,22 @@
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>

View File

@ -4,7 +4,7 @@
- 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"

View File

@ -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>

View File

@ -9,8 +9,6 @@
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">
@ -18,6 +16,8 @@
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">

View File

@ -6,63 +6,74 @@
<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">
<LinearLayout android:id="@+id/site_identity_known_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/site_identity_unknown" />
<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"/>
<LinearLayout android:id="@+id/site_identity_known_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<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: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/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/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"
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_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/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/owner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/placeholder_active_grey"
android:textSize="16sp"
android:textStyle="bold"/>
</LinearLayout>
<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"/>
</FrameLayout>
</LinearLayout>
</FrameLayout>
</LinearLayout>
<View android:id="@+id/divider_doorhanger"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/divider_light"
android:visibility="gone"/>
</LinearLayout>

View File

@ -100,6 +100,10 @@
<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>

View File

@ -410,14 +410,21 @@
<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">
@ -817,7 +824,7 @@
</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>

View File

@ -30,6 +30,7 @@ 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;
@ -341,6 +342,14 @@ public class BaseResource implements Resource {
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());
@ -463,4 +472,16 @@ public class BaseResource implements Resource {
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));
}
}

View File

@ -15,5 +15,6 @@ public interface Resource {
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);
}

View File

@ -190,6 +190,11 @@ public class SyncStorageRequest implements Resource {
this.resource.post(body);
}
@Override
public void patch(HttpEntity body) {
this.resource.patch(body);
}
@Override
public void put(HttpEntity body) {
this.resource.put(body);

View File

@ -5,17 +5,26 @@
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();
@ -24,6 +33,8 @@ public class TabQueueHelper {
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.
@ -52,7 +63,9 @@ public class TabQueueHelper {
* @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);
@ -60,7 +73,7 @@ public class TabQueueHelper {
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 {
@ -69,12 +82,68 @@ public class TabQueueHelper {
}
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();
}
}

View File

@ -8,6 +8,7 @@ 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;
@ -22,6 +23,7 @@ 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;
@ -165,6 +167,13 @@ public class TabQueueService extends Service {
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();
}
});
}

View File

@ -5,7 +5,6 @@
package org.mozilla.gecko.tests;
public class StringHelper {
private StringHelper() {}
@ -55,6 +54,7 @@ public class StringHelper {
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";
@ -105,7 +105,8 @@ public class StringHelper {
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";
@ -263,9 +264,9 @@ public class StringHelper {
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";

View File

@ -1,8 +1,8 @@
<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>

View File

@ -0,0 +1,21 @@
<html>
<script>
function login(){
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_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>

View File

@ -78,12 +78,12 @@ public class testClearPrivateData extends PixelTest {
}
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));
}

View File

@ -24,7 +24,6 @@ public class testDoorHanger extends BaseTest {
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();
@ -63,7 +62,6 @@ public class testDoorHanger extends BaseTest {
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" };
@ -130,25 +128,24 @@ public class testDoorHanger extends BaseTest {
mAsserter.ok(false, "exception setting preference", e.toString());
}
// Load login page
loadUrlAndWait(LOGIN_URL);
// Load new login page
loadUrlAndWait(getAbsoluteUrl(StringHelper.ROBOCOP_LOGIN_01_URL));
waitForText(StringHelper.LOGIN_MESSAGE);
// Test doorhanger is dismissed when tapping "Don't save"
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
// 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(getAbsoluteUrl(StringHelper.ROBOCOP_LOGIN_02_URL));
waitForText(StringHelper.LOGIN_MESSAGE);
// 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");
testPopupBlocking();
}

View File

@ -180,7 +180,7 @@ public class testMasterPassword extends PixelTest {
}
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

View File

@ -27,6 +27,7 @@ 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
@ -53,6 +54,8 @@ public class SiteIdentityPopup extends AnchoredPopup {
private TextView mOwner;
private TextView mVerifier;
private View mDivider;
private DoorHanger mMixedContentNotification;
private DoorHanger mTrackingContentNotification;
@ -85,6 +88,7 @@ public class SiteIdentityPopup extends AnchoredPopup {
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) {
@ -137,26 +141,27 @@ public class SiteIdentityPopup extends AnchoredPopup {
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() {
@ -169,27 +174,30 @@ public class SiteIdentityPopup extends AnchoredPopup {
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() {
@ -200,6 +208,7 @@ public class SiteIdentityPopup extends AnchoredPopup {
}
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);
@ -276,6 +285,7 @@ public class SiteIdentityPopup extends AnchoredPopup {
super.dismiss();
removeMixedContentNotification();
removeTrackingContentNotification();
mDivider.setVisibility(View.GONE);
}
private class PopupButtonListener implements OnButtonClickListener {

View File

@ -0,0 +1,132 @@
/* -*- 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.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.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import java.util.ArrayList;
import java.util.List;
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;
public DefaultDoorHanger(Context context, DoorhangerConfig config) {
this(context, config, Type.DEFAULT);
}
public DefaultDoorHanger(Context context, DoorhangerConfig config, Type type) {
super(context, config, type);
mResources = getResources();
if (sSpinnerTextColor == -1) {
sSpinnerTextColor = getResources().getColor(R.color.text_color_primary_disable_only);
}
loadConfig(config);
}
@Override
protected void loadConfig(DoorhangerConfig config) {
final String message = config.getMessage();
if (message != null) {
setMessage(message);
}
final JSONObject options = config.getOptions();
if (options != null) {
setOptions(options);
}
final DoorhangerConfig.Link link = config.getLink();
if (link != null) {
addLink(link.label, link.url, link.delimiter);
}
}
@Override
public List<PromptInput> getInputs() {
return mInputs;
}
@Override
public CheckBox getCheckBox() {
return mCheckBox;
}
@Override
public void setOptions(final JSONObject options) {
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) { }
}
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);
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) {
styleDropdownInputs(input, view);
}
view.setPadding(0, 0, 0, mResources.getDimensionPixelSize(R.dimen.doorhanger_padding));
}
private void styleDropdownInputs(PromptInput input, View view) {
PromptInput.MenulistInput spinInput = (PromptInput.MenulistInput) input;
if (spinInput.textView != null) {
spinInput.textView.setTextColor(sSpinnerTextColor);
}
}
}

View File

@ -5,152 +5,128 @@
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;
private List<PromptInput> mInputs;
private CheckBox mCheckBox;
protected Context mContext;
private int mPersistence;
private boolean mPersistWhileVisible;
private long mTimeout;
protected int mDividerColor;
// Color used for dividers above and between buttons.
private int mDividerColor;
protected boolean mPersistWhileVisible;
protected int mPersistenceCount;
protected long mTimeout;
public static enum Theme {
LIGHT,
DARK
}
public interface OnButtonClickListener {
public void onButtonClick(DoorHanger dh, String tag);
}
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);
}
private DoorHanger(Context context, int tabId, String value, Theme theme) {
protected DoorHanger(Context context, DoorhangerConfig config, Type type) {
super(context);
mContext = context;
mTabId = config.getTabId();
mIdentifier = config.getId();
mTabId = tabId;
mValue = value;
mResources = getResources();
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);
int resource;
switch (type) {
case LOGIN:
resource = R.layout.login_doorhanger;
break;
default:
resource = R.layout.doorhanger;
}
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);
LayoutInflater.from(context).inflate(resource, this);
mDivider = findViewById(R.id.divider_doorhanger);
setTheme(theme);
}
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);
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);
mDividerColor = getResources().getColor(R.color.divider_light);
setOrientation(VERTICAL);
}
abstract protected void loadConfig(DoorhangerConfig config);
protected void setOptions(final JSONObject options) {
final int persistence = options.optInt("persistence");
if (persistence > 0) {
mPersistenceCount = persistence;
}
mPersistWhileVisible = options.optBoolean("persistWhileVisible");
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() {
@ -161,19 +137,18 @@ public class DoorHanger extends LinearLayout {
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
@ -183,12 +158,12 @@ public class DoorHanger extends LinearLayout {
};
// 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) {
@ -203,126 +178,23 @@ public class DoorHanger extends LinearLayout {
}
});
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);
mButtonsContainer.addView(divider);
}
mChoicesLayout.addView(button, sButtonParams);
mButtonsContainer.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) { }
}
}
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);
}
}
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.
*
@ -332,15 +204,15 @@ public class DoorHanger extends LinearLayout {
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;
}
@ -350,4 +222,14 @@ public class DoorHanger extends LinearLayout {
return true;
}
// TODO: remove and expose through instance Button Handler.
public List<PromptInput> getInputs() {
return null;
}
public CheckBox getCheckBox() {
return null;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -136,7 +136,8 @@ let Passwords = {
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) => {
@ -152,6 +153,21 @@ let Passwords = {
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);
}
});
}
});

View File

@ -2213,11 +2213,24 @@ var NativeWindow = {
* 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 = [];
}
@ -2236,7 +2249,8 @@ var NativeWindow = {
buttons: aButtons,
// use the current tab if none is provided
tabID: aTabID || BrowserApp.selectedTab.id,
options: aOptions || {}
options: aOptions || {},
category: aCategory
};
Messaging.sendRequest(json);
},

View File

@ -63,8 +63,12 @@ LoginManagerPrompter.prototype = {
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!";
}
@ -136,9 +140,19 @@ LoginManagerPrompter.prototype = {
* _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;
@ -155,12 +169,14 @@ LoginManagerPrompter.prototype = {
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");
},
@ -173,15 +189,16 @@ LoginManagerPrompter.prototype = {
*
*/
_showSaveLoginNotification : function (aLogin) {
var displayHost = this._getShortDisplayHost(aLogin.hostname);
var notificationText;
if (aLogin.username) {
var displayUser = this._sanitizeUsername(aLogin.username);
notificationText = this._getLocalizedString("savePassword", [displayUser, displayHost]);
} else {
notificationText = this._getLocalizedString("savePasswordNoUser", [displayHost]);
}
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) {
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.
@ -190,22 +207,22 @@ LoginManagerPrompter.prototype = {
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);
},
/*
@ -236,6 +253,9 @@ LoginManagerPrompter.prototype = {
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.
@ -243,23 +263,23 @@ LoginManagerPrompter.prototype = {
let promptHistogram = Services.telemetry.getHistogramById("PWMGR_PROMPT_UPDATE_ACTION");
var buttons = [
{
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
}
},
{
label: this._getLocalizedString("updateButton"),
callback: function() {
self._updateLogin(aOldLogin, aNewPassword);
promptHistogram.add(PROMPT_UPDATE);
}
}
];
this._showLoginNotification("password-change", notificationText, buttons);
this._showLoginNotification("password-change", title, notificationText, buttons);
},
@ -377,10 +397,10 @@ LoginManagerPrompter.prototype = {
*/
_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);
},

View File

@ -88,6 +88,7 @@ SessionStore.prototype = {
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");
@ -157,6 +158,11 @@ SessionStore.prototype = {
}
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
@ -910,6 +916,21 @@ SessionStore.prototype = {
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");

View File

@ -5,6 +5,11 @@
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

View File

@ -197,18 +197,17 @@ body {
.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 */

View File

@ -2,13 +2,10 @@
# 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?

View File

@ -10,6 +10,7 @@ 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

View File

@ -1 +1 @@
0.2.9
0.2.10

View File

@ -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

View File

@ -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

View File

@ -28,7 +28,11 @@ if [ ! -d mozharness ]; then
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...

View File

@ -9,6 +9,7 @@ flags:
- emulator
- emulator-jb
- emulator-kk
- emulator-x86-kk
- emulator-l
- linux32_gecko # b2g desktop linux 32 bit
- linux64_gecko # b2g desktop linux 64 bit

View File

@ -36,6 +36,18 @@ builds:
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
@ -134,6 +146,10 @@ tests:
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:
@ -150,6 +166,10 @@ tests:
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:
@ -158,6 +178,8 @@ tests:
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:

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -14,6 +14,9 @@ task:
TARGET: 'flame-kk'
DEBUG: 0
VARIANT: eng
GAIA_OPTIMIZE: '1'
B2G_SYSTEM_APPS: '1'
B2G_UPDATER: '1'
command:
- >
checkout-gecko workspace &&
@ -28,8 +31,3 @@ task:
platform: b2g-device-image
locations:
img: 'private/build/flame-kk.zip'
GAIA_OPTIMIZE: '1'
B2G_SYSTEM_APPS: '1'
B2G_UPDATER: '1'

View File

@ -8,6 +8,7 @@ flags:
- 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

View File

@ -1,6 +1,7 @@
---
$inherits:
from: 'tasks/test.yml'
reruns: 2
task:
metadata:
name: '[TC] - Gaia Build Unit Test'

View File

@ -1,6 +1,7 @@
---
$inherits:
from: 'tasks/test.yml'
reruns: 2
task:
metadata:
name: '[TC] - Gaia JS Integration Test'

Some files were not shown because too many files have changed in this diff Show More