зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to fx-team
This commit is contained in:
Коммит
0fc1cfad3f
|
@ -236,7 +236,7 @@ two words
|
|||
<ul id="ul1">
|
||||
<li id="li1">Item</li>
|
||||
<li id="li2"></li>
|
||||
<li id="li3" style="width:10ex; font-family:monospace; font-size:10pt;">a long and winding road that lead me to your door</li>
|
||||
<li id="li3" style="font-family:monospace; font-size:10pt; width:8ch;">a long and winding road that lead me to your door</li>
|
||||
<li id="li4">a <a href=''>b</a> c</li>
|
||||
<li id="li5"><br>hello</li>
|
||||
</ul>
|
||||
|
@ -244,7 +244,7 @@ two words
|
|||
<ol id="ol1">
|
||||
<li id="li6">Item</li>
|
||||
<li id="li7"></li>
|
||||
<li id="li8" style="width:10ex; font-family:monospace; font-size:10pt;">a long and winding road that lead me to your door</li>
|
||||
<li id="li8" style="font-family:monospace; font-size:10pt; width:8ch;">a long and winding road that lead me to your door</li>
|
||||
<li id="li9">a <a href=''>b</a> c</li>
|
||||
<li id="li10"><br>hello</li>
|
||||
</ol>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<!--
|
||||
B2G repositories for all targets
|
||||
-->
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6164cd7af92ec2a3422d48f17f9577fc9b3f7ff4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||
|
@ -35,7 +35,7 @@
|
|||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="9eb1475f0ec4b6512cf43206845b504d61c989b2"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
|
||||
<!-- 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="0f86914b89cf8a069533e66b218533a17bad6b43"/>
|
||||
<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="6b1fb5b730b1299f99f9194c1fcf088579cc7977"/>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<!--
|
||||
B2G repositories for all targets
|
||||
-->
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6164cd7af92ec2a3422d48f17f9577fc9b3f7ff4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||
|
@ -35,7 +35,7 @@
|
|||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="9eb1475f0ec4b6512cf43206845b504d61c989b2"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
|
||||
<!-- 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"/>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<!--
|
||||
B2G repositories for all targets
|
||||
-->
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6164cd7af92ec2a3422d48f17f9577fc9b3f7ff4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||
|
@ -35,7 +35,7 @@
|
|||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="9eb1475f0ec4b6512cf43206845b504d61c989b2"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
|
||||
<!-- 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"/>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<!--
|
||||
B2G repositories for all targets
|
||||
-->
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6164cd7af92ec2a3422d48f17f9577fc9b3f7ff4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<!--
|
||||
B2G repositories for all targets
|
||||
-->
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6164cd7af92ec2a3422d48f17f9577fc9b3f7ff4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||
|
@ -31,7 +31,7 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="9eb1475f0ec4b6512cf43206845b504d61c989b2"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
|
||||
<!-- Stock Android things -->
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<!--
|
||||
B2G repositories for all targets
|
||||
-->
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6164cd7af92ec2a3422d48f17f9577fc9b3f7ff4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||
|
@ -34,7 +34,7 @@
|
|||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="9eb1475f0ec4b6512cf43206845b504d61c989b2"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
|
||||
<!-- 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"/>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<!--
|
||||
B2G repositories for all targets
|
||||
-->
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6164cd7af92ec2a3422d48f17f9577fc9b3f7ff4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||
|
@ -34,7 +34,7 @@
|
|||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="9eb1475f0ec4b6512cf43206845b504d61c989b2"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
|
||||
<!-- Stock Android things -->
|
||||
<project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
|
||||
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<!--
|
||||
B2G repositories for all targets
|
||||
-->
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6164cd7af92ec2a3422d48f17f9577fc9b3f7ff4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<!--
|
||||
B2G repositories for all targets
|
||||
-->
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6164cd7af92ec2a3422d48f17f9577fc9b3f7ff4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||
|
@ -35,7 +35,7 @@
|
|||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="9eb1475f0ec4b6512cf43206845b504d61c989b2"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
|
||||
<!-- Stock Android things -->
|
||||
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
|
||||
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"git": {
|
||||
"git_revision": "6164cd7af92ec2a3422d48f17f9577fc9b3f7ff4",
|
||||
"git_revision": "43852628a9d506c65525cceb5789b257cc939fe8",
|
||||
"remote": "https://git.mozilla.org/releases/gaia.git",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "5dec1b4e9d9c827e70b3f2018f1857b8ed1e1068",
|
||||
"revision": "c1f3c74a949f3d10f802aae79a705d440b08b091",
|
||||
"repo_path": "integration/gaia-central"
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<!--
|
||||
B2G repositories for all targets
|
||||
-->
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6164cd7af92ec2a3422d48f17f9577fc9b3f7ff4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||
|
@ -35,7 +35,7 @@
|
|||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="9eb1475f0ec4b6512cf43206845b504d61c989b2"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
|
||||
<!-- 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"/>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<!--
|
||||
B2G repositories for all targets
|
||||
-->
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6164cd7af92ec2a3422d48f17f9577fc9b3f7ff4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||
|
@ -32,7 +32,7 @@
|
|||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="9eb1475f0ec4b6512cf43206845b504d61c989b2"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
|
||||
<!-- Stock Android things -->
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<!--
|
||||
B2G repositories for all targets
|
||||
-->
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6164cd7af92ec2a3422d48f17f9577fc9b3f7ff4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
|
||||
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
|
||||
|
@ -35,7 +35,7 @@
|
|||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="9eb1475f0ec4b6512cf43206845b504d61c989b2"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
|
||||
<!-- Stock Android things -->
|
||||
<project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
|
||||
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>
|
||||
|
|
|
@ -401,12 +401,34 @@ nsScriptSecurityManager::GetChannelURIPrincipal(nsIChannel* aChannel,
|
|||
nsCOMPtr<nsILoadContext> loadContext;
|
||||
NS_QueryNotificationCallbacks(aChannel, loadContext);
|
||||
|
||||
if (loadContext) {
|
||||
return GetLoadContextCodebasePrincipal(uri, loadContext, aPrincipal);
|
||||
nsCOMPtr<nsILoadInfo> loadInfo;
|
||||
aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
|
||||
nsContentPolicyType contentPolicyType = nsIContentPolicy::TYPE_INVALID;
|
||||
if (loadInfo) {
|
||||
contentPolicyType = loadInfo->GetExternalContentPolicyType();
|
||||
}
|
||||
|
||||
PrincipalOriginAttributes attrs;
|
||||
if (nsIContentPolicy::TYPE_DOCUMENT == contentPolicyType ||
|
||||
nsIContentPolicy::TYPE_SUBDOCUMENT == contentPolicyType) {
|
||||
// If it's document or sub-document, inherit originAttributes from
|
||||
// the document.
|
||||
if (loadContext) {
|
||||
DocShellOriginAttributes docShellAttrs;
|
||||
loadContext->GetOriginAttributes(docShellAttrs);
|
||||
attrs.InheritFromDocShellToDoc(docShellAttrs, uri);
|
||||
}
|
||||
} else {
|
||||
// Inherit origin attributes from loading principal if any.
|
||||
nsCOMPtr<nsIPrincipal> loadingPrincipal;
|
||||
if (loadInfo) {
|
||||
loadInfo->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal));
|
||||
}
|
||||
if (loadingPrincipal) {
|
||||
attrs = BasePrincipal::Cast(loadingPrincipal)->OriginAttributesRef();
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Bug 1211590. inherit Origin Attributes from LoadInfo.
|
||||
PrincipalOriginAttributes attrs(UNKNOWN_APP_ID, false);
|
||||
rv = MaybeSetAddonIdFromURI(attrs, uri);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(uri, attrs);
|
||||
|
|
|
@ -4011,6 +4011,7 @@ nsDocShell::AddChild(nsIDocShellTreeItem* aChild)
|
|||
}
|
||||
|
||||
aChild->SetTreeOwner(mTreeOwner);
|
||||
childDocShell->SetUserContextId(mUserContextId);
|
||||
|
||||
nsCOMPtr<nsIDocShell> childAsDocShell(do_QueryInterface(aChild));
|
||||
if (!childAsDocShell) {
|
||||
|
@ -13858,6 +13859,17 @@ NS_IMETHODIMP
|
|||
nsDocShell::SetUserContextId(uint32_t aUserContextId)
|
||||
{
|
||||
mUserContextId = aUserContextId;
|
||||
|
||||
nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
|
||||
while (iter.HasMore()) {
|
||||
nsCOMPtr<nsIDocShell> docshell = do_QueryObject(iter.GetNext());
|
||||
if (!docshell || docshell->ItemType() != ItemType()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
docshell->SetUserContextId(aUserContextId);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -796,7 +796,6 @@ Animation::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
|
|||
// immediately before updating the style rule and then restore it immediately
|
||||
// afterwards. This is purely to prevent visual flicker. Other behavior
|
||||
// such as dispatching events continues to rely on the regular timeline time.
|
||||
bool updatedHoldTime = false;
|
||||
AnimationPlayState playState = PlayState();
|
||||
{
|
||||
AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime);
|
||||
|
@ -813,20 +812,12 @@ Animation::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
|
|||
if (!timeToUse.IsNull()) {
|
||||
mHoldTime.SetValue((timeToUse.Value() - mStartTime.Value())
|
||||
.MultDouble(mPlaybackRate));
|
||||
// Push the change down to the effect
|
||||
UpdateEffect();
|
||||
updatedHoldTime = true;
|
||||
}
|
||||
}
|
||||
|
||||
mEffect->ComposeStyle(aStyleRule, aSetProperties);
|
||||
}
|
||||
|
||||
// Now that the hold time has been restored, update the effect
|
||||
if (updatedHoldTime) {
|
||||
UpdateEffect();
|
||||
}
|
||||
|
||||
MOZ_ASSERT(playState == PlayState(),
|
||||
"Play state should not change during the course of compositing");
|
||||
mFinishedAtLastComposeStyle = (playState == AnimationPlayState::Finished);
|
||||
|
|
|
@ -565,6 +565,9 @@ EffectCompositor::ComposeAnimationRule(dom::Element* aElement,
|
|||
effect->GetAnimation()->ComposeStyle(animationRule, properties);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(effects == EffectSet::GetEffectSet(aElement, aPseudoType),
|
||||
"EffectSet should not change while composing style");
|
||||
|
||||
effects->UpdateAnimationRuleRefreshTime(aCascadeLevel, aRefreshTime);
|
||||
}
|
||||
|
||||
|
|
|
@ -1780,8 +1780,7 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
|
|||
// We need to delete the properties while we're still in document
|
||||
// (if we were in document).
|
||||
// FIXME (Bug 522599): Need a test for this.
|
||||
//XXXsmaug this looks slow.
|
||||
if (HasFlag(NODE_HAS_PROPERTIES)) {
|
||||
if (MayHaveAnimations()) {
|
||||
DeleteProperty(nsGkAtoms::transitionsOfBeforeProperty);
|
||||
DeleteProperty(nsGkAtoms::transitionsOfAfterProperty);
|
||||
DeleteProperty(nsGkAtoms::transitionsProperty);
|
||||
|
|
|
@ -8243,6 +8243,24 @@ nsContentUtils::InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal,
|
|||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPermissionManager> permissionManager =
|
||||
services::GetPermissionManager();
|
||||
if (!permissionManager) {
|
||||
return StorageAccess::eDeny;
|
||||
}
|
||||
|
||||
// check the permission manager for any allow or deny permissions
|
||||
// for cookies for the window.
|
||||
uint32_t perm;
|
||||
permissionManager->TestPermissionFromPrincipal(aPrincipal, "cookie", &perm);
|
||||
if (perm == nsIPermissionManager::DENY_ACTION) {
|
||||
return StorageAccess::eDeny;
|
||||
} else if (perm == nsICookiePermission::ACCESS_SESSION) {
|
||||
return std::min(access, StorageAccess::eSessionScoped);
|
||||
} else if (perm == nsIPermissionManager::ALLOW_ACTION) {
|
||||
return access;
|
||||
}
|
||||
|
||||
// Check if we should only allow storage for the session, and record that fact
|
||||
if (sCookiesLifetimePolicy == nsICookieService::ACCEPT_SESSION) {
|
||||
// Storage could be StorageAccess::ePrivateBrowsing or StorageAccess::eAllow
|
||||
|
@ -8284,24 +8302,6 @@ nsContentUtils::InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal,
|
|||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPermissionManager> permissionManager =
|
||||
services::GetPermissionManager();
|
||||
if (!permissionManager) {
|
||||
return StorageAccess::eDeny;
|
||||
}
|
||||
|
||||
// check the permission manager for any allow or deny permissions
|
||||
// for cookies for the window.
|
||||
uint32_t perm;
|
||||
permissionManager->TestPermissionFromPrincipal(aPrincipal, "cookie", &perm);
|
||||
if (perm == nsIPermissionManager::DENY_ACTION) {
|
||||
return StorageAccess::eDeny;
|
||||
} else if (perm == nsICookiePermission::ACCESS_SESSION) {
|
||||
return std::min(access, StorageAccess::eSessionScoped);
|
||||
} else if (perm == nsIPermissionManager::ALLOW_ACTION) {
|
||||
return access;
|
||||
}
|
||||
|
||||
// We don't want to prompt for every attempt to access permissions.
|
||||
if (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT) {
|
||||
return StorageAccess::eDeny;
|
||||
|
|
|
@ -1222,6 +1222,7 @@ GK_ATOM(tr, "tr")
|
|||
GK_ATOM(track, "track")
|
||||
GK_ATOM(trailing, "trailing")
|
||||
GK_ATOM(transform, "transform")
|
||||
GK_ATOM(transform_3d, "transform-3d")
|
||||
GK_ATOM(transformiix, "transformiix")
|
||||
GK_ATOM(translate, "translate")
|
||||
GK_ATOM(transparent, "transparent")
|
||||
|
|
|
@ -529,7 +529,7 @@ nsDOMCameraControl::TrackCreated(TrackID aTrackID) {
|
|||
// This track is not connected through a port.
|
||||
MediaInputPort* inputPort = nullptr;
|
||||
dom::VideoStreamTrack* track =
|
||||
new dom::VideoStreamTrack(this, aTrackID);
|
||||
new dom::VideoStreamTrack(this, aTrackID, nsString());
|
||||
RefPtr<TrackPort> port =
|
||||
new TrackPort(inputPort, track,
|
||||
TrackPort::InputPortOwnership::OWNED);
|
||||
|
|
|
@ -682,7 +682,7 @@ HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
stream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO);
|
||||
stream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO, nsString());
|
||||
|
||||
rv = RegisterFrameCaptureListener(stream->FrameCaptureListener());
|
||||
if (NS_FAILED(rv)) {
|
||||
|
|
|
@ -1890,11 +1890,11 @@ HTMLMediaElement::CaptureStreamInternal(bool aFinishWhenEnded,
|
|||
// Expose the tracks to JS directly.
|
||||
if (HasAudio()) {
|
||||
TrackID audioTrackId = mMediaInfo.mAudio.mTrackId;
|
||||
out->mStream->CreateOwnDOMTrack(audioTrackId, MediaSegment::AUDIO);
|
||||
out->mStream->CreateOwnDOMTrack(audioTrackId, MediaSegment::AUDIO, nsString());
|
||||
}
|
||||
if (HasVideo()) {
|
||||
TrackID videoTrackId = mMediaInfo.mVideo.mTrackId;
|
||||
out->mStream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO);
|
||||
out->mStream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO, nsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ namespace dom {
|
|||
|
||||
class AudioStreamTrack : public MediaStreamTrack {
|
||||
public:
|
||||
AudioStreamTrack(DOMMediaStream* aStream, TrackID aTrackID)
|
||||
: MediaStreamTrack(aStream, aTrackID) {}
|
||||
AudioStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel)
|
||||
: MediaStreamTrack(aStream, aTrackID, aLabel) {}
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ public:
|
|||
NS_WARN_IF_FALSE(!mStream->mTracks.IsEmpty(),
|
||||
"A new track was detected on the input stream; creating a corresponding MediaStreamTrack. "
|
||||
"Initial tracks should be added manually to immediately and synchronously be available to JS.");
|
||||
mStream->CreateOwnDOMTrack(aTrackId, aType);
|
||||
mStream->CreateOwnDOMTrack(aTrackId, aType, nsString());
|
||||
}
|
||||
|
||||
void DoNotifyTrackEnded(TrackID aTrackId)
|
||||
|
@ -619,7 +619,7 @@ DOMMediaStream::InitAudioCaptureStream(nsIDOMWindow* aWindow,
|
|||
InitInputStreamCommon(aGraph->CreateAudioCaptureStream(this, AUDIO_TRACK), aGraph);
|
||||
InitOwnedStreamCommon(aGraph);
|
||||
InitPlaybackStreamCommon(aGraph);
|
||||
CreateOwnDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO);
|
||||
CreateOwnDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO, nsString());
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -780,7 +780,7 @@ DOMMediaStream::RemovePrincipalChangeObserver(PrincipalChangeObserver* aObserver
|
|||
}
|
||||
|
||||
MediaStreamTrack*
|
||||
DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType)
|
||||
DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType, const nsString& aLabel)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(mInputStream);
|
||||
MOZ_RELEASE_ASSERT(mOwnedStream);
|
||||
|
@ -790,10 +790,10 @@ DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType)
|
|||
MediaStreamTrack* track;
|
||||
switch (aType) {
|
||||
case MediaSegment::AUDIO:
|
||||
track = new AudioStreamTrack(this, aTrackID);
|
||||
track = new AudioStreamTrack(this, aTrackID, aLabel);
|
||||
break;
|
||||
case MediaSegment::VIDEO:
|
||||
track = new VideoStreamTrack(this, aTrackID);
|
||||
track = new VideoStreamTrack(this, aTrackID, aLabel);
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH("Unhandled track type");
|
||||
|
|
|
@ -476,7 +476,7 @@ public:
|
|||
*
|
||||
* Creates a MediaStreamTrack, adds it to mTracks and returns it.
|
||||
*/
|
||||
MediaStreamTrack* CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType);
|
||||
MediaStreamTrack* CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType, const nsString& aLabel);
|
||||
|
||||
class OnTracksAvailableCallback {
|
||||
public:
|
||||
|
|
|
@ -1025,10 +1025,14 @@ public:
|
|||
msg);
|
||||
|
||||
if (mAudioDevice) {
|
||||
domStream->CreateOwnDOMTrack(kAudioTrack, MediaSegment::AUDIO);
|
||||
nsString audioDeviceName;
|
||||
mAudioDevice->GetName(audioDeviceName);
|
||||
domStream->CreateOwnDOMTrack(kAudioTrack, MediaSegment::AUDIO, audioDeviceName);
|
||||
}
|
||||
if (mVideoDevice) {
|
||||
domStream->CreateOwnDOMTrack(kVideoTrack, MediaSegment::VIDEO);
|
||||
nsString videoDeviceName;
|
||||
mVideoDevice->GetName(videoDeviceName);
|
||||
domStream->CreateOwnDOMTrack(kVideoTrack, MediaSegment::VIDEO, videoDeviceName);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID)
|
||||
: mOwningStream(aStream), mTrackID(aTrackID), mEnded(false), mEnabled(true)
|
||||
MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel)
|
||||
: mOwningStream(aStream), mTrackID(aTrackID), mLabel(aLabel), mEnded(false), mEnabled(true)
|
||||
{
|
||||
|
||||
nsresult rv;
|
||||
|
|
|
@ -29,7 +29,7 @@ public:
|
|||
* aTrackID is the MediaStreamGraph track ID for the track in the
|
||||
* MediaStream owned by aStream.
|
||||
*/
|
||||
MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID);
|
||||
MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel);
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamTrack,
|
||||
|
@ -54,7 +54,7 @@ public:
|
|||
// WebIDL
|
||||
virtual void GetKind(nsAString& aKind) = 0;
|
||||
void GetId(nsAString& aID) const;
|
||||
void GetLabel(nsAString& aLabel) { aLabel.Truncate(); }
|
||||
void GetLabel(nsAString& aLabel) { aLabel.Assign(mLabel); }
|
||||
bool Enabled() { return mEnabled; }
|
||||
void SetEnabled(bool aEnabled);
|
||||
void Stop();
|
||||
|
@ -75,6 +75,7 @@ protected:
|
|||
RefPtr<DOMMediaStream> mOwningStream;
|
||||
TrackID mTrackID;
|
||||
nsString mID;
|
||||
nsString mLabel;
|
||||
bool mEnded;
|
||||
bool mEnabled;
|
||||
};
|
||||
|
|
|
@ -14,8 +14,8 @@ namespace dom {
|
|||
|
||||
class VideoStreamTrack : public MediaStreamTrack {
|
||||
public:
|
||||
VideoStreamTrack(DOMMediaStream* aStream, TrackID aTrackID)
|
||||
: MediaStreamTrack(aStream, aTrackID) {}
|
||||
VideoStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel)
|
||||
: MediaStreamTrack(aStream, aTrackID, aLabel) {}
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
|
|
|
@ -29,74 +29,16 @@ mozilla::LazyLogModule gCamerasChildLog("CamerasChild");
|
|||
namespace mozilla {
|
||||
namespace camera {
|
||||
|
||||
// We emulate the sync webrtc.org API with the help of singleton
|
||||
// CamerasSingleton, which manages a pointer to an IPC object, a thread
|
||||
// where IPC operations should run on, and a mutex.
|
||||
// The static function Cameras() will use that Singleton to set up,
|
||||
// if needed, both the thread and the associated IPC objects and return
|
||||
// a pointer to the IPC object. Users can then do IPC calls on that object
|
||||
// after dispatching them to aforementioned thread.
|
||||
CamerasSingleton::CamerasSingleton()
|
||||
: mCamerasMutex("CamerasSingleton::mCamerasMutex"),
|
||||
mCameras(nullptr),
|
||||
mCamerasChildThread(nullptr) {
|
||||
LOG(("CamerasSingleton: %p", this));
|
||||
}
|
||||
|
||||
// 2 Threads are involved in this code:
|
||||
// - the MediaManager thread, which will call the (static, sync API) functions
|
||||
// through MediaEngineRemoteVideoSource
|
||||
// - the Cameras IPC thread, which will be doing our IPC to the parent process
|
||||
// via PBackground
|
||||
|
||||
// Our main complication is that we emulate a sync API while (having to do)
|
||||
// async messaging. We dispatch the messages to another thread to send them
|
||||
// async and hold a Monitor to wait for the result to be asynchronously received
|
||||
// again. The requirement for async messaging originates on the parent side:
|
||||
// it's not reasonable to block all PBackground IPC there while waiting for
|
||||
// something like device enumeration to complete.
|
||||
|
||||
class CamerasSingleton {
|
||||
public:
|
||||
CamerasSingleton()
|
||||
: mCamerasMutex("CamerasSingleton::mCamerasMutex"),
|
||||
mCameras(nullptr),
|
||||
mCamerasChildThread(nullptr) {
|
||||
LOG(("CamerasSingleton: %p", this));
|
||||
}
|
||||
|
||||
~CamerasSingleton() {
|
||||
LOG(("~CamerasSingleton: %p", this));
|
||||
}
|
||||
|
||||
static CamerasSingleton& GetInstance() {
|
||||
static CamerasSingleton instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
static OffTheBooksMutex& Mutex() {
|
||||
return GetInstance().mCamerasMutex;
|
||||
}
|
||||
|
||||
static CamerasChild*& Child() {
|
||||
GetInstance().Mutex().AssertCurrentThreadOwns();
|
||||
return GetInstance().mCameras;
|
||||
}
|
||||
|
||||
static nsCOMPtr<nsIThread>& Thread() {
|
||||
GetInstance().Mutex().AssertCurrentThreadOwns();
|
||||
return GetInstance().mCamerasChildThread;
|
||||
}
|
||||
|
||||
private:
|
||||
// Reinitializing CamerasChild will change the pointers below.
|
||||
// We don't want this to happen in the middle of preparing IPC.
|
||||
// We will be alive on destruction, so this needs to be off the books.
|
||||
mozilla::OffTheBooksMutex mCamerasMutex;
|
||||
|
||||
// This is owned by the IPC code, and the same code controls the lifetime.
|
||||
// It will set and clear this pointer as appropriate in setup/teardown.
|
||||
// We'd normally make this a WeakPtr but unfortunately the IPC code already
|
||||
// uses the WeakPtr mixin in a protected base class of CamerasChild, and in
|
||||
// any case the object becomes unusable as soon as IPC is tearing down, which
|
||||
// will be before actual destruction.
|
||||
CamerasChild* mCameras;
|
||||
nsCOMPtr<nsIThread> mCamerasChildThread;
|
||||
};
|
||||
CamerasSingleton::~CamerasSingleton() {
|
||||
LOG(("~CamerasSingleton: %p", this));
|
||||
}
|
||||
|
||||
class InitializeIPCThread : public nsRunnable
|
||||
{
|
||||
|
@ -135,7 +77,7 @@ private:
|
|||
CamerasChild* mCamerasChild;
|
||||
};
|
||||
|
||||
static CamerasChild*
|
||||
CamerasChild*
|
||||
GetCamerasChild() {
|
||||
CamerasSingleton::Mutex().AssertCurrentThreadOwns();
|
||||
if (!CamerasSingleton::Child()) {
|
||||
|
@ -189,17 +131,6 @@ CamerasChild::RecvReplySuccess(void)
|
|||
return true;
|
||||
}
|
||||
|
||||
int NumberOfCapabilities(CaptureEngine aCapEngine, const char* deviceUniqueIdUTF8)
|
||||
{
|
||||
OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
|
||||
CamerasChild* child = GetCamerasChild();
|
||||
if (child) {
|
||||
return child->NumberOfCapabilities(aCapEngine, deviceUniqueIdUTF8);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
CamerasChild::RecvReplyNumberOfCapabilities(const int& numdev)
|
||||
{
|
||||
|
@ -263,17 +194,6 @@ CamerasChild::NumberOfCapabilities(CaptureEngine aCapEngine,
|
|||
return mReplyInteger;
|
||||
}
|
||||
|
||||
int NumberOfCaptureDevices(CaptureEngine aCapEngine)
|
||||
{
|
||||
OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
|
||||
CamerasChild* child = GetCamerasChild();
|
||||
if (child) {
|
||||
return child->NumberOfCaptureDevices(aCapEngine);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
CamerasChild::NumberOfCaptureDevices(CaptureEngine aCapEngine)
|
||||
{
|
||||
|
@ -307,22 +227,6 @@ CamerasChild::RecvReplyNumberOfCaptureDevices(const int& numdev)
|
|||
return true;
|
||||
}
|
||||
|
||||
int GetCaptureCapability(CaptureEngine aCapEngine, const char* unique_idUTF8,
|
||||
const unsigned int capability_number,
|
||||
webrtc::CaptureCapability& capability)
|
||||
{
|
||||
OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
|
||||
CamerasChild* child = GetCamerasChild();
|
||||
if (child) {
|
||||
return child->GetCaptureCapability(aCapEngine,
|
||||
unique_idUTF8,
|
||||
capability_number,
|
||||
capability);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
CamerasChild::GetCaptureCapability(CaptureEngine aCapEngine,
|
||||
const char* unique_idUTF8,
|
||||
|
@ -365,27 +269,6 @@ CamerasChild::RecvReplyGetCaptureCapability(const CaptureCapability& ipcCapabili
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
int GetCaptureDevice(CaptureEngine aCapEngine,
|
||||
unsigned int list_number, char* device_nameUTF8,
|
||||
const unsigned int device_nameUTF8Length,
|
||||
char* unique_idUTF8,
|
||||
const unsigned int unique_idUTF8Length)
|
||||
{
|
||||
OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
|
||||
CamerasChild* child = GetCamerasChild();
|
||||
if (child) {
|
||||
return child->GetCaptureDevice(aCapEngine,
|
||||
list_number,
|
||||
device_nameUTF8,
|
||||
device_nameUTF8Length,
|
||||
unique_idUTF8,
|
||||
unique_idUTF8Length);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
CamerasChild::GetCaptureDevice(CaptureEngine aCapEngine,
|
||||
unsigned int list_number, char* device_nameUTF8,
|
||||
|
@ -427,23 +310,6 @@ CamerasChild::RecvReplyGetCaptureDevice(const nsCString& device_name,
|
|||
return true;
|
||||
}
|
||||
|
||||
int AllocateCaptureDevice(CaptureEngine aCapEngine,
|
||||
const char* unique_idUTF8,
|
||||
const unsigned int unique_idUTF8Length,
|
||||
int& capture_id)
|
||||
{
|
||||
OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
|
||||
CamerasChild* child = GetCamerasChild();
|
||||
if (child) {
|
||||
return child->AllocateCaptureDevice(aCapEngine,
|
||||
unique_idUTF8,
|
||||
unique_idUTF8Length,
|
||||
capture_id);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
CamerasChild::AllocateCaptureDevice(CaptureEngine aCapEngine,
|
||||
const char* unique_idUTF8,
|
||||
|
@ -483,17 +349,6 @@ CamerasChild::RecvReplyAllocateCaptureDevice(const int& numdev)
|
|||
return true;
|
||||
}
|
||||
|
||||
int ReleaseCaptureDevice(CaptureEngine aCapEngine, const int capture_id)
|
||||
{
|
||||
OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
|
||||
CamerasChild* child = GetCamerasChild();
|
||||
if (child) {
|
||||
return child->ReleaseCaptureDevice(aCapEngine, capture_id);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
CamerasChild::ReleaseCaptureDevice(CaptureEngine aCapEngine,
|
||||
const int capture_id)
|
||||
|
@ -539,23 +394,6 @@ CamerasChild::RemoveCallback(const CaptureEngine aCapEngine, const int capture_i
|
|||
}
|
||||
}
|
||||
|
||||
int StartCapture(CaptureEngine aCapEngine,
|
||||
const int capture_id,
|
||||
webrtc::CaptureCapability& webrtcCaps,
|
||||
webrtc::ExternalRenderer* cb)
|
||||
{
|
||||
OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
|
||||
CamerasChild* child = GetCamerasChild();
|
||||
if (child) {
|
||||
return child->StartCapture(aCapEngine,
|
||||
capture_id,
|
||||
webrtcCaps,
|
||||
cb);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
CamerasChild::StartCapture(CaptureEngine aCapEngine,
|
||||
const int capture_id,
|
||||
|
@ -586,17 +424,6 @@ CamerasChild::StartCapture(CaptureEngine aCapEngine,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int StopCapture(CaptureEngine aCapEngine, const int capture_id)
|
||||
{
|
||||
OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
|
||||
CamerasChild* child = GetCamerasChild();
|
||||
if (child) {
|
||||
return child->StopCapture(aCapEngine, capture_id);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
CamerasChild::StopCapture(CaptureEngine aCapEngine, const int capture_id)
|
||||
{
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef mozilla_CamerasChild_h
|
||||
#define mozilla_CamerasChild_h
|
||||
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/Pair.h"
|
||||
#include "mozilla/dom/ContentChild.h"
|
||||
#include "mozilla/camera/PCamerasChild.h"
|
||||
|
@ -46,33 +47,96 @@ struct CapturerElement {
|
|||
webrtc::ExternalRenderer* callback;
|
||||
};
|
||||
|
||||
// statically mirror webrtc.org ViECapture API
|
||||
// these are called via MediaManager->MediaEngineRemoteVideoSource
|
||||
// on the MediaManager thread
|
||||
int NumberOfCapabilities(CaptureEngine aCapEngine,
|
||||
const char* deviceUniqueIdUTF8);
|
||||
int GetCaptureCapability(CaptureEngine aCapEngine,
|
||||
const char* unique_idUTF8,
|
||||
const unsigned int capability_number,
|
||||
webrtc::CaptureCapability& capability);
|
||||
int NumberOfCaptureDevices(CaptureEngine aCapEngine);
|
||||
int GetCaptureDevice(CaptureEngine aCapEngine,
|
||||
unsigned int list_number, char* device_nameUTF8,
|
||||
const unsigned int device_nameUTF8Length,
|
||||
char* unique_idUTF8,
|
||||
const unsigned int unique_idUTF8Length);
|
||||
int AllocateCaptureDevice(CaptureEngine aCapEngine,
|
||||
const char* unique_idUTF8,
|
||||
const unsigned int unique_idUTF8Length,
|
||||
int& capture_id);
|
||||
int ReleaseCaptureDevice(CaptureEngine aCapEngine,
|
||||
const int capture_id);
|
||||
int StartCapture(CaptureEngine aCapEngine,
|
||||
const int capture_id, webrtc::CaptureCapability& capability,
|
||||
webrtc::ExternalRenderer* func);
|
||||
int StopCapture(CaptureEngine aCapEngine, const int capture_id);
|
||||
// Forward declaration so we can work with pointers to it.
|
||||
class CamerasChild;
|
||||
|
||||
// We emulate the sync webrtc.org API with the help of singleton
|
||||
// CamerasSingleton, which manages a pointer to an IPC object, a thread
|
||||
// where IPC operations should run on, and a mutex.
|
||||
// The static function Cameras() will use that Singleton to set up,
|
||||
// if needed, both the thread and the associated IPC objects and return
|
||||
// a pointer to the IPC object. Users can then do IPC calls on that object
|
||||
// after dispatching them to aforementioned thread.
|
||||
|
||||
// 2 Threads are involved in this code:
|
||||
// - the MediaManager thread, which will call the (static, sync API) functions
|
||||
// through MediaEngineRemoteVideoSource
|
||||
// - the Cameras IPC thread, which will be doing our IPC to the parent process
|
||||
// via PBackground
|
||||
|
||||
// Our main complication is that we emulate a sync API while (having to do)
|
||||
// async messaging. We dispatch the messages to another thread to send them
|
||||
// async and hold a Monitor to wait for the result to be asynchronously received
|
||||
// again. The requirement for async messaging originates on the parent side:
|
||||
// it's not reasonable to block all PBackground IPC there while waiting for
|
||||
// something like device enumeration to complete.
|
||||
|
||||
class CamerasSingleton {
|
||||
public:
|
||||
CamerasSingleton();
|
||||
~CamerasSingleton();
|
||||
|
||||
static OffTheBooksMutex& Mutex() {
|
||||
return GetInstance().mCamerasMutex;
|
||||
}
|
||||
|
||||
static CamerasChild*& Child() {
|
||||
Mutex().AssertCurrentThreadOwns();
|
||||
return GetInstance().mCameras;
|
||||
}
|
||||
|
||||
static nsCOMPtr<nsIThread>& Thread() {
|
||||
Mutex().AssertCurrentThreadOwns();
|
||||
return GetInstance().mCamerasChildThread;
|
||||
}
|
||||
|
||||
private:
|
||||
static CamerasSingleton& GetInstance() {
|
||||
static CamerasSingleton instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Reinitializing CamerasChild will change the pointers below.
|
||||
// We don't want this to happen in the middle of preparing IPC.
|
||||
// We will be alive on destruction, so this needs to be off the books.
|
||||
mozilla::OffTheBooksMutex mCamerasMutex;
|
||||
|
||||
// This is owned by the IPC code, and the same code controls the lifetime.
|
||||
// It will set and clear this pointer as appropriate in setup/teardown.
|
||||
// We'd normally make this a WeakPtr but unfortunately the IPC code already
|
||||
// uses the WeakPtr mixin in a protected base class of CamerasChild, and in
|
||||
// any case the object becomes unusable as soon as IPC is tearing down, which
|
||||
// will be before actual destruction.
|
||||
CamerasChild* mCameras;
|
||||
nsCOMPtr<nsIThread> mCamerasChildThread;
|
||||
};
|
||||
|
||||
// Get a pointer to a CamerasChild object we can use to do IPC with.
|
||||
// This does everything needed to set up, including starting the IPC
|
||||
// channel with PBackground, blocking until thats done, and starting the
|
||||
// thread to do IPC on. This will fail if we're in shutdown. On success
|
||||
// it will set up the CamerasSingleton.
|
||||
CamerasChild* GetCamerasChild();
|
||||
|
||||
// Shut down the IPC channel and everything associated, like WebRTC.
|
||||
// This is a static call because the CamerasChild object may not even
|
||||
// be alive when we're called.
|
||||
void Shutdown(void);
|
||||
|
||||
// Obtain the CamerasChild object (if possible, i.e. not shutting down),
|
||||
// and maintain a grip on the object for the duration of the call.
|
||||
template <class MEM_FUN, class... ARGS>
|
||||
int GetChildAndCall(MEM_FUN&& f, ARGS&&... args)
|
||||
{
|
||||
OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
|
||||
CamerasChild* child = GetCamerasChild();
|
||||
if (child) {
|
||||
return (child->*f)(mozilla::Forward<ARGS>(args)...);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
class CamerasChild final : public PCamerasChild
|
||||
{
|
||||
friend class mozilla::ipc::BackgroundChildImpl;
|
||||
|
|
|
@ -32,15 +32,25 @@ runTest(() =>
|
|||
.then(() => navigator.mediaDevices.enumerateDevices())
|
||||
.then(devices => {
|
||||
ok(devices.length > 0, "At least one device found");
|
||||
devices.forEach(d => {
|
||||
var jsoned = JSON.parse(JSON.stringify(devices));
|
||||
is(jsoned[0].kind, devices[0].kind, "kind survived serializer");
|
||||
is(jsoned[0].deviceId, devices[0].deviceId, "deviceId survived serializer");
|
||||
return devices.reduce((p, d) => {
|
||||
ok(d.kind == "videoinput" || d.kind == "audioinput", "Known device kind");
|
||||
is(d.deviceId.length, 44, "Correct device id length");
|
||||
ok(d.label.length !== undefined, "Device label: " + d.label);
|
||||
is(d.groupId, "", "Don't support groupId yet");
|
||||
});
|
||||
var jsoned = JSON.parse(JSON.stringify(devices));
|
||||
is(jsoned[0].kind, devices[0].kind, "kind survived serializer");
|
||||
is(jsoned[0].deviceId, devices[0].deviceId, "deviceId survived serializer");
|
||||
var c = {};
|
||||
c[d.kind == "videoinput" ? "video" : "audio"] = { deviceId: d.deviceId };
|
||||
return p
|
||||
.then(() => navigator.mediaDevices.getUserMedia(c))
|
||||
.then(stream => {
|
||||
stream.getTracks().forEach(track => {
|
||||
is(typeof(track.label), "string", "Label is a string")
|
||||
is(track.label, d.label, "Label is the device label")
|
||||
})
|
||||
});
|
||||
}, Promise.resolve());
|
||||
})
|
||||
// Check deviceId failure paths for video.
|
||||
.then(() => mustSucceed("unknown plain deviceId on video",
|
||||
|
|
|
@ -34,7 +34,7 @@ MediaStreamAudioDestinationNode::MediaStreamAudioDestinationNode(AudioContext* a
|
|||
aContext->Graph()))
|
||||
{
|
||||
// Ensure an audio track with the correct ID is exposed to JS
|
||||
mDOMStream->CreateOwnDOMTrack(AudioNodeStream::AUDIO_TRACK, MediaSegment::AUDIO);
|
||||
mDOMStream->CreateOwnDOMTrack(AudioNodeStream::AUDIO_TRACK, MediaSegment::AUDIO, nsString());
|
||||
|
||||
ProcessedMediaStream* outputStream = mDOMStream->GetInputStream()->AsProcessedStream();
|
||||
MOZ_ASSERT(!!outputStream);
|
||||
|
|
|
@ -17,6 +17,12 @@ extern mozilla::LogModule* GetMediaManagerLog();
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
// These need a definition somewhere because template
|
||||
// code is allowed to take their address, and they aren't
|
||||
// guaranteed to have one without this.
|
||||
const unsigned int MediaEngineSource::kMaxDeviceNameLength;
|
||||
const unsigned int MediaEngineSource::kMaxUniqueIdLength;;
|
||||
|
||||
using dom::ConstrainLongRange;
|
||||
|
||||
NS_IMPL_ISUPPORTS0(MediaEngineRemoteVideoSource)
|
||||
|
@ -38,10 +44,11 @@ MediaEngineRemoteVideoSource::Init()
|
|||
LOG((__PRETTY_FUNCTION__));
|
||||
char deviceName[kMaxDeviceNameLength];
|
||||
char uniqueId[kMaxUniqueIdLength];
|
||||
if (mozilla::camera::GetCaptureDevice(mCapEngine,
|
||||
mCaptureIndex,
|
||||
deviceName, kMaxDeviceNameLength,
|
||||
uniqueId, kMaxUniqueIdLength)) {
|
||||
if (mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::GetCaptureDevice,
|
||||
mCapEngine, mCaptureIndex,
|
||||
deviceName, kMaxDeviceNameLength,
|
||||
uniqueId, kMaxUniqueIdLength)) {
|
||||
LOG(("Error initializing RemoteVideoSource (GetCaptureDevice)"));
|
||||
return;
|
||||
}
|
||||
|
@ -111,9 +118,9 @@ MediaEngineRemoteVideoSource::Allocate(const dom::MediaTrackConstraints& aConstr
|
|||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
if (mozilla::camera::AllocateCaptureDevice(mCapEngine,
|
||||
GetUUID().get(),
|
||||
kMaxUniqueIdLength, mCaptureIndex)) {
|
||||
if (mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::AllocateCaptureDevice,
|
||||
mCapEngine, GetUUID().get(), kMaxUniqueIdLength, mCaptureIndex)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mState = kAllocated;
|
||||
|
@ -145,7 +152,9 @@ MediaEngineRemoteVideoSource::Deallocate()
|
|||
if (mState != kStopped && mState != kAllocated) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mozilla::camera::ReleaseCaptureDevice(mCapEngine, mCaptureIndex);
|
||||
mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::ReleaseCaptureDevice,
|
||||
mCapEngine, mCaptureIndex);
|
||||
mState = kReleased;
|
||||
LOG(("Video device %d deallocated", mCaptureIndex));
|
||||
} else {
|
||||
|
@ -179,8 +188,9 @@ MediaEngineRemoteVideoSource::Start(SourceMediaStream* aStream, TrackID aID)
|
|||
mState = kStarted;
|
||||
mTrackID = aID;
|
||||
|
||||
if (mozilla::camera::StartCapture(mCapEngine,
|
||||
mCaptureIndex, mCapability, this)) {
|
||||
if (mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::StartCapture,
|
||||
mCapEngine, mCaptureIndex, mCapability, this)) {
|
||||
LOG(("StartCapture failed"));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
@ -217,7 +227,9 @@ MediaEngineRemoteVideoSource::Stop(mozilla::SourceMediaStream* aSource,
|
|||
mImage = nullptr;
|
||||
}
|
||||
|
||||
mozilla::camera::StopCapture(mCapEngine, mCaptureIndex);
|
||||
mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::StopCapture,
|
||||
mCapEngine, mCaptureIndex);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -239,9 +251,12 @@ MediaEngineRemoteVideoSource::Restart(const dom::MediaTrackConstraints& aConstra
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
mozilla::camera::StopCapture(mCapEngine, mCaptureIndex);
|
||||
if (mozilla::camera::StartCapture(mCapEngine,
|
||||
mCaptureIndex, mCapability, this)) {
|
||||
mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::StopCapture,
|
||||
mCapEngine, mCaptureIndex);
|
||||
if (mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::StartCapture,
|
||||
mCapEngine, mCaptureIndex, mCapability, this)) {
|
||||
LOG(("StartCapture failed"));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
@ -354,7 +369,10 @@ MediaEngineRemoteVideoSource::DeliverFrame(unsigned char* buffer,
|
|||
size_t
|
||||
MediaEngineRemoteVideoSource::NumCapabilities()
|
||||
{
|
||||
int num = mozilla::camera::NumberOfCapabilities(mCapEngine, GetUUID().get());
|
||||
int num = mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::NumberOfCapabilities,
|
||||
mCapEngine,
|
||||
GetUUID().get());
|
||||
if (num > 0) {
|
||||
return num;
|
||||
}
|
||||
|
@ -433,10 +451,12 @@ MediaEngineRemoteVideoSource::GetCapability(size_t aIndex,
|
|||
if (!mHardcodedCapabilities.IsEmpty()) {
|
||||
MediaEngineCameraVideoSource::GetCapability(aIndex, aOut);
|
||||
}
|
||||
mozilla::camera::GetCaptureCapability(mCapEngine,
|
||||
GetUUID().get(),
|
||||
aIndex,
|
||||
aOut);
|
||||
mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::GetCaptureCapability,
|
||||
mCapEngine,
|
||||
GetUUID().get(),
|
||||
aIndex,
|
||||
aOut);
|
||||
}
|
||||
|
||||
void MediaEngineRemoteVideoSource::Refresh(int aIndex) {
|
||||
|
@ -446,10 +466,11 @@ void MediaEngineRemoteVideoSource::Refresh(int aIndex) {
|
|||
char deviceName[kMaxDeviceNameLength];
|
||||
char uniqueId[kMaxUniqueIdLength];
|
||||
|
||||
if (mozilla::camera::GetCaptureDevice(mCapEngine,
|
||||
aIndex,
|
||||
deviceName, sizeof(deviceName),
|
||||
uniqueId, sizeof(uniqueId))) {
|
||||
if (mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::GetCaptureDevice,
|
||||
mCapEngine, aIndex,
|
||||
deviceName, sizeof(deviceName),
|
||||
uniqueId, sizeof(uniqueId))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -161,7 +161,9 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
|||
* mVideoSources must be updated.
|
||||
*/
|
||||
int num;
|
||||
num = mozilla::camera::NumberOfCaptureDevices(capEngine);
|
||||
num = mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::NumberOfCaptureDevices,
|
||||
capEngine);
|
||||
if (num <= 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -175,11 +177,12 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
|||
uniqueId[0] = '\0';
|
||||
int error;
|
||||
|
||||
error = mozilla::camera::GetCaptureDevice(capEngine,
|
||||
i, deviceName,
|
||||
sizeof(deviceName), uniqueId,
|
||||
sizeof(uniqueId));
|
||||
|
||||
error = mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::GetCaptureDevice,
|
||||
capEngine,
|
||||
i, deviceName,
|
||||
sizeof(deviceName), uniqueId,
|
||||
sizeof(uniqueId));
|
||||
if (error) {
|
||||
LOG(("camera:GetCaptureDevice: Failed %d", error ));
|
||||
continue;
|
||||
|
@ -188,13 +191,17 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
|||
LOG((" Capture Device Index %d, Name %s", i, deviceName));
|
||||
|
||||
webrtc::CaptureCapability cap;
|
||||
int numCaps = mozilla::camera::NumberOfCapabilities(capEngine,
|
||||
uniqueId);
|
||||
int numCaps = mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::NumberOfCapabilities,
|
||||
capEngine,
|
||||
uniqueId);
|
||||
LOG(("Number of Capabilities %d", numCaps));
|
||||
for (int j = 0; j < numCaps; j++) {
|
||||
if (mozilla::camera::GetCaptureCapability(capEngine,
|
||||
uniqueId,
|
||||
j, cap ) != 0 ) {
|
||||
if (mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::GetCaptureCapability,
|
||||
capEngine,
|
||||
uniqueId,
|
||||
j, cap) != 0) {
|
||||
break;
|
||||
}
|
||||
LOG(("type=%d width=%d height=%d maxFPS=%d",
|
||||
|
|
|
@ -40,7 +40,7 @@ const TYPED_ARRAY_THINGS = new Set([
|
|||
]);
|
||||
|
||||
this.SETTINGSDB_NAME = "settings";
|
||||
this.SETTINGSDB_VERSION = 7;
|
||||
this.SETTINGSDB_VERSION = 8;
|
||||
this.SETTINGSSTORE_NAME = "settings";
|
||||
|
||||
Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<script>
|
||||
parent.postMessage(SpecialPowers.wrap(localStorage).isSessionOnly, "*");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -16,6 +16,7 @@ support-files =
|
|||
interOriginTest.js
|
||||
interOriginTest2.js
|
||||
localStorageCommon.js
|
||||
frameLocalStorageSessionOnly.html
|
||||
|
||||
[test_appIsolation.html]
|
||||
skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #bug 793211 # b2g(needs https to work) b2g-debug(needs https to work) b2g-desktop(needs https to work)
|
||||
|
@ -56,3 +57,4 @@ skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT # b2g(needs https
|
|||
skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || toolkit == 'android'
|
||||
[test_lowDeviceStorage.html]
|
||||
[test_storageConstructor.html]
|
||||
[test_localStorageSessionPrefOverride.html]
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Local Storage Session Pref Override</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script>
|
||||
const ACCEPT_SESSION = 2;
|
||||
|
||||
add_task(function*() {
|
||||
yield new Promise((resolve, reject) => {
|
||||
SpecialPowers.pushPrefEnv({"set": [["network.cookie.lifetimePolicy",
|
||||
ACCEPT_SESSION]]},
|
||||
resolve);
|
||||
});
|
||||
|
||||
// Before setting permission
|
||||
yield new Promise((resolve) => {
|
||||
var frame = document.createElement('iframe');
|
||||
frame.src = "frameLocalStorageSessionOnly.html";
|
||||
|
||||
var listener = (e) => {
|
||||
is(e.data, true, "Before adding permission should be session only");
|
||||
window.removeEventListener('message', listener);
|
||||
resolve();
|
||||
};
|
||||
window.addEventListener('message', listener);
|
||||
document.body.appendChild(frame);
|
||||
});
|
||||
|
||||
// After setting permission
|
||||
yield new Promise((resolve) => {
|
||||
SpecialPowers.pushPermissions([{"type": "cookie", "allow": 1, "context": document}],
|
||||
resolve);
|
||||
});
|
||||
|
||||
yield new Promise((resolve) => {
|
||||
var frame = document.createElement('iframe');
|
||||
frame.src = "frameLocalStorageSessionOnly.html";
|
||||
|
||||
var listener = (e) => {
|
||||
is(e.data, false, "After adding permission should not be session only");
|
||||
window.removeEventListener('message', listener);
|
||||
resolve();
|
||||
};
|
||||
window.addEventListener('message', listener);
|
||||
document.body.appendChild(frame);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
|
@ -49,6 +49,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
}
|
||||
|
||||
var firstMoveListener = function (e) {
|
||||
info("Got first mousemove");
|
||||
div.removeEventListener("mousemove", firstMoveListener, false);
|
||||
div.addEventListener("mousemove", secondMoveListener, false);
|
||||
|
||||
|
@ -59,6 +60,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
}
|
||||
|
||||
var secondMoveListener = function (e) {
|
||||
info("Got second mousemove");
|
||||
totalMovementX = divCenterWidth + ((divCenterWidth / 2) * 3);
|
||||
totalMovementY = divCenterHeight + ((divCenterHeight / 2) * 3);
|
||||
|
||||
|
@ -68,6 +70,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
|
||||
document.addEventListener("mozpointerlockchange", function (e) {
|
||||
if (document.mozPointerLockElement === div) {
|
||||
info("Got mozpointerlockchange for entering");
|
||||
div.addEventListener("mousemove", firstMoveListener, false);
|
||||
|
||||
divCenterWidth = Math.round(div.getBoundingClientRect().width / 2);
|
||||
|
@ -76,20 +79,25 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
synthesizeMouse(div, divCenterWidth, divCenterHeight, {
|
||||
type: "mousemove"
|
||||
}, window);
|
||||
} else {
|
||||
info("Got mozpointerlockchange for exiting");
|
||||
}
|
||||
}, false);
|
||||
|
||||
document.addEventListener("mozfullscreenchange", function() {
|
||||
if (document.mozFullScreenElement === div) {
|
||||
info("Got mozfullscreenchange for entering");
|
||||
div.mozRequestPointerLock();
|
||||
}
|
||||
else {
|
||||
info("Got mozfullscreenchange for exiting");
|
||||
runTests();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}, false);
|
||||
|
||||
function start() {
|
||||
info("Requesting fullscreen on parent");
|
||||
div.mozRequestFullScreen();
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -57,6 +57,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
}
|
||||
|
||||
var moveMouse = function(e) {
|
||||
info("Got mouse move");
|
||||
mozMovementX = ("mozMovementX" in e);
|
||||
mozMovementY = ("mozMovementY" in e);
|
||||
|
||||
|
@ -75,6 +76,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
};
|
||||
|
||||
var moveMouseAgain = function(e) {
|
||||
info("Got mouse move again");
|
||||
secondMove.screenX = e.screenX;
|
||||
secondMove.screenY = e.screenY;
|
||||
secondMove.mozMovementX = e.mozMovementX;
|
||||
|
@ -86,6 +88,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
|
||||
function fullscreenchange() {
|
||||
if (document.mozFullScreenElement === div) {
|
||||
info("Got mozfullscreenchange for entering");
|
||||
var screenX = window.screenX;
|
||||
var screenY = window.screenY;
|
||||
if (screenX != 0 || screenY != 0) {
|
||||
|
@ -94,10 +97,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
setTimeout(fullscreenchange, 250);
|
||||
return;
|
||||
}
|
||||
info("Finish waiting for fullscreenchange");
|
||||
div.addEventListener("mousemove", moveMouse, false);
|
||||
synthesizeMouseAtCenter(div, {type: "mousemove"}, window);
|
||||
}
|
||||
else {
|
||||
info("Got mozfullscreenchange for exiting");
|
||||
runTests();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
@ -106,6 +111,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
document.addEventListener("mozfullscreenchange", fullscreenchange, false);
|
||||
|
||||
function start() {
|
||||
info("Requesting fullscreen on parent");
|
||||
div.mozRequestFullScreen();
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
}
|
||||
|
||||
function mouseMoveHandler(e) {
|
||||
info("Got mousemove");
|
||||
document.removeEventListener("mousemove", mouseMoveHandler, false);
|
||||
|
||||
hasMovementX = "mozMovementX" in e;
|
||||
|
@ -62,6 +63,7 @@
|
|||
pointerLockChangeEventFired = true;
|
||||
|
||||
if (document.mozPointerLockElement) {
|
||||
info("Got mozpointerlockchange for entering");
|
||||
pointerLocked = true;
|
||||
pointerLockElement = document.mozPointerLockElement === div;
|
||||
|
||||
|
@ -75,6 +77,7 @@
|
|||
document.addEventListener("mousemove", mouseMoveHandler, false);
|
||||
synthesizeMouseAtCenter(div, {type: "mousemove"}, window);
|
||||
} else {
|
||||
info("Got mozpointerlockchange for exiting");
|
||||
pointerUnlocked = true;
|
||||
document.mozCancelFullScreen();
|
||||
}
|
||||
|
@ -82,9 +85,11 @@
|
|||
|
||||
document.addEventListener("mozfullscreenchange", function(e) {
|
||||
if (document.mozFullScreenElement === div) {
|
||||
info("Got mozfullscreenchange for entering");
|
||||
hasRequestPointerLock = "mozRequestPointerLock" in div;
|
||||
div.mozRequestPointerLock();
|
||||
} else {
|
||||
info("Got mozfullscreenchange for exiting");
|
||||
runTests();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
@ -92,6 +97,7 @@
|
|||
|
||||
function start() {
|
||||
div = document.getElementById("div");
|
||||
info("Requesting fullscreen on parent");
|
||||
div.mozRequestFullScreen();
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -93,6 +93,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
|
||||
// Event listeners for the parent element
|
||||
var startMouseTests = function() {
|
||||
info("Got parent mousemove");
|
||||
parent.removeEventListener("mousemove", startMouseTests);
|
||||
parent.addEventListener("DOMMouseScroll", parentScrollTest);
|
||||
child.addEventListener("DOMMouseScroll", childScrollTest);
|
||||
|
@ -103,6 +104,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
};
|
||||
|
||||
var parentScrollTest = function (e) {
|
||||
info("Got parent DOMMouseScroll");
|
||||
parentStats.mouseScroll = true;
|
||||
parent.removeEventListener("DOMMouseScroll", parentScrollTest);
|
||||
child.removeEventListener("DOMMouseScroll", childScrollTest);
|
||||
|
@ -115,6 +117,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
};
|
||||
|
||||
var parentWheelTest = function (e) {
|
||||
info("Got parent wheel");
|
||||
parentStats.wheel = true;
|
||||
parent.removeEventListener("wheel", parentWheelTest);
|
||||
child.removeEventListener("wheel", childWheelTest);
|
||||
|
@ -126,6 +129,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
};
|
||||
|
||||
var parentDownTest = function (e) {
|
||||
info("Got parent mousedown");
|
||||
parentStats.mouseDown = true;
|
||||
parent.removeEventListener("mousedown", parentDownTest);
|
||||
child.removeEventListener("mousedown", childDownTest);
|
||||
|
@ -137,6 +141,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
};
|
||||
|
||||
var parentUpTest = function (e) {
|
||||
info("Got parent mouseup");
|
||||
parentStats.mouseUp = true;
|
||||
parent.removeEventListener("mouseup", parentUpTest);
|
||||
child.removeEventListener("mouseup", childUpTest);
|
||||
|
@ -148,6 +153,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
};
|
||||
|
||||
var parentClickTest = function (e) {
|
||||
info("Got parent click");
|
||||
parentStats.mouseClick = true;
|
||||
parent.removeEventListener("click", parentClickTest);
|
||||
child.removeEventListener("click", childClickTest);
|
||||
|
@ -159,34 +165,42 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
};
|
||||
|
||||
var parentMoveTest = function (e) {
|
||||
info("Got parent mousemove");
|
||||
parentStats.mouseMove = true;
|
||||
parent.removeEventListener("mousemove", parentMoveTest);
|
||||
child.removeEventListener("mousemove", childMoveTest);
|
||||
SimpleTest.executeSoon(function () {
|
||||
info("Exit fullscreen");
|
||||
document.mozCancelFullScreen();
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("mozpointerlockchange", function (e) {
|
||||
if (document.mozPointerLockElement === parent) {
|
||||
info("Got mozpointerlockchange for entering");
|
||||
parent.addEventListener("mousemove", startMouseTests);
|
||||
child.addEventListener("mousemove", childMoveTest);
|
||||
SimpleTest.executeSoon(function () {
|
||||
synthesizeMouseAtCenter(parent, {type: "mousemove"}, window);
|
||||
});
|
||||
} else {
|
||||
info("Got mozpointerlockchange for exiting");
|
||||
}
|
||||
}, false);
|
||||
|
||||
document.addEventListener("mozfullscreenchange", function (e) {
|
||||
if (document.mozFullScreenElement === parent) {
|
||||
info("Got mozfullscreenchange for entering");
|
||||
parent.mozRequestPointerLock();
|
||||
} else {
|
||||
info("Got mozfullscreenchange for exiting");
|
||||
runTests();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}, false);
|
||||
|
||||
function start() {
|
||||
info("Requesting fullscreen on parent");
|
||||
parent.mozRequestFullScreen();
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -49,6 +49,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
}
|
||||
|
||||
function moveUnlocked(e) {
|
||||
info("Got mousemove via moveUnlocked");
|
||||
var firstCall = !unLockedCoords;
|
||||
if (!firstCall) {
|
||||
todo(false, "mousemove is fired twice.");
|
||||
|
@ -70,6 +71,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
}
|
||||
|
||||
function moveLocked(e) {
|
||||
info("Got mousemove via moveLocked");
|
||||
div.removeEventListener("mousemove", moveLocked, false);
|
||||
|
||||
isLocked = !!document.mozPointerLockElement;
|
||||
|
@ -85,11 +87,14 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
|
||||
document.addEventListener("mozpointerlockchange", function (e) {
|
||||
if (document.mozPointerLockElement === div) {
|
||||
info("Got mozpointerlockchange for entering");
|
||||
div.removeEventListener("mousemove", moveUnlocked, false);
|
||||
div.addEventListener("mousemove", moveLocked, false);
|
||||
divRect = div.getBoundingClientRect();
|
||||
synthesizeNativeMouseMove(div, (divRect.width / 4) * 3,
|
||||
(divRect.height / 4) * 3);
|
||||
} else {
|
||||
info("Got mozpointerlockchange for exiting");
|
||||
}
|
||||
}, false);
|
||||
|
||||
|
@ -97,6 +102,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
var screenX = window.screenX;
|
||||
var screenY = window.screenY;
|
||||
if (document.mozFullScreenElement === div) {
|
||||
info("Got mozfullscreenchange for entering");
|
||||
if (screenX != 0 || screenY != 0) {
|
||||
todo(screenX == 0 && screenY == 0,
|
||||
"We should only receive fullscreenchange once we've finished fullscreen transition " +
|
||||
|
@ -104,12 +110,14 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
setTimeout(fullscreenchange, 250);
|
||||
return;
|
||||
}
|
||||
info("Finish waiting for fullscreenchange");
|
||||
synthesizeNativeMouseMove(div, 0, 0, () => {
|
||||
div.addEventListener("mousemove", moveUnlocked, false);
|
||||
divRect = div.getBoundingClientRect();
|
||||
synthesizeNativeMouseMove(div, divRect.width / 2, divRect.height / 2);
|
||||
});
|
||||
} else {
|
||||
info("Got mozfullscreenchange for exiting");
|
||||
runTests();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
@ -118,6 +126,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
|
|||
|
||||
function start() {
|
||||
div = document.getElementById("div");
|
||||
info("Requesting fullscreen on parent");
|
||||
div.mozRequestFullScreen();
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -32,6 +32,10 @@ if (window.opener) {
|
|||
opener.todo_isnot(a, b, testName + ": " + msg);
|
||||
};
|
||||
|
||||
window.info = function(msg) {
|
||||
opener.info(testName + ": " + msg);
|
||||
};
|
||||
|
||||
// Override bits of SimpleTest so test files work stand-alone
|
||||
var SimpleTest = SimpleTest || {};
|
||||
|
||||
|
|
|
@ -29,4 +29,7 @@ interface Storage {
|
|||
|
||||
[Throws]
|
||||
void clear();
|
||||
|
||||
[ChromeOnly]
|
||||
readonly attribute boolean isSessionOnly;
|
||||
};
|
||||
|
|
|
@ -383,7 +383,9 @@ public:
|
|||
template<typename T>
|
||||
Log &operator<<(Hexa<T> aHex) {
|
||||
if (MOZ_UNLIKELY(LogIt())) {
|
||||
mMessage << "0x" << std::hex << aHex.mVal << std::dec;
|
||||
mMessage << std::showbase << std::hex
|
||||
<< aHex.mVal
|
||||
<< std::noshowbase << std::dec;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
|
|
@ -357,6 +357,28 @@ DecomposeIntoNoRepeatRects(const gfx::Rect& aRect,
|
|||
return 4;
|
||||
}
|
||||
|
||||
gfx::IntRect
|
||||
Compositor::ComputeBackdropCopyRect(const gfx::Rect& aRect,
|
||||
const gfx::Rect& aClipRect,
|
||||
const gfx::Matrix4x4& aTransform)
|
||||
{
|
||||
gfx::Rect renderBounds = mRenderBounds;
|
||||
|
||||
// Compute the clip.
|
||||
renderBounds.IntersectRect(renderBounds, aClipRect);
|
||||
|
||||
// Apply the layer transform.
|
||||
gfx::Rect dest = aTransform.TransformAndClipBounds(aRect, renderBounds);
|
||||
dest.RoundOut();
|
||||
|
||||
gfx::IntRect result;
|
||||
dest.ToIntRect(&result);
|
||||
|
||||
gfx::IntPoint offset = GetCurrentRenderTarget()->GetOrigin();
|
||||
result.MoveBy(-offset);
|
||||
return result;
|
||||
}
|
||||
|
||||
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
|
||||
void
|
||||
Compositor::SetDispAcquireFence(Layer* aLayer, nsIWidget* aWidget)
|
||||
|
|
|
@ -119,6 +119,7 @@ namespace layers {
|
|||
struct Effect;
|
||||
struct EffectChain;
|
||||
class Image;
|
||||
class ImageHostOverlay;
|
||||
class Layer;
|
||||
class TextureSource;
|
||||
class DataTextureSource;
|
||||
|
@ -453,6 +454,12 @@ public:
|
|||
// these methods properly.
|
||||
virtual nsIWidget* GetWidget() const { return nullptr; }
|
||||
|
||||
virtual bool HasImageHostOverlays() { return false; }
|
||||
|
||||
virtual void AddImageHostOverlay(ImageHostOverlay* aOverlay) {}
|
||||
|
||||
virtual void RemoveImageHostOverlay(ImageHostOverlay* aOverlay) {}
|
||||
|
||||
/**
|
||||
* Debug-build assertion that can be called to ensure code is running on the
|
||||
* compositor thread.
|
||||
|
@ -507,6 +514,16 @@ protected:
|
|||
|
||||
bool ShouldDrawDiagnostics(DiagnosticFlags);
|
||||
|
||||
/**
|
||||
* Given a layer rect, clip, and transform, compute the area of the backdrop that
|
||||
* needs to be copied for mix-blending. The output transform translates from 0..1
|
||||
* space into the backdrop rect space.
|
||||
*/
|
||||
gfx::IntRect ComputeBackdropCopyRect(
|
||||
const gfx::Rect& aRect,
|
||||
const gfx::Rect& aClipRect,
|
||||
const gfx::Matrix4x4& aTransform);
|
||||
|
||||
/**
|
||||
* Render time for the current composition.
|
||||
*/
|
||||
|
@ -535,6 +552,8 @@ protected:
|
|||
RefPtr<gfx::DrawTarget> mTarget;
|
||||
gfx::IntRect mTargetBounds;
|
||||
|
||||
gfx::Rect mRenderBounds;
|
||||
|
||||
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
|
||||
FenceHandle mReleaseFenceHandle;
|
||||
#endif
|
||||
|
@ -551,6 +570,31 @@ size_t DecomposeIntoNoRepeatRects(const gfx::Rect& aRect,
|
|||
decomposedRectArrayT* aLayerRects,
|
||||
decomposedRectArrayT* aTextureRects);
|
||||
|
||||
static inline bool
|
||||
BlendOpIsMixBlendMode(gfx::CompositionOp aOp)
|
||||
{
|
||||
switch (aOp) {
|
||||
case gfx::CompositionOp::OP_MULTIPLY:
|
||||
case gfx::CompositionOp::OP_SCREEN:
|
||||
case gfx::CompositionOp::OP_OVERLAY:
|
||||
case gfx::CompositionOp::OP_DARKEN:
|
||||
case gfx::CompositionOp::OP_LIGHTEN:
|
||||
case gfx::CompositionOp::OP_COLOR_DODGE:
|
||||
case gfx::CompositionOp::OP_COLOR_BURN:
|
||||
case gfx::CompositionOp::OP_HARD_LIGHT:
|
||||
case gfx::CompositionOp::OP_SOFT_LIGHT:
|
||||
case gfx::CompositionOp::OP_DIFFERENCE:
|
||||
case gfx::CompositionOp::OP_EXCLUSION:
|
||||
case gfx::CompositionOp::OP_HUE:
|
||||
case gfx::CompositionOp::OP_SATURATION:
|
||||
case gfx::CompositionOp::OP_COLOR:
|
||||
case gfx::CompositionOp::OP_LUMINOSITY:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace layers
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -2762,7 +2762,7 @@ void AsyncPanZoomController::FlushRepaintForNewInputBlock() {
|
|||
APZC_LOG("%p flushing repaint for new input block\n", this);
|
||||
|
||||
ReentrantMonitorAutoEnter lock(mMonitor);
|
||||
RequestContentRepaint(mFrameMetrics);
|
||||
RequestContentRepaint();
|
||||
UpdateSharedCompositorFrameMetrics();
|
||||
}
|
||||
|
||||
|
@ -2805,14 +2805,44 @@ int32_t AsyncPanZoomController::GetLastTouchIdentifier() const {
|
|||
}
|
||||
|
||||
void AsyncPanZoomController::RequestContentRepaint() {
|
||||
RequestContentRepaint(mFrameMetrics);
|
||||
// Reinvoke this method on the main thread if it's not there already. It's
|
||||
// important to do this before the call to CalculatePendingDisplayPort, so
|
||||
// that CalculatePendingDisplayPort uses the most recent available version of
|
||||
// mFrameMetrics, just before the paint request is dispatched to content.
|
||||
if (!NS_IsMainThread()) {
|
||||
// use the local variable to resolve the function overload.
|
||||
auto func = static_cast<void (AsyncPanZoomController::*)()>
|
||||
(&AsyncPanZoomController::RequestContentRepaint);
|
||||
NS_DispatchToMainThread(NS_NewRunnableMethod(this, func));
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
ReentrantMonitorAutoEnter lock(mMonitor);
|
||||
ParentLayerPoint velocity = GetVelocityVector();
|
||||
mFrameMetrics.SetDisplayPortMargins(CalculatePendingDisplayPort(mFrameMetrics, velocity));
|
||||
mFrameMetrics.SetUseDisplayPortMargins(true);
|
||||
mFrameMetrics.SetPaintRequestTime(TimeStamp::Now());
|
||||
RequestContentRepaint(mFrameMetrics, velocity);
|
||||
}
|
||||
|
||||
void AsyncPanZoomController::RequestContentRepaint(FrameMetrics& aFrameMetrics)
|
||||
/*static*/ CSSRect
|
||||
GetDisplayPortRect(const FrameMetrics& aFrameMetrics)
|
||||
{
|
||||
ParentLayerPoint velocity = GetVelocityVector();
|
||||
aFrameMetrics.SetDisplayPortMargins(CalculatePendingDisplayPort(aFrameMetrics, velocity));
|
||||
aFrameMetrics.SetUseDisplayPortMargins(true);
|
||||
// This computation is based on what happens in CalculatePendingDisplayPort. If that
|
||||
// changes then this might need to change too
|
||||
CSSRect baseRect(aFrameMetrics.GetScrollOffset(),
|
||||
aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels());
|
||||
baseRect.Inflate(aFrameMetrics.GetDisplayPortMargins() / aFrameMetrics.DisplayportPixelsPerCSSPixel());
|
||||
return baseRect;
|
||||
}
|
||||
|
||||
void
|
||||
AsyncPanZoomController::RequestContentRepaint(const FrameMetrics& aFrameMetrics,
|
||||
const ParentLayerPoint& aVelocity)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// If we're trying to paint what we already think is painted, discard this
|
||||
// request since it's a pointless paint.
|
||||
|
@ -2835,26 +2865,6 @@ void AsyncPanZoomController::RequestContentRepaint(FrameMetrics& aFrameMetrics)
|
|||
return;
|
||||
}
|
||||
|
||||
aFrameMetrics.SetPaintRequestTime(TimeStamp::Now());
|
||||
DispatchRepaintRequest(aFrameMetrics, velocity);
|
||||
aFrameMetrics.SetPresShellId(mLastContentPaintMetrics.GetPresShellId());
|
||||
}
|
||||
|
||||
/*static*/ CSSRect
|
||||
GetDisplayPortRect(const FrameMetrics& aFrameMetrics)
|
||||
{
|
||||
// This computation is based on what happens in CalculatePendingDisplayPort. If that
|
||||
// changes then this might need to change too
|
||||
CSSRect baseRect(aFrameMetrics.GetScrollOffset(),
|
||||
aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels());
|
||||
baseRect.Inflate(aFrameMetrics.GetDisplayPortMargins() / aFrameMetrics.DisplayportPixelsPerCSSPixel());
|
||||
return baseRect;
|
||||
}
|
||||
|
||||
void
|
||||
AsyncPanZoomController::DispatchRepaintRequest(const FrameMetrics& aFrameMetrics,
|
||||
const ParentLayerPoint& aVelocity)
|
||||
{
|
||||
RefPtr<GeckoContentController> controller = GetGeckoContentController();
|
||||
if (!controller) {
|
||||
return;
|
||||
|
@ -2870,12 +2880,7 @@ AsyncPanZoomController::DispatchRepaintRequest(const FrameMetrics& aFrameMetrics
|
|||
str);
|
||||
}
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
controller->RequestContentRepaint(aFrameMetrics);
|
||||
} else {
|
||||
NS_DispatchToMainThread(NS_NewRunnableMethodWithArg<FrameMetrics>(
|
||||
controller, &GeckoContentController::RequestContentRepaint, aFrameMetrics));
|
||||
}
|
||||
controller->RequestContentRepaint(aFrameMetrics);
|
||||
mExpectedGeckoMetrics = aFrameMetrics;
|
||||
mLastPaintRequestMetrics = aFrameMetrics;
|
||||
}
|
||||
|
@ -3460,9 +3465,6 @@ void AsyncPanZoomController::ZoomToRect(CSSRect aRect) {
|
|||
}
|
||||
|
||||
endZoomToMetrics.SetScrollOffset(aRect.TopLeft());
|
||||
endZoomToMetrics.SetDisplayPortMargins(
|
||||
CalculatePendingDisplayPort(endZoomToMetrics, ParentLayerPoint(0,0)));
|
||||
endZoomToMetrics.SetUseDisplayPortMargins(true);
|
||||
|
||||
StartAnimation(new ZoomAnimation(
|
||||
mFrameMetrics.GetScrollOffset(),
|
||||
|
@ -3472,7 +3474,21 @@ void AsyncPanZoomController::ZoomToRect(CSSRect aRect) {
|
|||
|
||||
// Schedule a repaint now, so the new displayport will be painted before the
|
||||
// animation finishes.
|
||||
RequestContentRepaint(endZoomToMetrics);
|
||||
ParentLayerPoint velocity(0, 0);
|
||||
endZoomToMetrics.SetDisplayPortMargins(
|
||||
CalculatePendingDisplayPort(endZoomToMetrics, velocity));
|
||||
endZoomToMetrics.SetUseDisplayPortMargins(true);
|
||||
endZoomToMetrics.SetPaintRequestTime(TimeStamp::Now());
|
||||
if (NS_IsMainThread()) {
|
||||
RequestContentRepaint(endZoomToMetrics, velocity);
|
||||
} else {
|
||||
// use a local var to resolve the function overload
|
||||
auto func = static_cast<void (AsyncPanZoomController::*)(const FrameMetrics&, const ParentLayerPoint&)>
|
||||
(&AsyncPanZoomController::RequestContentRepaint);
|
||||
NS_DispatchToMainThread(
|
||||
NS_NewRunnableMethodWithArgs<FrameMetrics, ParentLayerPoint>(
|
||||
this, func, endZoomToMetrics, velocity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -577,27 +577,19 @@ protected:
|
|||
/**
|
||||
* Utility function to send updated FrameMetrics to Gecko so that it can paint
|
||||
* the displayport area. Calls into GeckoContentController to do the actual
|
||||
* work. Note that only one paint request can be active at a time. If a paint
|
||||
* request is made while a paint is currently happening, it gets queued up. If
|
||||
* a new paint request arrives before a paint is completed, the old request
|
||||
* gets discarded.
|
||||
* work. This call will use the current metrics. If this function is called
|
||||
* from a non-main thread, it will redispatch itself to the main thread, and
|
||||
* use the latest metrics during the redispatch.
|
||||
*/
|
||||
void RequestContentRepaint();
|
||||
|
||||
/**
|
||||
* Tell the paint throttler to request a content repaint with the given
|
||||
* metrics. (Helper function used by RequestContentRepaint.) If aThrottled
|
||||
* is set to false, the repaint request is sent directly without going through
|
||||
* the paint throttler. In particular, the GeckoContentController::RequestContentRepaint
|
||||
* function will be invoked before this function returns.
|
||||
* Send the provided metrics to Gecko to trigger a repaint. This function
|
||||
* may filter duplicate calls with the same metrics. This function must be
|
||||
* called on the main thread.
|
||||
*/
|
||||
void RequestContentRepaint(FrameMetrics& aFrameMetrics);
|
||||
|
||||
/**
|
||||
* Actually send the next pending paint request to gecko.
|
||||
*/
|
||||
void DispatchRepaintRequest(const FrameMetrics& aFrameMetrics,
|
||||
const ParentLayerPoint& aVelocity);
|
||||
void RequestContentRepaint(const FrameMetrics& aFrameMetrics,
|
||||
const ParentLayerPoint& aVelocity);
|
||||
|
||||
/**
|
||||
* Gets the current frame metrics. This is *not* the Gecko copy stored in the
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_layers_APZCBasicTester_h
|
||||
#define mozilla_layers_APZCBasicTester_h
|
||||
|
||||
/**
|
||||
* Defines a test fixture used for testing a single APZC.
|
||||
*/
|
||||
|
||||
#include "APZTestCommon.h"
|
||||
|
||||
class APZCBasicTester : public ::testing::Test {
|
||||
public:
|
||||
explicit APZCBasicTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES)
|
||||
: mGestureBehavior(aGestureBehavior)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void SetUp()
|
||||
{
|
||||
gfxPrefs::GetSingleton();
|
||||
APZThreadUtils::SetThreadAssertionsEnabled(false);
|
||||
APZThreadUtils::SetControllerThread(MessageLoop::current());
|
||||
|
||||
mcc = new NiceMock<MockContentControllerDelayed>();
|
||||
tm = new TestAPZCTreeManager(mcc);
|
||||
apzc = new TestAsyncPanZoomController(0, mcc, tm, mGestureBehavior);
|
||||
apzc->SetFrameMetrics(TestFrameMetrics());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the APZC's scroll range in CSS pixels.
|
||||
*/
|
||||
CSSRect GetScrollRange() const
|
||||
{
|
||||
const FrameMetrics& metrics = apzc->GetFrameMetrics();
|
||||
return CSSRect(
|
||||
metrics.GetScrollableRect().TopLeft(),
|
||||
metrics.GetScrollableRect().Size() - metrics.CalculateCompositedSizeInCssPixels());
|
||||
}
|
||||
|
||||
virtual void TearDown()
|
||||
{
|
||||
while (mcc->RunThroughDelayedTasks());
|
||||
apzc->Destroy();
|
||||
}
|
||||
|
||||
void MakeApzcWaitForMainThread()
|
||||
{
|
||||
apzc->SetWaitForMainThread();
|
||||
}
|
||||
|
||||
void MakeApzcZoomable()
|
||||
{
|
||||
apzc->UpdateZoomConstraints(ZoomConstraints(true, true, CSSToParentLayerScale(0.25f), CSSToParentLayerScale(4.0f)));
|
||||
}
|
||||
|
||||
void MakeApzcUnzoomable()
|
||||
{
|
||||
apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToParentLayerScale(1.0f), CSSToParentLayerScale(1.0f)));
|
||||
}
|
||||
|
||||
void PanIntoOverscroll();
|
||||
|
||||
/**
|
||||
* Sample animations once, 1 ms later than the last sample.
|
||||
*/
|
||||
void SampleAnimationOnce()
|
||||
{
|
||||
const TimeDuration increment = TimeDuration::FromMilliseconds(1);
|
||||
ParentLayerPoint pointOut;
|
||||
AsyncTransform viewTransformOut;
|
||||
mcc->AdvanceBy(increment);
|
||||
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample animations until we recover from overscroll.
|
||||
* @param aExpectedScrollOffset the expected reported scroll offset
|
||||
* throughout the animation
|
||||
*/
|
||||
void SampleAnimationUntilRecoveredFromOverscroll(const ParentLayerPoint& aExpectedScrollOffset)
|
||||
{
|
||||
const TimeDuration increment = TimeDuration::FromMilliseconds(1);
|
||||
bool recoveredFromOverscroll = false;
|
||||
ParentLayerPoint pointOut;
|
||||
AsyncTransform viewTransformOut;
|
||||
while (apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut)) {
|
||||
// The reported scroll offset should be the same throughout.
|
||||
EXPECT_EQ(aExpectedScrollOffset, pointOut);
|
||||
|
||||
// Trigger computation of the overscroll tranform, to make sure
|
||||
// no assetions fire during the calculation.
|
||||
apzc->GetOverscrollTransform();
|
||||
|
||||
if (!apzc->IsOverscrolled()) {
|
||||
recoveredFromOverscroll = true;
|
||||
}
|
||||
|
||||
mcc->AdvanceBy(increment);
|
||||
}
|
||||
EXPECT_TRUE(recoveredFromOverscroll);
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
void TestOverscroll();
|
||||
|
||||
AsyncPanZoomController::GestureBehavior mGestureBehavior;
|
||||
RefPtr<MockContentControllerDelayed> mcc;
|
||||
RefPtr<TestAPZCTreeManager> tm;
|
||||
RefPtr<TestAsyncPanZoomController> apzc;
|
||||
};
|
||||
|
||||
#endif // mozilla_layers_APZCBasicTester_h
|
|
@ -0,0 +1,167 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_layers_APZCTreeManagerTester_h
|
||||
#define mozilla_layers_APZCTreeManagerTester_h
|
||||
|
||||
/**
|
||||
* Defines a test fixture used for testing multiple APZCs interacting in
|
||||
* an APZCTreeManager.
|
||||
*/
|
||||
|
||||
#include "APZTestCommon.h"
|
||||
|
||||
class APZCTreeManagerTester : public ::testing::Test {
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
gfxPrefs::GetSingleton();
|
||||
APZThreadUtils::SetThreadAssertionsEnabled(false);
|
||||
APZThreadUtils::SetControllerThread(MessageLoop::current());
|
||||
|
||||
mcc = new NiceMock<MockContentControllerDelayed>();
|
||||
manager = new TestAPZCTreeManager(mcc);
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
while (mcc->RunThroughDelayedTasks());
|
||||
manager->ClearTree();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample animations once for all APZCs, 1 ms later than the last sample.
|
||||
*/
|
||||
void SampleAnimationsOnce() {
|
||||
const TimeDuration increment = TimeDuration::FromMilliseconds(1);
|
||||
ParentLayerPoint pointOut;
|
||||
AsyncTransform viewTransformOut;
|
||||
mcc->AdvanceBy(increment);
|
||||
|
||||
for (const RefPtr<Layer>& layer : layers) {
|
||||
if (TestAsyncPanZoomController* apzc = ApzcOf(layer)) {
|
||||
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<MockContentControllerDelayed> mcc;
|
||||
|
||||
nsTArray<RefPtr<Layer> > layers;
|
||||
RefPtr<LayerManager> lm;
|
||||
RefPtr<Layer> root;
|
||||
|
||||
RefPtr<TestAPZCTreeManager> manager;
|
||||
|
||||
protected:
|
||||
static void SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId,
|
||||
CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1)) {
|
||||
FrameMetrics metrics;
|
||||
metrics.SetScrollId(aScrollId);
|
||||
// By convention in this test file, START_SCROLL_ID is the root, so mark it as such.
|
||||
if (aScrollId == FrameMetrics::START_SCROLL_ID) {
|
||||
metrics.SetIsLayersIdRoot(true);
|
||||
}
|
||||
IntRect layerBound = aLayer->GetVisibleRegion().ToUnknownRegion().GetBounds();
|
||||
metrics.SetCompositionBounds(ParentLayerRect(layerBound.x, layerBound.y,
|
||||
layerBound.width, layerBound.height));
|
||||
metrics.SetScrollableRect(aScrollableRect);
|
||||
metrics.SetScrollOffset(CSSPoint(0, 0));
|
||||
metrics.SetPageScrollAmount(LayoutDeviceIntSize(50, 100));
|
||||
metrics.SetAllowVerticalScrollWithWheel(true);
|
||||
aLayer->SetFrameMetrics(metrics);
|
||||
aLayer->SetClipRect(Some(ViewAs<ParentLayerPixel>(layerBound)));
|
||||
if (!aScrollableRect.IsEqualEdges(CSSRect(-1, -1, -1, -1))) {
|
||||
// The purpose of this is to roughly mimic what layout would do in the
|
||||
// case of a scrollable frame with the event regions and clip. This lets
|
||||
// us exercise the hit-testing code in APZCTreeManager
|
||||
EventRegions er = aLayer->GetEventRegions();
|
||||
IntRect scrollRect = RoundedToInt(aScrollableRect * metrics.LayersPixelsPerCSSPixel()).ToUnknownRect();
|
||||
er.mHitRegion = nsIntRegion(IntRect(layerBound.TopLeft(), scrollRect.Size()));
|
||||
aLayer->SetEventRegions(er);
|
||||
}
|
||||
}
|
||||
|
||||
void SetScrollHandoff(Layer* aChild, Layer* aParent) {
|
||||
FrameMetrics metrics = aChild->GetFrameMetrics(0);
|
||||
metrics.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId());
|
||||
aChild->SetFrameMetrics(metrics);
|
||||
}
|
||||
|
||||
static TestAsyncPanZoomController* ApzcOf(Layer* aLayer) {
|
||||
EXPECT_EQ(1u, aLayer->GetFrameMetricsCount());
|
||||
return (TestAsyncPanZoomController*)aLayer->GetAsyncPanZoomController(0);
|
||||
}
|
||||
|
||||
void CreateSimpleScrollingLayer() {
|
||||
const char* layerTreeSyntax = "t";
|
||||
nsIntRegion layerVisibleRegion[] = {
|
||||
nsIntRegion(IntRect(0,0,200,200)),
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 500, 500));
|
||||
}
|
||||
|
||||
void CreateSimpleDTCScrollingLayer() {
|
||||
const char* layerTreeSyntax = "t";
|
||||
nsIntRegion layerVisibleRegion[] = {
|
||||
nsIntRegion(IntRect(0,0,200,200)),
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 500, 500));
|
||||
|
||||
EventRegions regions;
|
||||
regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 200));
|
||||
regions.mDispatchToContentHitRegion = regions.mHitRegion;
|
||||
layers[0]->SetEventRegions(regions);
|
||||
}
|
||||
|
||||
void CreateSimpleMultiLayerTree() {
|
||||
const char* layerTreeSyntax = "c(tt)";
|
||||
// LayerID 0 12
|
||||
nsIntRegion layerVisibleRegion[] = {
|
||||
nsIntRegion(IntRect(0,0,100,100)),
|
||||
nsIntRegion(IntRect(0,0,100,50)),
|
||||
nsIntRegion(IntRect(0,50,100,50)),
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
|
||||
}
|
||||
|
||||
void CreatePotentiallyLeakingTree() {
|
||||
const char* layerTreeSyntax = "c(c(c(t))c(c(t)))";
|
||||
// LayerID 0 1 2 3 4 5 6
|
||||
root = CreateLayerTree(layerTreeSyntax, nullptr, nullptr, lm, layers);
|
||||
SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID);
|
||||
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1);
|
||||
SetScrollableFrameMetrics(layers[5], FrameMetrics::START_SCROLL_ID + 1);
|
||||
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2);
|
||||
SetScrollableFrameMetrics(layers[6], FrameMetrics::START_SCROLL_ID + 3);
|
||||
}
|
||||
|
||||
void CreateBug1194876Tree() {
|
||||
const char* layerTreeSyntax = "c(t)";
|
||||
// LayerID 0 1
|
||||
nsIntRegion layerVisibleRegion[] = {
|
||||
nsIntRegion(IntRect(0,0,100,100)),
|
||||
nsIntRegion(IntRect(0,0,100,100)),
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
|
||||
SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID);
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
|
||||
|
||||
// Make layers[1] the root content
|
||||
FrameMetrics childMetrics = layers[1]->GetFrameMetrics(0);
|
||||
childMetrics.SetIsRootContent(true);
|
||||
layers[1]->SetFrameMetrics(childMetrics);
|
||||
|
||||
// Both layers are fully dispatch-to-content
|
||||
EventRegions regions;
|
||||
regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
|
||||
regions.mDispatchToContentHitRegion = regions.mHitRegion;
|
||||
layers[0]->SetEventRegions(regions);
|
||||
layers[1]->SetEventRegions(regions);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // mozilla_layers_APZCTreeManagerTester_h
|
|
@ -0,0 +1,297 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_layers_APZTestCommon_h
|
||||
#define mozilla_layers_APZTestCommon_h
|
||||
|
||||
/**
|
||||
* Defines a set of mock classes and utility functions/classes for
|
||||
* writing APZ gtests.
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
|
||||
#include "mozilla/layers/GeckoContentController.h"
|
||||
#include "mozilla/layers/CompositorParent.h"
|
||||
#include "mozilla/layers/APZCTreeManager.h"
|
||||
#include "mozilla/layers/LayerMetricsWrapper.h"
|
||||
#include "mozilla/layers/APZThreadUtils.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "apz/src/AsyncPanZoomController.h"
|
||||
#include "apz/src/HitTestingTreeNode.h"
|
||||
#include "base/task.h"
|
||||
#include "Layers.h"
|
||||
#include "TestLayers.h"
|
||||
#include "UnitTransforms.h"
|
||||
#include "gfxPrefs.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::layers;
|
||||
using ::testing::_;
|
||||
using ::testing::NiceMock;
|
||||
using ::testing::AtLeast;
|
||||
using ::testing::AtMost;
|
||||
using ::testing::MockFunction;
|
||||
using ::testing::InSequence;
|
||||
|
||||
class Task;
|
||||
|
||||
template<class T>
|
||||
class ScopedGfxPref {
|
||||
public:
|
||||
ScopedGfxPref(T (*aGetPrefFunc)(void), void (*aSetPrefFunc)(T), T aVal)
|
||||
: mSetPrefFunc(aSetPrefFunc)
|
||||
{
|
||||
mOldVal = aGetPrefFunc();
|
||||
aSetPrefFunc(aVal);
|
||||
}
|
||||
|
||||
~ScopedGfxPref() {
|
||||
mSetPrefFunc(mOldVal);
|
||||
}
|
||||
|
||||
private:
|
||||
void (*mSetPrefFunc)(T);
|
||||
T mOldVal;
|
||||
};
|
||||
|
||||
#define SCOPED_GFX_PREF(prefBase, prefType, prefValue) \
|
||||
ScopedGfxPref<prefType> pref_##prefBase( \
|
||||
&(gfxPrefs::prefBase), \
|
||||
&(gfxPrefs::Set##prefBase), \
|
||||
prefValue)
|
||||
|
||||
class MockContentController : public GeckoContentController {
|
||||
public:
|
||||
MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&));
|
||||
MOCK_METHOD2(RequestFlingSnap, void(const FrameMetrics::ViewID& aScrollId, const mozilla::CSSPoint& aDestination));
|
||||
MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration));
|
||||
MOCK_METHOD3(HandleDoubleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&));
|
||||
MOCK_METHOD3(HandleSingleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&));
|
||||
MOCK_METHOD4(HandleLongTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&, uint64_t));
|
||||
MOCK_METHOD2(PostDelayedTask, void(Task* aTask, int aDelayMs));
|
||||
MOCK_METHOD3(NotifyAPZStateChange, void(const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg));
|
||||
MOCK_METHOD0(NotifyFlushComplete, void());
|
||||
};
|
||||
|
||||
class MockContentControllerDelayed : public MockContentController {
|
||||
public:
|
||||
MockContentControllerDelayed()
|
||||
: mTime(TimeStamp::Now())
|
||||
{
|
||||
}
|
||||
|
||||
const TimeStamp& Time() {
|
||||
return mTime;
|
||||
}
|
||||
|
||||
void AdvanceByMillis(int aMillis) {
|
||||
AdvanceBy(TimeDuration::FromMilliseconds(aMillis));
|
||||
}
|
||||
|
||||
void AdvanceBy(const TimeDuration& aIncrement) {
|
||||
TimeStamp target = mTime + aIncrement;
|
||||
while (mTaskQueue.Length() > 0 && mTaskQueue[0].second <= target) {
|
||||
RunNextDelayedTask();
|
||||
}
|
||||
mTime = target;
|
||||
}
|
||||
|
||||
void PostDelayedTask(Task* aTask, int aDelayMs) {
|
||||
TimeStamp runAtTime = mTime + TimeDuration::FromMilliseconds(aDelayMs);
|
||||
int insIndex = mTaskQueue.Length();
|
||||
while (insIndex > 0) {
|
||||
if (mTaskQueue[insIndex - 1].second <= runAtTime) {
|
||||
break;
|
||||
}
|
||||
insIndex--;
|
||||
}
|
||||
mTaskQueue.InsertElementAt(insIndex, std::make_pair(aTask, runAtTime));
|
||||
}
|
||||
|
||||
// Run all the tasks in the queue, returning the number of tasks
|
||||
// run. Note that if a task queues another task while running, that
|
||||
// new task will not be run. Therefore, there may be still be tasks
|
||||
// in the queue after this function is called. Only when the return
|
||||
// value is 0 is the queue guaranteed to be empty.
|
||||
int RunThroughDelayedTasks() {
|
||||
nsTArray<std::pair<Task*, TimeStamp>> runQueue;
|
||||
runQueue.SwapElements(mTaskQueue);
|
||||
int numTasks = runQueue.Length();
|
||||
for (int i = 0; i < numTasks; i++) {
|
||||
mTime = runQueue[i].second;
|
||||
runQueue[i].first->Run();
|
||||
|
||||
// Deleting the task is important in order to release the reference to
|
||||
// the callee object.
|
||||
delete runQueue[i].first;
|
||||
}
|
||||
return numTasks;
|
||||
}
|
||||
|
||||
private:
|
||||
void RunNextDelayedTask() {
|
||||
std::pair<Task*, TimeStamp> next = mTaskQueue[0];
|
||||
mTaskQueue.RemoveElementAt(0);
|
||||
mTime = next.second;
|
||||
next.first->Run();
|
||||
// Deleting the task is important in order to release the reference to
|
||||
// the callee object.
|
||||
delete next.first;
|
||||
}
|
||||
|
||||
// The following array is sorted by timestamp (tasks are inserted in order by
|
||||
// timestamp).
|
||||
nsTArray<std::pair<Task*, TimeStamp>> mTaskQueue;
|
||||
TimeStamp mTime;
|
||||
};
|
||||
|
||||
class TestAPZCTreeManager : public APZCTreeManager {
|
||||
public:
|
||||
explicit TestAPZCTreeManager(MockContentControllerDelayed* aMcc) : mcc(aMcc) {}
|
||||
|
||||
RefPtr<InputQueue> GetInputQueue() const {
|
||||
return mInputQueue;
|
||||
}
|
||||
|
||||
protected:
|
||||
AsyncPanZoomController* NewAPZCInstance(uint64_t aLayersId,
|
||||
GeckoContentController* aController) override;
|
||||
|
||||
TimeStamp GetFrameTime() override {
|
||||
return mcc->Time();
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<MockContentControllerDelayed> mcc;
|
||||
};
|
||||
|
||||
class TestAsyncPanZoomController : public AsyncPanZoomController {
|
||||
public:
|
||||
TestAsyncPanZoomController(uint64_t aLayersId, MockContentControllerDelayed* aMcc,
|
||||
TestAPZCTreeManager* aTreeManager,
|
||||
GestureBehavior aBehavior = DEFAULT_GESTURES)
|
||||
: AsyncPanZoomController(aLayersId, aTreeManager, aTreeManager->GetInputQueue(),
|
||||
aMcc, aBehavior)
|
||||
, mWaitForMainThread(false)
|
||||
, mcc(aMcc)
|
||||
{}
|
||||
|
||||
nsEventStatus ReceiveInputEvent(const InputData& aEvent, ScrollableLayerGuid* aDummy, uint64_t* aOutInputBlockId) {
|
||||
// This is a function whose signature matches exactly the ReceiveInputEvent
|
||||
// on APZCTreeManager. This allows us to templates for functions like
|
||||
// TouchDown, TouchUp, etc so that we can reuse the code for dispatching
|
||||
// events into both APZC and APZCTM.
|
||||
return ReceiveInputEvent(aEvent, aOutInputBlockId);
|
||||
}
|
||||
|
||||
nsEventStatus ReceiveInputEvent(const InputData& aEvent, uint64_t* aOutInputBlockId) {
|
||||
return GetInputQueue()->ReceiveInputEvent(this, !mWaitForMainThread, aEvent, aOutInputBlockId);
|
||||
}
|
||||
|
||||
void ContentReceivedInputBlock(uint64_t aInputBlockId, bool aPreventDefault) {
|
||||
GetInputQueue()->ContentReceivedInputBlock(aInputBlockId, aPreventDefault);
|
||||
}
|
||||
|
||||
void ConfirmTarget(uint64_t aInputBlockId) {
|
||||
RefPtr<AsyncPanZoomController> target = this;
|
||||
GetInputQueue()->SetConfirmedTargetApzc(aInputBlockId, target);
|
||||
}
|
||||
|
||||
void SetAllowedTouchBehavior(uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aBehaviors) {
|
||||
GetInputQueue()->SetAllowedTouchBehavior(aInputBlockId, aBehaviors);
|
||||
}
|
||||
|
||||
void SetFrameMetrics(const FrameMetrics& metrics) {
|
||||
ReentrantMonitorAutoEnter lock(mMonitor);
|
||||
mFrameMetrics = metrics;
|
||||
}
|
||||
|
||||
FrameMetrics& GetFrameMetrics() {
|
||||
ReentrantMonitorAutoEnter lock(mMonitor);
|
||||
return mFrameMetrics;
|
||||
}
|
||||
|
||||
const FrameMetrics& GetFrameMetrics() const {
|
||||
ReentrantMonitorAutoEnter lock(mMonitor);
|
||||
return mFrameMetrics;
|
||||
}
|
||||
|
||||
using AsyncPanZoomController::GetVelocityVector;
|
||||
|
||||
void AssertStateIsReset() const {
|
||||
ReentrantMonitorAutoEnter lock(mMonitor);
|
||||
EXPECT_EQ(NOTHING, mState);
|
||||
}
|
||||
|
||||
void AssertStateIsFling() const {
|
||||
ReentrantMonitorAutoEnter lock(mMonitor);
|
||||
EXPECT_EQ(FLING, mState);
|
||||
}
|
||||
|
||||
void AdvanceAnimationsUntilEnd(const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(10)) {
|
||||
while (AdvanceAnimations(mcc->Time())) {
|
||||
mcc->AdvanceBy(aIncrement);
|
||||
}
|
||||
}
|
||||
|
||||
bool SampleContentTransformForFrame(AsyncTransform* aOutTransform,
|
||||
ParentLayerPoint& aScrollOffset,
|
||||
const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(0)) {
|
||||
mcc->AdvanceBy(aIncrement);
|
||||
bool ret = AdvanceAnimations(mcc->Time());
|
||||
AsyncPanZoomController::SampleContentTransformForFrame(
|
||||
aOutTransform, aScrollOffset);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SetWaitForMainThread() {
|
||||
mWaitForMainThread = true;
|
||||
}
|
||||
|
||||
static TimeStamp GetStartupTime() {
|
||||
static TimeStamp sStartupTime = TimeStamp::Now();
|
||||
return sStartupTime;
|
||||
}
|
||||
|
||||
private:
|
||||
bool mWaitForMainThread;
|
||||
MockContentControllerDelayed* mcc;
|
||||
};
|
||||
|
||||
AsyncPanZoomController*
|
||||
TestAPZCTreeManager::NewAPZCInstance(uint64_t aLayersId,
|
||||
GeckoContentController* aController)
|
||||
{
|
||||
MockContentControllerDelayed* mcc = static_cast<MockContentControllerDelayed*>(aController);
|
||||
return new TestAsyncPanZoomController(aLayersId, mcc, this,
|
||||
AsyncPanZoomController::USE_GESTURE_DETECTOR);
|
||||
}
|
||||
|
||||
FrameMetrics
|
||||
TestFrameMetrics()
|
||||
{
|
||||
FrameMetrics fm;
|
||||
|
||||
fm.SetDisplayPort(CSSRect(0, 0, 10, 10));
|
||||
fm.SetCompositionBounds(ParentLayerRect(0, 0, 10, 10));
|
||||
fm.SetCriticalDisplayPort(CSSRect(0, 0, 10, 10));
|
||||
fm.SetScrollableRect(CSSRect(0, 0, 100, 100));
|
||||
|
||||
return fm;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
MillisecondsSinceStartup(TimeStamp aTime)
|
||||
{
|
||||
return (aTime - TestAsyncPanZoomController::GetStartupTime()).ToMilliseconds();
|
||||
}
|
||||
|
||||
#endif // mozilla_layers_APZTestCommon_h
|
|
@ -0,0 +1,466 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_layers_InputUtils_h
|
||||
#define mozilla_layers_InputUtils_h
|
||||
|
||||
/**
|
||||
* Defines a set of utility functions for generating input events
|
||||
* to an APZC/APZCTM during APZ gtests.
|
||||
*/
|
||||
|
||||
#include "APZTestCommon.h"
|
||||
|
||||
/* The InputReceiver template parameter used in the helper functions below needs
|
||||
* to be a class that implements functions with the signatures:
|
||||
* nsEventStatus ReceiveInputEvent(const InputData& aEvent,
|
||||
* ScrollableLayerGuid* aGuid,
|
||||
* uint64_t* aOutInputBlockId);
|
||||
* void SetAllowedTouchBehavior(uint64_t aInputBlockId,
|
||||
* const nsTArray<uint32_t>& aBehaviours);
|
||||
* The classes that currently implement these are APZCTreeManager and
|
||||
* TestAsyncPanZoomController. Using this template allows us to test individual
|
||||
* APZC instances in isolation and also an entire APZ tree, while using the same
|
||||
* code to dispatch input events.
|
||||
*/
|
||||
|
||||
// Some helper functions for constructing input event objects suitable to be
|
||||
// passed either to an APZC (which expects an transformed point), or to an APZTM
|
||||
// (which expects an untransformed point). We handle both cases by setting both
|
||||
// the transformed and untransformed fields to the same value.
|
||||
SingleTouchData
|
||||
CreateSingleTouchData(int32_t aIdentifier, int aX, int aY)
|
||||
{
|
||||
SingleTouchData touch(aIdentifier, ScreenIntPoint(aX, aY), ScreenSize(0, 0), 0, 0);
|
||||
touch.mLocalScreenPoint = ParentLayerPoint(aX, aY);
|
||||
return touch;
|
||||
}
|
||||
|
||||
PinchGestureInput
|
||||
CreatePinchGestureInput(PinchGestureInput::PinchGestureType aType,
|
||||
int aFocusX, int aFocusY,
|
||||
float aCurrentSpan, float aPreviousSpan)
|
||||
{
|
||||
PinchGestureInput result(aType, 0, TimeStamp(), ScreenPoint(aFocusX, aFocusY),
|
||||
aCurrentSpan, aPreviousSpan, 0);
|
||||
result.mLocalFocusPoint = ParentLayerPoint(aFocusX, aFocusY);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<class InputReceiver>
|
||||
void
|
||||
SetDefaultAllowedTouchBehavior(const RefPtr<InputReceiver>& aTarget,
|
||||
uint64_t aInputBlockId,
|
||||
int touchPoints = 1)
|
||||
{
|
||||
nsTArray<uint32_t> defaultBehaviors;
|
||||
// use the default value where everything is allowed
|
||||
for (int i = 0; i < touchPoints; i++) {
|
||||
defaultBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
|
||||
| mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
|
||||
| mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM
|
||||
| mozilla::layers::AllowedTouchBehavior::DOUBLE_TAP_ZOOM);
|
||||
}
|
||||
aTarget->SetAllowedTouchBehavior(aInputBlockId, defaultBehaviors);
|
||||
}
|
||||
|
||||
|
||||
MultiTouchInput
|
||||
CreateMultiTouchInput(MultiTouchInput::MultiTouchType aType, TimeStamp aTime)
|
||||
{
|
||||
return MultiTouchInput(aType, MillisecondsSinceStartup(aTime), aTime, 0);
|
||||
}
|
||||
|
||||
template<class InputReceiver>
|
||||
nsEventStatus
|
||||
TouchDown(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr)
|
||||
{
|
||||
MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY));
|
||||
return aTarget->ReceiveInputEvent(mti, nullptr, aOutInputBlockId);
|
||||
}
|
||||
|
||||
template<class InputReceiver>
|
||||
nsEventStatus
|
||||
TouchMove(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime)
|
||||
{
|
||||
MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY));
|
||||
return aTarget->ReceiveInputEvent(mti, nullptr, nullptr);
|
||||
}
|
||||
|
||||
template<class InputReceiver>
|
||||
nsEventStatus
|
||||
TouchUp(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime)
|
||||
{
|
||||
MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY));
|
||||
return aTarget->ReceiveInputEvent(mti, nullptr, nullptr);
|
||||
}
|
||||
|
||||
template<class InputReceiver>
|
||||
void
|
||||
Tap(const RefPtr<InputReceiver>& aTarget, int aX, int aY, MockContentControllerDelayed* aMcc,
|
||||
TimeDuration aTapLength,
|
||||
nsEventStatus (*aOutEventStatuses)[2] = nullptr,
|
||||
uint64_t* aOutInputBlockId = nullptr)
|
||||
{
|
||||
// Even if the caller doesn't care about the block id, we need it to set the
|
||||
// allowed touch behaviour below, so make sure aOutInputBlockId is non-null.
|
||||
uint64_t blockId;
|
||||
if (!aOutInputBlockId) {
|
||||
aOutInputBlockId = &blockId;
|
||||
}
|
||||
|
||||
nsEventStatus status = TouchDown(aTarget, aX, aY, aMcc->Time(), aOutInputBlockId);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[0] = status;
|
||||
}
|
||||
aMcc->AdvanceBy(aTapLength);
|
||||
|
||||
// If touch-action is enabled then simulate the allowed touch behaviour
|
||||
// notification that the main thread is supposed to deliver.
|
||||
if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
|
||||
SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId);
|
||||
}
|
||||
|
||||
status = TouchUp(aTarget, aX, aY, aMcc->Time());
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[1] = status;
|
||||
}
|
||||
}
|
||||
|
||||
template<class InputReceiver>
|
||||
void
|
||||
TapAndCheckStatus(const RefPtr<InputReceiver>& aTarget, int aX, int aY,
|
||||
MockContentControllerDelayed* aMcc, TimeDuration aTapLength)
|
||||
{
|
||||
nsEventStatus statuses[2];
|
||||
Tap(aTarget, aX, aY, aMcc, aTapLength, &statuses);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]);
|
||||
}
|
||||
|
||||
template<class InputReceiver>
|
||||
void
|
||||
Pan(const RefPtr<InputReceiver>& aTarget,
|
||||
MockContentControllerDelayed* aMcc,
|
||||
const ScreenPoint& aTouchStart,
|
||||
const ScreenPoint& aTouchEnd,
|
||||
bool aKeepFingerDown = false,
|
||||
nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
|
||||
nsEventStatus (*aOutEventStatuses)[4] = nullptr,
|
||||
uint64_t* aOutInputBlockId = nullptr)
|
||||
{
|
||||
// Reduce the touch start and move tolerance to a tiny value.
|
||||
// We can't use a scoped pref because this value might be read at some later
|
||||
// time when the events are actually processed, rather than when we deliver
|
||||
// them.
|
||||
gfxPrefs::SetAPZTouchStartTolerance(1.0f / 1000.0f);
|
||||
gfxPrefs::SetAPZTouchMoveTolerance(0.0f);
|
||||
const int OVERCOME_TOUCH_TOLERANCE = 1;
|
||||
|
||||
const TimeDuration TIME_BETWEEN_TOUCH_EVENT = TimeDuration::FromMilliseconds(50);
|
||||
|
||||
// Even if the caller doesn't care about the block id, we need it to set the
|
||||
// allowed touch behaviour below, so make sure aOutInputBlockId is non-null.
|
||||
uint64_t blockId;
|
||||
if (!aOutInputBlockId) {
|
||||
aOutInputBlockId = &blockId;
|
||||
}
|
||||
|
||||
// Make sure the move is large enough to not be handled as a tap
|
||||
nsEventStatus status = TouchDown(aTarget, aTouchStart.x, aTouchStart.y + OVERCOME_TOUCH_TOLERANCE, aMcc->Time(), aOutInputBlockId);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[0] = status;
|
||||
}
|
||||
|
||||
aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
|
||||
|
||||
// Allowed touch behaviours must be set after sending touch-start.
|
||||
if (status != nsEventStatus_eConsumeNoDefault) {
|
||||
if (aAllowedTouchBehaviors) {
|
||||
EXPECT_EQ(1UL, aAllowedTouchBehaviors->Length());
|
||||
aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, *aAllowedTouchBehaviors);
|
||||
} else if (gfxPrefs::TouchActionEnabled()) {
|
||||
SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId);
|
||||
}
|
||||
}
|
||||
|
||||
status = TouchMove(aTarget, aTouchStart.x, aTouchStart.y, aMcc->Time());
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[1] = status;
|
||||
}
|
||||
|
||||
aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
|
||||
|
||||
status = TouchMove(aTarget, aTouchEnd.x, aTouchEnd.y, aMcc->Time());
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[2] = status;
|
||||
}
|
||||
|
||||
aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
|
||||
|
||||
if (!aKeepFingerDown) {
|
||||
status = TouchUp(aTarget, aTouchEnd.x, aTouchEnd.y, aMcc->Time());
|
||||
} else {
|
||||
status = nsEventStatus_eIgnore;
|
||||
}
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[3] = status;
|
||||
}
|
||||
|
||||
// Don't increment the time here. Animations started on touch-up, such as
|
||||
// flings, are affected by elapsed time, and we want to be able to sample
|
||||
// them immediately after they start, without time having elapsed.
|
||||
}
|
||||
|
||||
// A version of Pan() that only takes y coordinates rather than (x, y) points
|
||||
// for the touch start and end points, and uses 10 for the x coordinates.
|
||||
// This is for convenience, as most tests only need to pan in one direction.
|
||||
template<class InputReceiver>
|
||||
void
|
||||
Pan(const RefPtr<InputReceiver>& aTarget,
|
||||
MockContentControllerDelayed* aMcc,
|
||||
int aTouchStartY,
|
||||
int aTouchEndY,
|
||||
bool aKeepFingerDown = false,
|
||||
nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
|
||||
nsEventStatus (*aOutEventStatuses)[4] = nullptr,
|
||||
uint64_t* aOutInputBlockId = nullptr)
|
||||
{
|
||||
::Pan(aTarget, aMcc, ScreenPoint(10, aTouchStartY), ScreenPoint(10, aTouchEndY),
|
||||
aKeepFingerDown, aAllowedTouchBehaviors, aOutEventStatuses, aOutInputBlockId);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dispatches mock touch events to the apzc and checks whether apzc properly
|
||||
* consumed them and triggered scrolling behavior.
|
||||
*/
|
||||
template<class InputReceiver>
|
||||
void
|
||||
PanAndCheckStatus(const RefPtr<InputReceiver>& aTarget,
|
||||
MockContentControllerDelayed* aMcc,
|
||||
int aTouchStartY,
|
||||
int aTouchEndY,
|
||||
bool aExpectConsumed,
|
||||
nsTArray<uint32_t>* aAllowedTouchBehaviors,
|
||||
uint64_t* aOutInputBlockId = nullptr)
|
||||
{
|
||||
nsEventStatus statuses[4]; // down, move, move, up
|
||||
Pan(aTarget, aMcc, aTouchStartY, aTouchEndY, false, aAllowedTouchBehaviors, &statuses, aOutInputBlockId);
|
||||
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
|
||||
|
||||
nsEventStatus touchMoveStatus;
|
||||
if (aExpectConsumed) {
|
||||
touchMoveStatus = nsEventStatus_eConsumeDoDefault;
|
||||
} else {
|
||||
touchMoveStatus = nsEventStatus_eIgnore;
|
||||
}
|
||||
EXPECT_EQ(touchMoveStatus, statuses[1]);
|
||||
EXPECT_EQ(touchMoveStatus, statuses[2]);
|
||||
}
|
||||
|
||||
void
|
||||
ApzcPanNoFling(const RefPtr<TestAsyncPanZoomController>& aApzc,
|
||||
MockContentControllerDelayed* aMcc,
|
||||
int aTouchStartY,
|
||||
int aTouchEndY,
|
||||
uint64_t* aOutInputBlockId = nullptr)
|
||||
{
|
||||
Pan(aApzc, aMcc, aTouchStartY, aTouchEndY, false, nullptr, nullptr, aOutInputBlockId);
|
||||
aApzc->CancelAnimation();
|
||||
}
|
||||
|
||||
template<class InputReceiver>
|
||||
void
|
||||
PinchWithPinchInput(const RefPtr<InputReceiver>& aTarget,
|
||||
int aFocusX, int aFocusY, int aSecondFocusX, int aSecondFocusY, float aScale,
|
||||
nsEventStatus (*aOutEventStatuses)[3] = nullptr)
|
||||
{
|
||||
nsEventStatus actualStatus = aTarget->ReceiveInputEvent(
|
||||
CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_START,
|
||||
aFocusX, aFocusY, 10.0, 10.0),
|
||||
nullptr);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[0] = actualStatus;
|
||||
}
|
||||
actualStatus = aTarget->ReceiveInputEvent(
|
||||
CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE,
|
||||
aSecondFocusX, aSecondFocusY, 10.0 * aScale, 10.0),
|
||||
nullptr);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[1] = actualStatus;
|
||||
}
|
||||
actualStatus = aTarget->ReceiveInputEvent(
|
||||
CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_END,
|
||||
// note: negative values here tell APZC
|
||||
// not to turn the pinch into a pan
|
||||
aFocusX, aFocusY, -1.0, -1.0),
|
||||
nullptr);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[2] = actualStatus;
|
||||
}
|
||||
}
|
||||
|
||||
template<class InputReceiver>
|
||||
void
|
||||
PinchWithPinchInputAndCheckStatus(const RefPtr<InputReceiver>& aTarget,
|
||||
int aFocusX, int aFocusY, float aScale,
|
||||
bool aShouldTriggerPinch)
|
||||
{
|
||||
nsEventStatus statuses[3]; // scalebegin, scale, scaleend
|
||||
PinchWithPinchInput(aTarget, aFocusX, aFocusY, aFocusX, aFocusY, aScale, &statuses);
|
||||
|
||||
nsEventStatus expectedStatus = aShouldTriggerPinch
|
||||
? nsEventStatus_eConsumeNoDefault
|
||||
: nsEventStatus_eIgnore;
|
||||
EXPECT_EQ(expectedStatus, statuses[0]);
|
||||
EXPECT_EQ(expectedStatus, statuses[1]);
|
||||
}
|
||||
|
||||
template<class InputReceiver>
|
||||
void
|
||||
PinchWithTouchInput(const RefPtr<InputReceiver>& aTarget,
|
||||
int aFocusX, int aFocusY, float aScale,
|
||||
int& inputId,
|
||||
nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
|
||||
nsEventStatus (*aOutEventStatuses)[4] = nullptr,
|
||||
uint64_t* aOutInputBlockId = nullptr)
|
||||
{
|
||||
// Having pinch coordinates in float type may cause problems with high-precision scale values
|
||||
// since SingleTouchData accepts integer value. But for trivial tests it should be ok.
|
||||
float pinchLength = 100.0;
|
||||
float pinchLengthScaled = pinchLength * aScale;
|
||||
|
||||
// Even if the caller doesn't care about the block id, we need it to set the
|
||||
// allowed touch behaviour below, so make sure aOutInputBlockId is non-null.
|
||||
uint64_t blockId;
|
||||
if (!aOutInputBlockId) {
|
||||
aOutInputBlockId = &blockId;
|
||||
}
|
||||
|
||||
MultiTouchInput mtiStart = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
|
||||
mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX, aFocusY));
|
||||
mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX, aFocusY));
|
||||
nsEventStatus status = aTarget->ReceiveInputEvent(mtiStart, aOutInputBlockId);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[0] = status;
|
||||
}
|
||||
|
||||
if (aAllowedTouchBehaviors) {
|
||||
EXPECT_EQ(2UL, aAllowedTouchBehaviors->Length());
|
||||
aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, *aAllowedTouchBehaviors);
|
||||
} else if (gfxPrefs::TouchActionEnabled()) {
|
||||
SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId, 2);
|
||||
}
|
||||
|
||||
MultiTouchInput mtiMove1 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
|
||||
mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLength, aFocusY));
|
||||
mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLength, aFocusY));
|
||||
status = aTarget->ReceiveInputEvent(mtiMove1, nullptr);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[1] = status;
|
||||
}
|
||||
|
||||
MultiTouchInput mtiMove2 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
|
||||
mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLengthScaled, aFocusY));
|
||||
mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLengthScaled, aFocusY));
|
||||
status = aTarget->ReceiveInputEvent(mtiMove2, nullptr);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[2] = status;
|
||||
}
|
||||
|
||||
MultiTouchInput mtiEnd = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
|
||||
mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLengthScaled, aFocusY));
|
||||
mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLengthScaled, aFocusY));
|
||||
status = aTarget->ReceiveInputEvent(mtiEnd, nullptr);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[3] = status;
|
||||
}
|
||||
|
||||
inputId += 2;
|
||||
}
|
||||
|
||||
template<class InputReceiver>
|
||||
void
|
||||
PinchWithTouchInputAndCheckStatus(const RefPtr<InputReceiver>& aTarget,
|
||||
int aFocusX, int aFocusY, float aScale,
|
||||
int& inputId, bool aShouldTriggerPinch,
|
||||
nsTArray<uint32_t>* aAllowedTouchBehaviors)
|
||||
{
|
||||
nsEventStatus statuses[4]; // down, move, move, up
|
||||
PinchWithTouchInput(aTarget, aFocusX, aFocusY, aScale, inputId, aAllowedTouchBehaviors, &statuses);
|
||||
|
||||
nsEventStatus expectedMoveStatus = aShouldTriggerPinch
|
||||
? nsEventStatus_eConsumeDoDefault
|
||||
: nsEventStatus_eIgnore;
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
|
||||
EXPECT_EQ(expectedMoveStatus, statuses[1]);
|
||||
EXPECT_EQ(expectedMoveStatus, statuses[2]);
|
||||
}
|
||||
|
||||
template<class InputReceiver>
|
||||
void
|
||||
DoubleTap(const RefPtr<InputReceiver>& aTarget, int aX, int aY, MockContentControllerDelayed* aMcc,
|
||||
nsEventStatus (*aOutEventStatuses)[4] = nullptr,
|
||||
uint64_t (*aOutInputBlockIds)[2] = nullptr)
|
||||
{
|
||||
uint64_t blockId;
|
||||
nsEventStatus status = TouchDown(aTarget, aX, aY, aMcc->Time(), &blockId);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[0] = status;
|
||||
}
|
||||
if (aOutInputBlockIds) {
|
||||
(*aOutInputBlockIds)[0] = blockId;
|
||||
}
|
||||
aMcc->AdvanceByMillis(10);
|
||||
|
||||
// If touch-action is enabled then simulate the allowed touch behaviour
|
||||
// notification that the main thread is supposed to deliver.
|
||||
if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
|
||||
SetDefaultAllowedTouchBehavior(aTarget, blockId);
|
||||
}
|
||||
|
||||
status = TouchUp(aTarget, aX, aY, aMcc->Time());
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[1] = status;
|
||||
}
|
||||
aMcc->AdvanceByMillis(10);
|
||||
status = TouchDown(aTarget, aX, aY, aMcc->Time(), &blockId);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[2] = status;
|
||||
}
|
||||
if (aOutInputBlockIds) {
|
||||
(*aOutInputBlockIds)[1] = blockId;
|
||||
}
|
||||
aMcc->AdvanceByMillis(10);
|
||||
|
||||
if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
|
||||
SetDefaultAllowedTouchBehavior(aTarget, blockId);
|
||||
}
|
||||
|
||||
status = TouchUp(aTarget, aX, aY, aMcc->Time());
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[3] = status;
|
||||
}
|
||||
}
|
||||
|
||||
template<class InputReceiver>
|
||||
void
|
||||
DoubleTapAndCheckStatus(const RefPtr<InputReceiver>& aTarget, int aX, int aY,
|
||||
MockContentControllerDelayed* aMcc, uint64_t (*aOutInputBlockIds)[2] = nullptr)
|
||||
{
|
||||
nsEventStatus statuses[4];
|
||||
DoubleTap(aTarget, aX, aY, aMcc, &statuses, aOutInputBlockIds);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[2]);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[3]);
|
||||
}
|
||||
|
||||
#endif // mozilla_layers_InputUtils_h
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,348 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* 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/. */
|
||||
|
||||
#include "APZCBasicTester.h"
|
||||
#include "APZTestCommon.h"
|
||||
#include "InputUtils.h"
|
||||
|
||||
TEST_F(APZCBasicTester, Overzoom) {
|
||||
// the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100
|
||||
FrameMetrics fm;
|
||||
fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
|
||||
fm.SetScrollableRect(CSSRect(0, 0, 125, 150));
|
||||
fm.SetScrollOffset(CSSPoint(10, 0));
|
||||
fm.SetZoom(CSSToParentLayerScale2D(1.0, 1.0));
|
||||
fm.SetIsRootContent(true);
|
||||
apzc->SetFrameMetrics(fm);
|
||||
|
||||
MakeApzcZoomable();
|
||||
|
||||
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
|
||||
|
||||
PinchWithPinchInputAndCheckStatus(apzc, 50, 50, 0.5, true);
|
||||
|
||||
fm = apzc->GetFrameMetrics();
|
||||
EXPECT_EQ(0.8f, fm.GetZoom().ToScaleFactor().scale);
|
||||
// bug 936721 - PGO builds introduce rounding error so
|
||||
// use a fuzzy match instead
|
||||
EXPECT_LT(std::abs(fm.GetScrollOffset().x), 1e-5);
|
||||
EXPECT_LT(std::abs(fm.GetScrollOffset().y), 1e-5);
|
||||
}
|
||||
|
||||
TEST_F(APZCBasicTester, SimpleTransform) {
|
||||
ParentLayerPoint pointOut;
|
||||
AsyncTransform viewTransformOut;
|
||||
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
|
||||
|
||||
EXPECT_EQ(ParentLayerPoint(), pointOut);
|
||||
EXPECT_EQ(AsyncTransform(), viewTransformOut);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(APZCBasicTester, ComplexTransform) {
|
||||
// This test assumes there is a page that gets rendered to
|
||||
// two layers. In CSS pixels, the first layer is 50x50 and
|
||||
// the second layer is 25x50. The widget scale factor is 3.0
|
||||
// and the presShell resolution is 2.0. Therefore, these layers
|
||||
// end up being 300x300 and 150x300 in layer pixels.
|
||||
//
|
||||
// The second (child) layer has an additional CSS transform that
|
||||
// stretches it by 2.0 on the x-axis. Therefore, after applying
|
||||
// CSS transforms, the two layers are the same size in screen
|
||||
// pixels.
|
||||
//
|
||||
// The screen itself is 24x24 in screen pixels (therefore 4x4 in
|
||||
// CSS pixels). The displayport is 1 extra CSS pixel on all
|
||||
// sides.
|
||||
|
||||
RefPtr<TestAsyncPanZoomController> childApzc =
|
||||
new TestAsyncPanZoomController(0, mcc, tm);
|
||||
|
||||
const char* layerTreeSyntax = "c(c)";
|
||||
// LayerID 0 1
|
||||
nsIntRegion layerVisibleRegion[] = {
|
||||
nsIntRegion(IntRect(0, 0, 300, 300)),
|
||||
nsIntRegion(IntRect(0, 0, 150, 300)),
|
||||
};
|
||||
Matrix4x4 transforms[] = {
|
||||
Matrix4x4(),
|
||||
Matrix4x4(),
|
||||
};
|
||||
transforms[0].PostScale(0.5f, 0.5f, 1.0f); // this results from the 2.0 resolution on the root layer
|
||||
transforms[1].PostScale(2.0f, 1.0f, 1.0f); // this is the 2.0 x-axis CSS transform on the child layer
|
||||
|
||||
nsTArray<RefPtr<Layer> > layers;
|
||||
RefPtr<LayerManager> lm;
|
||||
RefPtr<Layer> root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers);
|
||||
|
||||
FrameMetrics metrics;
|
||||
metrics.SetCompositionBounds(ParentLayerRect(0, 0, 24, 24));
|
||||
metrics.SetDisplayPort(CSSRect(-1, -1, 6, 6));
|
||||
metrics.SetScrollOffset(CSSPoint(10, 10));
|
||||
metrics.SetScrollableRect(CSSRect(0, 0, 50, 50));
|
||||
metrics.SetCumulativeResolution(LayoutDeviceToLayerScale2D(2, 2));
|
||||
metrics.SetPresShellResolution(2.0f);
|
||||
metrics.SetZoom(CSSToParentLayerScale2D(6, 6));
|
||||
metrics.SetDevPixelsPerCSSPixel(CSSToLayoutDeviceScale(3));
|
||||
metrics.SetScrollId(FrameMetrics::START_SCROLL_ID);
|
||||
|
||||
FrameMetrics childMetrics = metrics;
|
||||
childMetrics.SetScrollId(FrameMetrics::START_SCROLL_ID + 1);
|
||||
|
||||
layers[0]->SetFrameMetrics(metrics);
|
||||
layers[1]->SetFrameMetrics(childMetrics);
|
||||
|
||||
ParentLayerPoint pointOut;
|
||||
AsyncTransform viewTransformOut;
|
||||
|
||||
// Both the parent and child layer should behave exactly the same here, because
|
||||
// the CSS transform on the child layer does not affect the SampleContentTransformForFrame code
|
||||
|
||||
// initial transform
|
||||
apzc->SetFrameMetrics(metrics);
|
||||
apzc->NotifyLayersUpdated(metrics, true, true);
|
||||
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
|
||||
EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut);
|
||||
EXPECT_EQ(ParentLayerPoint(60, 60), pointOut);
|
||||
|
||||
childApzc->SetFrameMetrics(childMetrics);
|
||||
childApzc->NotifyLayersUpdated(childMetrics, true, true);
|
||||
childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
|
||||
EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut);
|
||||
EXPECT_EQ(ParentLayerPoint(60, 60), pointOut);
|
||||
|
||||
// do an async scroll by 5 pixels and check the transform
|
||||
metrics.ScrollBy(CSSPoint(5, 0));
|
||||
apzc->SetFrameMetrics(metrics);
|
||||
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
|
||||
EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), viewTransformOut);
|
||||
EXPECT_EQ(ParentLayerPoint(90, 60), pointOut);
|
||||
|
||||
childMetrics.ScrollBy(CSSPoint(5, 0));
|
||||
childApzc->SetFrameMetrics(childMetrics);
|
||||
childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
|
||||
EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), viewTransformOut);
|
||||
EXPECT_EQ(ParentLayerPoint(90, 60), pointOut);
|
||||
|
||||
// do an async zoom of 1.5x and check the transform
|
||||
metrics.ZoomBy(1.5f);
|
||||
apzc->SetFrameMetrics(metrics);
|
||||
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
|
||||
EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut);
|
||||
EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);
|
||||
|
||||
childMetrics.ZoomBy(1.5f);
|
||||
childApzc->SetFrameMetrics(childMetrics);
|
||||
childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
|
||||
EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut);
|
||||
EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);
|
||||
|
||||
childApzc->Destroy();
|
||||
}
|
||||
|
||||
TEST_F(APZCBasicTester, Fling) {
|
||||
int touchStart = 50;
|
||||
int touchEnd = 10;
|
||||
ParentLayerPoint pointOut;
|
||||
AsyncTransform viewTransformOut;
|
||||
|
||||
// Fling down. Each step scroll further down
|
||||
Pan(apzc, mcc, touchStart, touchEnd);
|
||||
ParentLayerPoint lastPoint;
|
||||
for (int i = 1; i < 50; i+=1) {
|
||||
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(1));
|
||||
EXPECT_GT(pointOut.y, lastPoint.y);
|
||||
lastPoint = pointOut;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(APZCBasicTester, FlingIntoOverscroll) {
|
||||
// Enable overscrolling.
|
||||
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
|
||||
|
||||
// Scroll down by 25 px. Don't fling for simplicity.
|
||||
ApzcPanNoFling(apzc, mcc, 50, 25);
|
||||
|
||||
// Now scroll back up by 20px, this time flinging after.
|
||||
// The fling should cover the remaining 5 px of room to scroll, then
|
||||
// go into overscroll, and finally snap-back to recover from overscroll.
|
||||
Pan(apzc, mcc, 25, 45);
|
||||
const TimeDuration increment = TimeDuration::FromMilliseconds(1);
|
||||
bool reachedOverscroll = false;
|
||||
bool recoveredFromOverscroll = false;
|
||||
while (apzc->AdvanceAnimations(mcc->Time())) {
|
||||
if (!reachedOverscroll && apzc->IsOverscrolled()) {
|
||||
reachedOverscroll = true;
|
||||
}
|
||||
if (reachedOverscroll && !apzc->IsOverscrolled()) {
|
||||
recoveredFromOverscroll = true;
|
||||
}
|
||||
mcc->AdvanceBy(increment);
|
||||
}
|
||||
EXPECT_TRUE(reachedOverscroll);
|
||||
EXPECT_TRUE(recoveredFromOverscroll);
|
||||
}
|
||||
|
||||
TEST_F(APZCBasicTester, PanningTransformNotifications) {
|
||||
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
|
||||
|
||||
// Scroll down by 25 px. Ensure we only get one set of
|
||||
// state change notifications.
|
||||
//
|
||||
// Then, scroll back up by 20px, this time flinging after.
|
||||
// The fling should cover the remaining 5 px of room to scroll, then
|
||||
// go into overscroll, and finally snap-back to recover from overscroll.
|
||||
// Again, ensure we only get one set of state change notifications for
|
||||
// this entire procedure.
|
||||
|
||||
MockFunction<void(std::string checkPointName)> check;
|
||||
{
|
||||
InSequence s;
|
||||
EXPECT_CALL(check, Call("Simple pan"));
|
||||
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartTouch,_)).Times(1);
|
||||
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformBegin,_)).Times(1);
|
||||
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartPanning,_)).Times(1);
|
||||
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::EndTouch,_)).Times(1);
|
||||
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformEnd,_)).Times(1);
|
||||
EXPECT_CALL(check, Call("Complex pan"));
|
||||
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartTouch,_)).Times(1);
|
||||
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformBegin,_)).Times(1);
|
||||
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartPanning,_)).Times(1);
|
||||
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::EndTouch,_)).Times(1);
|
||||
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformEnd,_)).Times(1);
|
||||
EXPECT_CALL(check, Call("Done"));
|
||||
}
|
||||
|
||||
check.Call("Simple pan");
|
||||
ApzcPanNoFling(apzc, mcc, 50, 25);
|
||||
check.Call("Complex pan");
|
||||
Pan(apzc, mcc, 25, 45);
|
||||
apzc->AdvanceAnimationsUntilEnd();
|
||||
check.Call("Done");
|
||||
}
|
||||
|
||||
void APZCBasicTester::PanIntoOverscroll()
|
||||
{
|
||||
int touchStart = 500;
|
||||
int touchEnd = 10;
|
||||
Pan(apzc, mcc, touchStart, touchEnd);
|
||||
EXPECT_TRUE(apzc->IsOverscrolled());
|
||||
}
|
||||
|
||||
void APZCBasicTester::TestOverscroll()
|
||||
{
|
||||
// Pan sufficiently to hit overscroll behavior
|
||||
PanIntoOverscroll();
|
||||
|
||||
// Check that we recover from overscroll via an animation.
|
||||
ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
|
||||
SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(APZCBasicTester, OverScrollPanning) {
|
||||
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
|
||||
|
||||
TestOverscroll();
|
||||
}
|
||||
|
||||
// Tests that an overscroll animation doesn't trigger an assertion failure
|
||||
// in the case where a sample has a velocity of zero.
|
||||
TEST_F(APZCBasicTester, OverScroll_Bug1152051a) {
|
||||
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
|
||||
|
||||
// Doctor the prefs to make the velocity zero at the end of the first sample.
|
||||
|
||||
// This ensures our incoming velocity to the overscroll animation is
|
||||
// a round(ish) number, 4.9 (that being the distance of the pan before
|
||||
// overscroll, which is 500 - 10 = 490 pixels, divided by the duration of
|
||||
// the pan, which is 100 ms).
|
||||
SCOPED_GFX_PREF(APZFlingFriction, float, 0);
|
||||
|
||||
// To ensure the velocity after the first sample is 0, set the spring
|
||||
// stiffness to the incoming velocity (4.9) divided by the overscroll
|
||||
// (400 pixels) times the step duration (1 ms).
|
||||
SCOPED_GFX_PREF(APZOverscrollSpringStiffness, float, 0.01225f);
|
||||
|
||||
TestOverscroll();
|
||||
}
|
||||
|
||||
// Tests that ending an overscroll animation doesn't leave around state that
|
||||
// confuses the next overscroll animation.
|
||||
TEST_F(APZCBasicTester, OverScroll_Bug1152051b) {
|
||||
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
|
||||
|
||||
SCOPED_GFX_PREF(APZOverscrollStopDistanceThreshold, float, 0.1f);
|
||||
|
||||
// Pan sufficiently to hit overscroll behavior
|
||||
PanIntoOverscroll();
|
||||
|
||||
// Sample animations once, to give the fling animation started on touch-up
|
||||
// a chance to realize it's overscrolled, and schedule a call to
|
||||
// HandleFlingOverscroll().
|
||||
SampleAnimationOnce();
|
||||
|
||||
// This advances the time and runs the HandleFlingOverscroll task scheduled in
|
||||
// the previous call, which starts an overscroll animation. It then samples
|
||||
// the overscroll animation once, to get it to initialize the first overscroll
|
||||
// sample.
|
||||
SampleAnimationOnce();
|
||||
|
||||
// Do a touch-down to cancel the overscroll animation, and then a touch-up
|
||||
// to schedule a new one since we're still overscrolled. We don't pan because
|
||||
// panning can trigger functions that clear the overscroll animation state
|
||||
// in other ways.
|
||||
TouchDown(apzc, 10, 10, mcc->Time(), nullptr);
|
||||
TouchUp(apzc, 10, 10, mcc->Time());
|
||||
|
||||
// Sample the second overscroll animation to its end.
|
||||
// If the ending of the first overscroll animation fails to clear state
|
||||
// properly, this will assert.
|
||||
ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
|
||||
SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
|
||||
}
|
||||
|
||||
TEST_F(APZCBasicTester, OverScrollAbort) {
|
||||
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
|
||||
|
||||
// Pan sufficiently to hit overscroll behavior
|
||||
int touchStart = 500;
|
||||
int touchEnd = 10;
|
||||
Pan(apzc, mcc, touchStart, touchEnd);
|
||||
EXPECT_TRUE(apzc->IsOverscrolled());
|
||||
|
||||
ParentLayerPoint pointOut;
|
||||
AsyncTransform viewTransformOut;
|
||||
|
||||
// This sample call will run to the end of the fling animation
|
||||
// and will schedule the overscroll animation.
|
||||
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(10000));
|
||||
EXPECT_TRUE(apzc->IsOverscrolled());
|
||||
|
||||
// At this point, we have an active overscroll animation.
|
||||
// Check that cancelling the animation clears the overscroll.
|
||||
apzc->CancelAnimation();
|
||||
EXPECT_FALSE(apzc->IsOverscrolled());
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
TEST_F(APZCBasicTester, OverScrollPanningAbort) {
|
||||
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
|
||||
|
||||
// Pan sufficiently to hit overscroll behaviour. Keep the finger down so
|
||||
// the pan does not end.
|
||||
int touchStart = 500;
|
||||
int touchEnd = 10;
|
||||
Pan(apzc, mcc, touchStart, touchEnd, true); // keep finger down
|
||||
EXPECT_TRUE(apzc->IsOverscrolled());
|
||||
|
||||
// Check that calling CancelAnimation() while the user is still panning
|
||||
// (and thus no fling or snap-back animation has had a chance to start)
|
||||
// clears the overscroll.
|
||||
apzc->CancelAnimation();
|
||||
EXPECT_FALSE(apzc->IsOverscrolled());
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* 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/. */
|
||||
|
||||
#include "APZCTreeManagerTester.h"
|
||||
#include "APZTestCommon.h"
|
||||
#include "InputUtils.h"
|
||||
|
||||
class APZEventRegionsTester : public APZCTreeManagerTester {
|
||||
protected:
|
||||
UniquePtr<ScopedLayerTreeRegistration> registration;
|
||||
TestAsyncPanZoomController* rootApzc;
|
||||
|
||||
void CreateEventRegionsLayerTree1() {
|
||||
const char* layerTreeSyntax = "c(tt)";
|
||||
nsIntRegion layerVisibleRegions[] = {
|
||||
nsIntRegion(IntRect(0, 0, 200, 200)), // root
|
||||
nsIntRegion(IntRect(0, 0, 100, 200)), // left half
|
||||
nsIntRegion(IntRect(0, 100, 200, 100)), // bottom half
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
|
||||
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2);
|
||||
SetScrollHandoff(layers[1], root);
|
||||
SetScrollHandoff(layers[2], root);
|
||||
|
||||
// Set up the event regions over a 200x200 area. The root layer has the
|
||||
// whole 200x200 as the hit region; layers[1] has the left half and
|
||||
// layers[2] has the bottom half. The bottom-left 100x100 area is also
|
||||
// in the d-t-c region for both layers[1] and layers[2] (but layers[2] is
|
||||
// on top so it gets the events by default if the main thread doesn't
|
||||
// respond).
|
||||
EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200)));
|
||||
root->SetEventRegions(regions);
|
||||
regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 100, 100, 100));
|
||||
regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 200));
|
||||
layers[1]->SetEventRegions(regions);
|
||||
regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100));
|
||||
layers[2]->SetEventRegions(regions);
|
||||
|
||||
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
rootApzc = ApzcOf(root);
|
||||
}
|
||||
|
||||
void CreateEventRegionsLayerTree2() {
|
||||
const char* layerTreeSyntax = "c(t)";
|
||||
nsIntRegion layerVisibleRegions[] = {
|
||||
nsIntRegion(IntRect(0, 0, 100, 500)),
|
||||
nsIntRegion(IntRect(0, 150, 100, 100)),
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
|
||||
|
||||
// Set up the event regions so that the child thebes layer is positioned far
|
||||
// away from the scrolling container layer.
|
||||
EventRegions regions(nsIntRegion(IntRect(0, 0, 100, 100)));
|
||||
root->SetEventRegions(regions);
|
||||
regions.mHitRegion = nsIntRegion(IntRect(0, 150, 100, 100));
|
||||
layers[1]->SetEventRegions(regions);
|
||||
|
||||
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
rootApzc = ApzcOf(root);
|
||||
}
|
||||
|
||||
void CreateObscuringLayerTree() {
|
||||
const char* layerTreeSyntax = "c(c(t)t)";
|
||||
// LayerID 0 1 2 3
|
||||
// 0 is the root.
|
||||
// 1 is a parent scrollable layer.
|
||||
// 2 is a child scrollable layer.
|
||||
// 3 is the Obscurer, who ruins everything.
|
||||
nsIntRegion layerVisibleRegions[] = {
|
||||
// x coordinates are uninteresting
|
||||
nsIntRegion(IntRect(0, 0, 200, 200)), // [0, 200]
|
||||
nsIntRegion(IntRect(0, 0, 200, 200)), // [0, 200]
|
||||
nsIntRegion(IntRect(0, 100, 200, 50)), // [100, 150]
|
||||
nsIntRegion(IntRect(0, 100, 200, 100)) // [100, 200]
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
|
||||
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 200, 300));
|
||||
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 200, 100));
|
||||
SetScrollHandoff(layers[2], layers[1]);
|
||||
SetScrollHandoff(layers[1], root);
|
||||
|
||||
EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200)));
|
||||
root->SetEventRegions(regions);
|
||||
regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 300));
|
||||
layers[1]->SetEventRegions(regions);
|
||||
regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100));
|
||||
layers[2]->SetEventRegions(regions);
|
||||
|
||||
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
rootApzc = ApzcOf(root);
|
||||
}
|
||||
|
||||
void CreateBug1119497LayerTree() {
|
||||
const char* layerTreeSyntax = "c(tt)";
|
||||
// LayerID 0 12
|
||||
// 0 is the root and has an APZC
|
||||
// 1 is behind 2 and has an APZC
|
||||
// 2 entirely covers 1 and should take all the input events, but has no APZC
|
||||
// so hits to 2 should go to to the root APZC
|
||||
nsIntRegion layerVisibleRegions[] = {
|
||||
nsIntRegion(IntRect(0, 0, 100, 100)),
|
||||
nsIntRegion(IntRect(0, 0, 100, 100)),
|
||||
nsIntRegion(IntRect(0, 0, 100, 100)),
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
|
||||
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
|
||||
|
||||
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
}
|
||||
|
||||
void CreateBug1117712LayerTree() {
|
||||
const char* layerTreeSyntax = "c(c(t)t)";
|
||||
// LayerID 0 1 2 3
|
||||
// 0 is the root
|
||||
// 1 is a container layer whose sole purpose to make a non-empty ancestor
|
||||
// transform for 2, so that 2's screen-to-apzc and apzc-to-gecko
|
||||
// transforms are different from 3's.
|
||||
// 2 is a small layer that is the actual target
|
||||
// 3 is a big layer obscuring 2 with a dispatch-to-content region
|
||||
nsIntRegion layerVisibleRegions[] = {
|
||||
nsIntRegion(IntRect(0, 0, 100, 100)),
|
||||
nsIntRegion(IntRect(0, 0, 0, 0)),
|
||||
nsIntRegion(IntRect(0, 0, 10, 10)),
|
||||
nsIntRegion(IntRect(0, 0, 100, 100)),
|
||||
};
|
||||
Matrix4x4 layerTransforms[] = {
|
||||
Matrix4x4(),
|
||||
Matrix4x4::Translation(50, 0, 0),
|
||||
Matrix4x4(),
|
||||
Matrix4x4(),
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, layerTransforms, lm, layers);
|
||||
|
||||
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 10, 10));
|
||||
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 100, 100));
|
||||
|
||||
EventRegions regions(nsIntRegion(IntRect(0, 0, 10, 10)));
|
||||
layers[2]->SetEventRegions(regions);
|
||||
regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
|
||||
regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
|
||||
layers[3]->SetEventRegions(regions);
|
||||
|
||||
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(APZEventRegionsTester, HitRegionImmediateResponse) {
|
||||
CreateEventRegionsLayerTree1();
|
||||
|
||||
TestAsyncPanZoomController* root = ApzcOf(layers[0]);
|
||||
TestAsyncPanZoomController* left = ApzcOf(layers[1]);
|
||||
TestAsyncPanZoomController* bottom = ApzcOf(layers[2]);
|
||||
|
||||
MockFunction<void(std::string checkPointName)> check;
|
||||
{
|
||||
InSequence s;
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(_, _, left->GetGuid())).Times(1);
|
||||
EXPECT_CALL(check, Call("Tapped on left"));
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(_, _, bottom->GetGuid())).Times(1);
|
||||
EXPECT_CALL(check, Call("Tapped on bottom"));
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(_, _, root->GetGuid())).Times(1);
|
||||
EXPECT_CALL(check, Call("Tapped on root"));
|
||||
EXPECT_CALL(check, Call("Tap pending on d-t-c region"));
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(_, _, bottom->GetGuid())).Times(1);
|
||||
EXPECT_CALL(check, Call("Tapped on bottom again"));
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(_, _, left->GetGuid())).Times(1);
|
||||
EXPECT_CALL(check, Call("Tapped on left this time"));
|
||||
}
|
||||
|
||||
TimeDuration tapDuration = TimeDuration::FromMilliseconds(100);
|
||||
|
||||
// Tap in the exposed hit regions of each of the layers once and ensure
|
||||
// the clicks are dispatched right away
|
||||
Tap(manager, 10, 10, mcc, tapDuration);
|
||||
mcc->RunThroughDelayedTasks(); // this runs the tap event
|
||||
check.Call("Tapped on left");
|
||||
Tap(manager, 110, 110, mcc, tapDuration);
|
||||
mcc->RunThroughDelayedTasks(); // this runs the tap event
|
||||
check.Call("Tapped on bottom");
|
||||
Tap(manager, 110, 10, mcc, tapDuration);
|
||||
mcc->RunThroughDelayedTasks(); // this runs the tap event
|
||||
check.Call("Tapped on root");
|
||||
|
||||
// Now tap on the dispatch-to-content region where the layers overlap
|
||||
Tap(manager, 10, 110, mcc, tapDuration);
|
||||
mcc->RunThroughDelayedTasks(); // this runs the main-thread timeout
|
||||
check.Call("Tap pending on d-t-c region");
|
||||
mcc->RunThroughDelayedTasks(); // this runs the tap event
|
||||
check.Call("Tapped on bottom again");
|
||||
|
||||
// Now let's do that again, but simulate a main-thread response
|
||||
uint64_t inputBlockId = 0;
|
||||
Tap(manager, 10, 110, mcc, tapDuration, nullptr, &inputBlockId);
|
||||
nsTArray<ScrollableLayerGuid> targets;
|
||||
targets.AppendElement(left->GetGuid());
|
||||
manager->SetTargetAPZC(inputBlockId, targets);
|
||||
while (mcc->RunThroughDelayedTasks()); // this runs the tap event
|
||||
check.Call("Tapped on left this time");
|
||||
}
|
||||
|
||||
TEST_F(APZEventRegionsTester, HitRegionAccumulatesChildren) {
|
||||
CreateEventRegionsLayerTree2();
|
||||
|
||||
// Tap in the area of the child layer that's not directly included in the
|
||||
// parent layer's hit region. Verify that it comes out of the APZC's
|
||||
// content controller, which indicates the input events got routed correctly
|
||||
// to the APZC.
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(_, _, rootApzc->GetGuid())).Times(1);
|
||||
Tap(manager, 10, 160, mcc, TimeDuration::FromMilliseconds(100));
|
||||
}
|
||||
|
||||
TEST_F(APZEventRegionsTester, Obscuration) {
|
||||
CreateObscuringLayerTree();
|
||||
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
|
||||
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
|
||||
TestAsyncPanZoomController* parent = ApzcOf(layers[1]);
|
||||
TestAsyncPanZoomController* child = ApzcOf(layers[2]);
|
||||
|
||||
ApzcPanNoFling(parent, mcc, 75, 25);
|
||||
|
||||
HitTestResult result;
|
||||
RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 75), &result);
|
||||
EXPECT_EQ(child, hit.get());
|
||||
EXPECT_EQ(HitTestResult::HitLayer, result);
|
||||
}
|
||||
|
||||
TEST_F(APZEventRegionsTester, Bug1119497) {
|
||||
CreateBug1119497LayerTree();
|
||||
|
||||
HitTestResult result;
|
||||
RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 50), &result);
|
||||
// We should hit layers[2], so |result| will be HitLayer but there's no
|
||||
// actual APZC on layers[2], so it will be the APZC of the root layer.
|
||||
EXPECT_EQ(ApzcOf(layers[0]), hit.get());
|
||||
EXPECT_EQ(HitTestResult::HitLayer, result);
|
||||
}
|
||||
|
||||
TEST_F(APZEventRegionsTester, Bug1117712) {
|
||||
CreateBug1117712LayerTree();
|
||||
|
||||
TestAsyncPanZoomController* apzc2 = ApzcOf(layers[2]);
|
||||
|
||||
// These touch events should hit the dispatch-to-content region of layers[3]
|
||||
// and so get queued with that APZC as the tentative target.
|
||||
uint64_t inputBlockId = 0;
|
||||
Tap(manager, 55, 5, mcc, TimeDuration::FromMilliseconds(100), nullptr, &inputBlockId);
|
||||
// But now we tell the APZ that really it hit layers[2], and expect the tap
|
||||
// to be delivered at the correct coordinates.
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(55, 5), 0, apzc2->GetGuid())).Times(1);
|
||||
|
||||
nsTArray<ScrollableLayerGuid> targets;
|
||||
targets.AppendElement(apzc2->GetGuid());
|
||||
manager->SetTargetAPZC(inputBlockId, targets);
|
||||
}
|
|
@ -0,0 +1,594 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* 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/. */
|
||||
|
||||
#include "APZCBasicTester.h"
|
||||
#include "APZTestCommon.h"
|
||||
|
||||
class APZCGestureDetectorTester : public APZCBasicTester {
|
||||
public:
|
||||
APZCGestureDetectorTester()
|
||||
: APZCBasicTester(AsyncPanZoomController::USE_GESTURE_DETECTOR)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
FrameMetrics GetPinchableFrameMetrics()
|
||||
{
|
||||
FrameMetrics fm;
|
||||
fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200));
|
||||
fm.SetScrollableRect(CSSRect(0, 0, 980, 1000));
|
||||
fm.SetScrollOffset(CSSPoint(300, 300));
|
||||
fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0));
|
||||
// APZC only allows zooming on the root scrollable frame.
|
||||
fm.SetIsRootContent(true);
|
||||
// the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100
|
||||
return fm;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(APZCGestureDetectorTester, Pan_After_Pinch) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
|
||||
|
||||
FrameMetrics originalMetrics = GetPinchableFrameMetrics();
|
||||
apzc->SetFrameMetrics(originalMetrics);
|
||||
|
||||
MakeApzcZoomable();
|
||||
|
||||
// Test parameters
|
||||
float zoomAmount = 1.25;
|
||||
float pinchLength = 100.0;
|
||||
float pinchLengthScaled = pinchLength * zoomAmount;
|
||||
int focusX = 250;
|
||||
int focusY = 300;
|
||||
int panDistance = 20;
|
||||
|
||||
int firstFingerId = 0;
|
||||
int secondFingerId = firstFingerId + 1;
|
||||
|
||||
// Put fingers down
|
||||
MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX, focusY));
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX, focusY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// Spread fingers out to enter the pinch state
|
||||
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLength, focusY));
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLength, focusY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// Do the actual pinch of 1.25x
|
||||
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLengthScaled, focusY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// Verify that the zoom changed, just to make sure our code above did what it
|
||||
// was supposed to.
|
||||
FrameMetrics zoomedMetrics = apzc->GetFrameMetrics();
|
||||
float newZoom = zoomedMetrics.GetZoom().ToScaleFactor().scale;
|
||||
EXPECT_EQ(originalMetrics.GetZoom().ToScaleFactor().scale * zoomAmount, newZoom);
|
||||
|
||||
// Now we lift one finger...
|
||||
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLengthScaled, focusY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// ... and pan with the remaining finger. This pan just breaks through the
|
||||
// distance threshold.
|
||||
focusY += 40;
|
||||
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// This one does an actual pan of 20 pixels
|
||||
focusY += panDistance;
|
||||
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// Lift the remaining finger
|
||||
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// Verify that we scrolled
|
||||
FrameMetrics finalMetrics = apzc->GetFrameMetrics();
|
||||
EXPECT_EQ(zoomedMetrics.GetScrollOffset().y - (panDistance / newZoom), finalMetrics.GetScrollOffset().y);
|
||||
|
||||
// Clear out any remaining fling animation and pending tasks
|
||||
apzc->AdvanceAnimationsUntilEnd();
|
||||
while (mcc->RunThroughDelayedTasks());
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
TEST_F(APZCGestureDetectorTester, Pan_With_Tap) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
|
||||
|
||||
FrameMetrics originalMetrics = GetPinchableFrameMetrics();
|
||||
apzc->SetFrameMetrics(originalMetrics);
|
||||
|
||||
// Making the APZC zoomable isn't really needed for the correct operation of
|
||||
// this test, but it could help catch regressions where we accidentally enter
|
||||
// a pinch state.
|
||||
MakeApzcZoomable();
|
||||
|
||||
// Test parameters
|
||||
int touchX = 250;
|
||||
int touchY = 300;
|
||||
int panDistance = 20;
|
||||
|
||||
int firstFingerId = 0;
|
||||
int secondFingerId = firstFingerId + 1;
|
||||
|
||||
// Put finger down
|
||||
MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// Start a pan, break through the threshold
|
||||
touchY += 40;
|
||||
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// Do an actual pan for a bit
|
||||
touchY += panDistance;
|
||||
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// Put a second finger down
|
||||
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, touchX + 10, touchY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// Lift the second finger
|
||||
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, touchX + 10, touchY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// Bust through the threshold again
|
||||
touchY += 40;
|
||||
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// Do some more actual panning
|
||||
touchY += panDistance;
|
||||
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// Lift the first finger
|
||||
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
|
||||
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
// Verify that we scrolled
|
||||
FrameMetrics finalMetrics = apzc->GetFrameMetrics();
|
||||
float zoom = finalMetrics.GetZoom().ToScaleFactor().scale;
|
||||
EXPECT_EQ(originalMetrics.GetScrollOffset().y - (panDistance * 2 / zoom), finalMetrics.GetScrollOffset().y);
|
||||
|
||||
// Clear out any remaining fling animation and pending tasks
|
||||
apzc->AdvanceAnimationsUntilEnd();
|
||||
while (mcc->RunThroughDelayedTasks());
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
class APZCFlingStopTester : public APZCGestureDetectorTester {
|
||||
protected:
|
||||
// Start a fling, and then tap while the fling is ongoing. When
|
||||
// aSlow is false, the tap will happen while the fling is at a
|
||||
// high velocity, and we check that the tap doesn't trigger sending a tap
|
||||
// to content. If aSlow is true, the tap will happen while the fling
|
||||
// is at a slow velocity, and we check that the tap does trigger sending
|
||||
// a tap to content. See bug 1022956.
|
||||
void DoFlingStopTest(bool aSlow) {
|
||||
int touchStart = 50;
|
||||
int touchEnd = 10;
|
||||
|
||||
// Start the fling down.
|
||||
Pan(apzc, mcc, touchStart, touchEnd);
|
||||
// The touchstart from the pan will leave some cancelled tasks in the queue, clear them out
|
||||
|
||||
// If we want to tap while the fling is fast, let the fling advance for 10ms only. If we want
|
||||
// the fling to slow down more, advance to 2000ms. These numbers may need adjusting if our
|
||||
// friction and threshold values change, but they should be deterministic at least.
|
||||
int timeDelta = aSlow ? 2000 : 10;
|
||||
int tapCallsExpected = aSlow ? 2 : 1;
|
||||
|
||||
// Advance the fling animation by timeDelta milliseconds.
|
||||
ParentLayerPoint pointOut;
|
||||
AsyncTransform viewTransformOut;
|
||||
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(timeDelta));
|
||||
|
||||
// Deliver a tap to abort the fling. Ensure that we get a HandleSingleTap
|
||||
// call out of it if and only if the fling is slow.
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(_, 0, apzc->GetGuid())).Times(tapCallsExpected);
|
||||
Tap(apzc, 10, 10, mcc, 0);
|
||||
while (mcc->RunThroughDelayedTasks());
|
||||
|
||||
// Deliver another tap, to make sure that taps are flowing properly once
|
||||
// the fling is aborted.
|
||||
Tap(apzc, 100, 100, mcc, 0);
|
||||
while (mcc->RunThroughDelayedTasks());
|
||||
|
||||
// Verify that we didn't advance any further after the fling was aborted, in either case.
|
||||
ParentLayerPoint finalPointOut;
|
||||
apzc->SampleContentTransformForFrame(&viewTransformOut, finalPointOut);
|
||||
EXPECT_EQ(pointOut.x, finalPointOut.x);
|
||||
EXPECT_EQ(pointOut.y, finalPointOut.y);
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
void DoFlingStopWithSlowListener(bool aPreventDefault) {
|
||||
MakeApzcWaitForMainThread();
|
||||
|
||||
int touchStart = 50;
|
||||
int touchEnd = 10;
|
||||
uint64_t blockId = 0;
|
||||
|
||||
// Start the fling down.
|
||||
Pan(apzc, mcc, touchStart, touchEnd, false, nullptr, nullptr, &blockId);
|
||||
apzc->ConfirmTarget(blockId);
|
||||
apzc->ContentReceivedInputBlock(blockId, false);
|
||||
|
||||
// Sample the fling a couple of times to ensure it's going.
|
||||
ParentLayerPoint point, finalPoint;
|
||||
AsyncTransform viewTransform;
|
||||
apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10));
|
||||
apzc->SampleContentTransformForFrame(&viewTransform, finalPoint, TimeDuration::FromMilliseconds(10));
|
||||
EXPECT_GT(finalPoint.y, point.y);
|
||||
|
||||
// Now we put our finger down to stop the fling
|
||||
TouchDown(apzc, 10, 10, mcc->Time(), &blockId);
|
||||
|
||||
// Re-sample to make sure it hasn't moved
|
||||
apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10));
|
||||
EXPECT_EQ(finalPoint.x, point.x);
|
||||
EXPECT_EQ(finalPoint.y, point.y);
|
||||
|
||||
// respond to the touchdown that stopped the fling.
|
||||
// even if we do a prevent-default on it, the animation should remain stopped.
|
||||
apzc->ContentReceivedInputBlock(blockId, aPreventDefault);
|
||||
|
||||
// Verify the page hasn't moved
|
||||
apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(70));
|
||||
EXPECT_EQ(finalPoint.x, point.x);
|
||||
EXPECT_EQ(finalPoint.y, point.y);
|
||||
|
||||
// clean up
|
||||
TouchUp(apzc, 10, 10, mcc->Time());
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(APZCFlingStopTester, FlingStop) {
|
||||
DoFlingStopTest(false);
|
||||
}
|
||||
|
||||
TEST_F(APZCFlingStopTester, FlingStopTap) {
|
||||
DoFlingStopTest(true);
|
||||
}
|
||||
|
||||
TEST_F(APZCFlingStopTester, FlingStopSlowListener) {
|
||||
DoFlingStopWithSlowListener(false);
|
||||
}
|
||||
|
||||
TEST_F(APZCFlingStopTester, FlingStopPreventDefault) {
|
||||
DoFlingStopWithSlowListener(true);
|
||||
}
|
||||
|
||||
TEST_F(APZCGestureDetectorTester, ShortPress) {
|
||||
MakeApzcUnzoomable();
|
||||
|
||||
MockFunction<void(std::string checkPointName)> check;
|
||||
{
|
||||
InSequence s;
|
||||
// This verifies that the single tap notification is sent after the
|
||||
// touchup is fully processed. The ordering here is important.
|
||||
EXPECT_CALL(check, Call("pre-tap"));
|
||||
EXPECT_CALL(check, Call("post-tap"));
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
|
||||
}
|
||||
|
||||
check.Call("pre-tap");
|
||||
TapAndCheckStatus(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100));
|
||||
check.Call("post-tap");
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
TEST_F(APZCGestureDetectorTester, MediumPress) {
|
||||
MakeApzcUnzoomable();
|
||||
|
||||
MockFunction<void(std::string checkPointName)> check;
|
||||
{
|
||||
InSequence s;
|
||||
// This verifies that the single tap notification is sent after the
|
||||
// touchup is fully processed. The ordering here is important.
|
||||
EXPECT_CALL(check, Call("pre-tap"));
|
||||
EXPECT_CALL(check, Call("post-tap"));
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
|
||||
}
|
||||
|
||||
check.Call("pre-tap");
|
||||
TapAndCheckStatus(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(400));
|
||||
check.Call("post-tap");
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
class APZCLongPressTester : public APZCGestureDetectorTester {
|
||||
protected:
|
||||
void DoLongPressTest(uint32_t aBehavior) {
|
||||
MakeApzcUnzoomable();
|
||||
|
||||
uint64_t blockId = 0;
|
||||
|
||||
nsEventStatus status = TouchDown(apzc, 10, 10, mcc->Time(), &blockId);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
|
||||
|
||||
if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
|
||||
// SetAllowedTouchBehavior() must be called after sending touch-start.
|
||||
nsTArray<uint32_t> allowedTouchBehaviors;
|
||||
allowedTouchBehaviors.AppendElement(aBehavior);
|
||||
apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors);
|
||||
}
|
||||
// Have content "respond" to the touchstart
|
||||
apzc->ContentReceivedInputBlock(blockId, false);
|
||||
|
||||
MockFunction<void(std::string checkPointName)> check;
|
||||
|
||||
{
|
||||
InSequence s;
|
||||
|
||||
EXPECT_CALL(check, Call("preHandleLongTap"));
|
||||
blockId++;
|
||||
EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(10, 10), 0, apzc->GetGuid(), blockId)).Times(1);
|
||||
EXPECT_CALL(check, Call("postHandleLongTap"));
|
||||
|
||||
EXPECT_CALL(check, Call("preHandleSingleTap"));
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
|
||||
EXPECT_CALL(check, Call("postHandleSingleTap"));
|
||||
}
|
||||
|
||||
// Manually invoke the longpress while the touch is currently down.
|
||||
check.Call("preHandleLongTap");
|
||||
mcc->RunThroughDelayedTasks();
|
||||
check.Call("postHandleLongTap");
|
||||
|
||||
// Dispatching the longpress event starts a new touch block, which
|
||||
// needs a new content response and also has a pending timeout task
|
||||
// in the queue. Deal with those here. We do the content response first
|
||||
// with preventDefault=false, and then we run the timeout task which
|
||||
// "loses the race" and does nothing.
|
||||
apzc->ContentReceivedInputBlock(blockId, false);
|
||||
mcc->AdvanceByMillis(1000);
|
||||
|
||||
// Finally, simulate lifting the finger. Since the long-press wasn't
|
||||
// prevent-defaulted, we should get a long-tap-up event.
|
||||
check.Call("preHandleSingleTap");
|
||||
status = TouchUp(apzc, 10, 10, mcc->Time());
|
||||
mcc->RunThroughDelayedTasks();
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
|
||||
check.Call("postHandleSingleTap");
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
void DoLongPressPreventDefaultTest(uint32_t aBehavior) {
|
||||
MakeApzcUnzoomable();
|
||||
|
||||
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
|
||||
|
||||
int touchX = 10,
|
||||
touchStartY = 10,
|
||||
touchEndY = 50;
|
||||
|
||||
uint64_t blockId = 0;
|
||||
nsEventStatus status = TouchDown(apzc, touchX, touchStartY, mcc->Time(), &blockId);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
|
||||
|
||||
if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
|
||||
// SetAllowedTouchBehavior() must be called after sending touch-start.
|
||||
nsTArray<uint32_t> allowedTouchBehaviors;
|
||||
allowedTouchBehaviors.AppendElement(aBehavior);
|
||||
apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors);
|
||||
}
|
||||
// Have content "respond" to the touchstart
|
||||
apzc->ContentReceivedInputBlock(blockId, false);
|
||||
|
||||
MockFunction<void(std::string checkPointName)> check;
|
||||
|
||||
{
|
||||
InSequence s;
|
||||
|
||||
EXPECT_CALL(check, Call("preHandleLongTap"));
|
||||
blockId++;
|
||||
EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(touchX, touchStartY), 0, apzc->GetGuid(), blockId)).Times(1);
|
||||
EXPECT_CALL(check, Call("postHandleLongTap"));
|
||||
}
|
||||
|
||||
// Manually invoke the longpress while the touch is currently down.
|
||||
check.Call("preHandleLongTap");
|
||||
mcc->RunThroughDelayedTasks();
|
||||
check.Call("postHandleLongTap");
|
||||
|
||||
// There should be a TimeoutContentResponse task in the queue still,
|
||||
// waiting for the response from the longtap event dispatched above.
|
||||
// Send the signal that content has handled the long-tap, and then run
|
||||
// the timeout task (it will be a no-op because the content "wins" the
|
||||
// race. This takes the place of the "contextmenu" event.
|
||||
apzc->ContentReceivedInputBlock(blockId, true);
|
||||
mcc->AdvanceByMillis(1000);
|
||||
|
||||
MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time());
|
||||
mti.mTouches.AppendElement(SingleTouchData(0, ParentLayerPoint(touchX, touchEndY), ScreenSize(0, 0), 0, 0));
|
||||
status = apzc->ReceiveInputEvent(mti, nullptr);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
|
||||
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(touchX, touchEndY), 0, apzc->GetGuid())).Times(0);
|
||||
status = TouchUp(apzc, touchX, touchEndY, mcc->Time());
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
|
||||
|
||||
ParentLayerPoint pointOut;
|
||||
AsyncTransform viewTransformOut;
|
||||
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
|
||||
|
||||
EXPECT_EQ(ParentLayerPoint(), pointOut);
|
||||
EXPECT_EQ(AsyncTransform(), viewTransformOut);
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(APZCLongPressTester, LongPress) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
|
||||
DoLongPressTest(mozilla::layers::AllowedTouchBehavior::NONE);
|
||||
}
|
||||
|
||||
TEST_F(APZCLongPressTester, LongPressWithTouchAction) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
|
||||
DoLongPressTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
|
||||
| mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
|
||||
| mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
|
||||
}
|
||||
|
||||
TEST_F(APZCLongPressTester, LongPressPreventDefault) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
|
||||
DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::NONE);
|
||||
}
|
||||
|
||||
TEST_F(APZCLongPressTester, LongPressPreventDefaultWithTouchAction) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
|
||||
DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
|
||||
| mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
|
||||
| mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
|
||||
}
|
||||
|
||||
TEST_F(APZCGestureDetectorTester, DoubleTap) {
|
||||
MakeApzcWaitForMainThread();
|
||||
MakeApzcZoomable();
|
||||
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
|
||||
EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
|
||||
|
||||
uint64_t blockIds[2];
|
||||
DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds);
|
||||
|
||||
// responses to the two touchstarts
|
||||
apzc->ContentReceivedInputBlock(blockIds[0], false);
|
||||
apzc->ContentReceivedInputBlock(blockIds[1], false);
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
TEST_F(APZCGestureDetectorTester, DoubleTapNotZoomable) {
|
||||
MakeApzcWaitForMainThread();
|
||||
MakeApzcUnzoomable();
|
||||
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(2);
|
||||
EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
|
||||
|
||||
uint64_t blockIds[2];
|
||||
DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds);
|
||||
|
||||
// responses to the two touchstarts
|
||||
apzc->ContentReceivedInputBlock(blockIds[0], false);
|
||||
apzc->ContentReceivedInputBlock(blockIds[1], false);
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultFirstOnly) {
|
||||
MakeApzcWaitForMainThread();
|
||||
MakeApzcZoomable();
|
||||
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
|
||||
EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
|
||||
|
||||
uint64_t blockIds[2];
|
||||
DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds);
|
||||
|
||||
// responses to the two touchstarts
|
||||
apzc->ContentReceivedInputBlock(blockIds[0], true);
|
||||
apzc->ContentReceivedInputBlock(blockIds[1], false);
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultBoth) {
|
||||
MakeApzcWaitForMainThread();
|
||||
MakeApzcZoomable();
|
||||
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
|
||||
EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
|
||||
|
||||
uint64_t blockIds[2];
|
||||
DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds);
|
||||
|
||||
// responses to the two touchstarts
|
||||
apzc->ContentReceivedInputBlock(blockIds[0], true);
|
||||
apzc->ContentReceivedInputBlock(blockIds[1], true);
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
// Test for bug 947892
|
||||
// We test whether we dispatch tap event when the tap is followed by pinch.
|
||||
TEST_F(APZCGestureDetectorTester, TapFollowedByPinch) {
|
||||
MakeApzcZoomable();
|
||||
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
|
||||
|
||||
Tap(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100));
|
||||
|
||||
int inputId = 0;
|
||||
MultiTouchInput mti;
|
||||
mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
|
||||
mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
|
||||
mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time());
|
||||
mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
|
||||
mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
TEST_F(APZCGestureDetectorTester, TapFollowedByMultipleTouches) {
|
||||
MakeApzcZoomable();
|
||||
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
|
||||
|
||||
Tap(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100));
|
||||
|
||||
int inputId = 0;
|
||||
MultiTouchInput mti;
|
||||
mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
|
||||
mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
|
||||
mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
|
||||
mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time());
|
||||
mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
|
||||
mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
|
||||
apzc->ReceiveInputEvent(mti, nullptr);
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
|
@ -0,0 +1,487 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* 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/. */
|
||||
|
||||
#include "APZCTreeManagerTester.h"
|
||||
#include "APZTestCommon.h"
|
||||
#include "InputUtils.h"
|
||||
|
||||
class APZHitTestingTester : public APZCTreeManagerTester {
|
||||
protected:
|
||||
ScreenToParentLayerMatrix4x4 transformToApzc;
|
||||
ParentLayerToScreenMatrix4x4 transformToGecko;
|
||||
|
||||
already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint) {
|
||||
RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(aPoint, nullptr);
|
||||
if (hit) {
|
||||
transformToApzc = manager->GetScreenToApzcTransform(hit.get());
|
||||
transformToGecko = manager->GetApzcToGeckoTransform(hit.get());
|
||||
}
|
||||
return hit.forget();
|
||||
}
|
||||
|
||||
protected:
|
||||
void CreateHitTesting1LayerTree() {
|
||||
const char* layerTreeSyntax = "c(tttt)";
|
||||
// LayerID 0 1234
|
||||
nsIntRegion layerVisibleRegion[] = {
|
||||
nsIntRegion(IntRect(0,0,100,100)),
|
||||
nsIntRegion(IntRect(0,0,100,100)),
|
||||
nsIntRegion(IntRect(10,10,20,20)),
|
||||
nsIntRegion(IntRect(10,10,20,20)),
|
||||
nsIntRegion(IntRect(5,5,20,20)),
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
|
||||
}
|
||||
|
||||
void CreateHitTesting2LayerTree() {
|
||||
const char* layerTreeSyntax = "c(tc(t))";
|
||||
// LayerID 0 12 3
|
||||
nsIntRegion layerVisibleRegion[] = {
|
||||
nsIntRegion(IntRect(0,0,100,100)),
|
||||
nsIntRegion(IntRect(10,10,40,40)),
|
||||
nsIntRegion(IntRect(10,60,40,40)),
|
||||
nsIntRegion(IntRect(10,60,40,40)),
|
||||
};
|
||||
Matrix4x4 transforms[] = {
|
||||
Matrix4x4(),
|
||||
Matrix4x4(),
|
||||
Matrix4x4::Scaling(2, 1, 1),
|
||||
Matrix4x4(),
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers);
|
||||
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 80, 80));
|
||||
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 80, 80));
|
||||
}
|
||||
|
||||
void CreateComplexMultiLayerTree() {
|
||||
const char* layerTreeSyntax = "c(tc(t)tc(c(t)tt))";
|
||||
// LayerID 0 12 3 45 6 7 89
|
||||
nsIntRegion layerVisibleRegion[] = {
|
||||
nsIntRegion(IntRect(0,0,300,400)), // root(0)
|
||||
nsIntRegion(IntRect(0,0,100,100)), // thebes(1) in top-left
|
||||
nsIntRegion(IntRect(50,50,200,300)), // container(2) centered in root(0)
|
||||
nsIntRegion(IntRect(50,50,200,300)), // thebes(3) fully occupying parent container(2)
|
||||
nsIntRegion(IntRect(0,200,100,100)), // thebes(4) in bottom-left
|
||||
nsIntRegion(IntRect(200,0,100,400)), // container(5) along the right 100px of root(0)
|
||||
nsIntRegion(IntRect(200,0,100,200)), // container(6) taking up the top half of parent container(5)
|
||||
nsIntRegion(IntRect(200,0,100,200)), // thebes(7) fully occupying parent container(6)
|
||||
nsIntRegion(IntRect(200,200,100,100)), // thebes(8) in bottom-right (below (6))
|
||||
nsIntRegion(IntRect(200,300,100,100)), // thebes(9) in bottom-right (below (8))
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
|
||||
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID);
|
||||
SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 1);
|
||||
SetScrollableFrameMetrics(layers[6], FrameMetrics::START_SCROLL_ID + 1);
|
||||
SetScrollableFrameMetrics(layers[7], FrameMetrics::START_SCROLL_ID + 2);
|
||||
SetScrollableFrameMetrics(layers[8], FrameMetrics::START_SCROLL_ID + 1);
|
||||
SetScrollableFrameMetrics(layers[9], FrameMetrics::START_SCROLL_ID + 3);
|
||||
}
|
||||
|
||||
void CreateBug1148350LayerTree() {
|
||||
const char* layerTreeSyntax = "c(t)";
|
||||
// LayerID 0 1
|
||||
nsIntRegion layerVisibleRegion[] = {
|
||||
nsIntRegion(IntRect(0,0,200,200)),
|
||||
nsIntRegion(IntRect(0,0,200,200)),
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
|
||||
}
|
||||
};
|
||||
|
||||
// A simple hit testing test that doesn't involve any transforms on layers.
|
||||
TEST_F(APZHitTestingTester, HitTesting1) {
|
||||
CreateHitTesting1LayerTree();
|
||||
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
|
||||
|
||||
// No APZC attached so hit testing will return no APZC at (20,20)
|
||||
RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(20, 20));
|
||||
TestAsyncPanZoomController* nullAPZC = nullptr;
|
||||
EXPECT_EQ(nullAPZC, hit.get());
|
||||
EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
|
||||
EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);
|
||||
|
||||
uint32_t paintSequenceNumber = 0;
|
||||
|
||||
// Now we have a root APZC that will match the page
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
|
||||
hit = GetTargetAPZC(ScreenPoint(15, 15));
|
||||
EXPECT_EQ(ApzcOf(root), hit.get());
|
||||
// expect hit point at LayerIntPoint(15, 15)
|
||||
EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc * ScreenPoint(15, 15));
|
||||
EXPECT_EQ(ScreenPoint(15, 15), transformToGecko * ParentLayerPoint(15, 15));
|
||||
|
||||
// Now we have a sub APZC with a better fit
|
||||
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
|
||||
EXPECT_NE(ApzcOf(root), ApzcOf(layers[3]));
|
||||
hit = GetTargetAPZC(ScreenPoint(25, 25));
|
||||
EXPECT_EQ(ApzcOf(layers[3]), hit.get());
|
||||
// expect hit point at LayerIntPoint(25, 25)
|
||||
EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25));
|
||||
EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25));
|
||||
|
||||
// At this point, layers[4] obscures layers[3] at the point (15, 15) so
|
||||
// hitting there should hit the root APZC
|
||||
hit = GetTargetAPZC(ScreenPoint(15, 15));
|
||||
EXPECT_EQ(ApzcOf(root), hit.get());
|
||||
|
||||
// Now test hit testing when we have two scrollable layers
|
||||
SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
|
||||
hit = GetTargetAPZC(ScreenPoint(15, 15));
|
||||
EXPECT_EQ(ApzcOf(layers[4]), hit.get());
|
||||
// expect hit point at LayerIntPoint(15, 15)
|
||||
EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc * ScreenPoint(15, 15));
|
||||
EXPECT_EQ(ScreenPoint(15, 15), transformToGecko * ParentLayerPoint(15, 15));
|
||||
|
||||
// Hit test ouside the reach of layer[3,4] but inside root
|
||||
hit = GetTargetAPZC(ScreenPoint(90, 90));
|
||||
EXPECT_EQ(ApzcOf(root), hit.get());
|
||||
// expect hit point at LayerIntPoint(90, 90)
|
||||
EXPECT_EQ(ParentLayerPoint(90, 90), transformToApzc * ScreenPoint(90, 90));
|
||||
EXPECT_EQ(ScreenPoint(90, 90), transformToGecko * ParentLayerPoint(90, 90));
|
||||
|
||||
// Hit test ouside the reach of any layer
|
||||
hit = GetTargetAPZC(ScreenPoint(1000, 10));
|
||||
EXPECT_EQ(nullAPZC, hit.get());
|
||||
EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
|
||||
EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);
|
||||
hit = GetTargetAPZC(ScreenPoint(-1000, 10));
|
||||
EXPECT_EQ(nullAPZC, hit.get());
|
||||
EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
|
||||
EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);
|
||||
}
|
||||
|
||||
// A more involved hit testing test that involves css and async transforms.
|
||||
TEST_F(APZHitTestingTester, HitTesting2) {
|
||||
CreateHitTesting2LayerTree();
|
||||
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
|
||||
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
|
||||
// At this point, the following holds (all coordinates in screen pixels):
|
||||
// layers[0] has content from (0,0)-(200,200), clipped by composition bounds (0,0)-(100,100)
|
||||
// layers[1] has content from (10,10)-(90,90), clipped by composition bounds (10,10)-(50,50)
|
||||
// layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer
|
||||
// layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100)
|
||||
|
||||
TestAsyncPanZoomController* apzcroot = ApzcOf(root);
|
||||
TestAsyncPanZoomController* apzc1 = ApzcOf(layers[1]);
|
||||
TestAsyncPanZoomController* apzc3 = ApzcOf(layers[3]);
|
||||
|
||||
// Hit an area that's clearly on the root layer but not any of the child layers.
|
||||
RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(75, 25));
|
||||
EXPECT_EQ(apzcroot, hit.get());
|
||||
EXPECT_EQ(ParentLayerPoint(75, 25), transformToApzc * ScreenPoint(75, 25));
|
||||
EXPECT_EQ(ScreenPoint(75, 25), transformToGecko * ParentLayerPoint(75, 25));
|
||||
|
||||
// Hit an area on the root that would be on layers[3] if layers[2]
|
||||
// weren't transformed.
|
||||
// Note that if layers[2] were scrollable, then this would hit layers[2]
|
||||
// because its composition bounds would be at (10,60)-(50,100) (and the
|
||||
// scale-only transform that we set on layers[2] would be invalid because
|
||||
// it would place the layer into overscroll, as its composition bounds
|
||||
// start at x=10 but its content at x=20).
|
||||
hit = GetTargetAPZC(ScreenPoint(15, 75));
|
||||
EXPECT_EQ(apzcroot, hit.get());
|
||||
EXPECT_EQ(ParentLayerPoint(15, 75), transformToApzc * ScreenPoint(15, 75));
|
||||
EXPECT_EQ(ScreenPoint(15, 75), transformToGecko * ParentLayerPoint(15, 75));
|
||||
|
||||
// Hit an area on layers[1].
|
||||
hit = GetTargetAPZC(ScreenPoint(25, 25));
|
||||
EXPECT_EQ(apzc1, hit.get());
|
||||
EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25));
|
||||
EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25));
|
||||
|
||||
// Hit an area on layers[3].
|
||||
hit = GetTargetAPZC(ScreenPoint(25, 75));
|
||||
EXPECT_EQ(apzc3, hit.get());
|
||||
// transformToApzc should unapply layers[2]'s transform
|
||||
EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc * ScreenPoint(25, 75));
|
||||
// and transformToGecko should reapply it
|
||||
EXPECT_EQ(ScreenPoint(25, 75), transformToGecko * ParentLayerPoint(12.5, 75));
|
||||
|
||||
// Hit an area on layers[3] that would be on the root if layers[2]
|
||||
// weren't transformed.
|
||||
hit = GetTargetAPZC(ScreenPoint(75, 75));
|
||||
EXPECT_EQ(apzc3, hit.get());
|
||||
// transformToApzc should unapply layers[2]'s transform
|
||||
EXPECT_EQ(ParentLayerPoint(37.5, 75), transformToApzc * ScreenPoint(75, 75));
|
||||
// and transformToGecko should reapply it
|
||||
EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(37.5, 75));
|
||||
|
||||
// Pan the root layer upward by 50 pixels.
|
||||
// This causes layers[1] to scroll out of view, and an async transform
|
||||
// of -50 to be set on the root layer.
|
||||
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
|
||||
|
||||
// This first pan will move the APZC by 50 pixels, and dispatch a paint request.
|
||||
// Since this paint request is in the queue to Gecko, transformToGecko will
|
||||
// take it into account.
|
||||
ApzcPanNoFling(apzcroot, mcc, 100, 50);
|
||||
|
||||
// Hit where layers[3] used to be. It should now hit the root.
|
||||
hit = GetTargetAPZC(ScreenPoint(75, 75));
|
||||
EXPECT_EQ(apzcroot, hit.get());
|
||||
// transformToApzc doesn't unapply the root's own async transform
|
||||
EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc * ScreenPoint(75, 75));
|
||||
// and transformToGecko unapplies it and then reapplies it, because by the
|
||||
// time the event being transformed reaches Gecko the new paint request will
|
||||
// have been handled.
|
||||
EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(75, 75));
|
||||
|
||||
// Hit where layers[1] used to be and where layers[3] should now be.
|
||||
hit = GetTargetAPZC(ScreenPoint(25, 25));
|
||||
EXPECT_EQ(apzc3, hit.get());
|
||||
// transformToApzc unapplies both layers[2]'s css transform and the root's
|
||||
// async transform
|
||||
EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc * ScreenPoint(25, 25));
|
||||
// transformToGecko reapplies both the css transform and the async transform
|
||||
// because we have already issued a paint request with it.
|
||||
EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(12.5, 75));
|
||||
|
||||
// This second pan will move the APZC by another 50 pixels.
|
||||
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
|
||||
ApzcPanNoFling(apzcroot, mcc, 100, 50);
|
||||
|
||||
// Hit where layers[3] used to be. It should now hit the root.
|
||||
hit = GetTargetAPZC(ScreenPoint(75, 75));
|
||||
EXPECT_EQ(apzcroot, hit.get());
|
||||
// transformToApzc doesn't unapply the root's own async transform
|
||||
EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc * ScreenPoint(75, 75));
|
||||
// transformToGecko unapplies the full async transform of -100 pixels
|
||||
EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(75, 75));
|
||||
|
||||
// Hit where layers[1] used to be. It should now hit the root.
|
||||
hit = GetTargetAPZC(ScreenPoint(25, 25));
|
||||
EXPECT_EQ(apzcroot, hit.get());
|
||||
// transformToApzc doesn't unapply the root's own async transform
|
||||
EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25));
|
||||
// transformToGecko unapplies the full async transform of -100 pixels
|
||||
EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25));
|
||||
}
|
||||
|
||||
TEST_F(APZHitTestingTester, ComplexMultiLayerTree) {
|
||||
CreateComplexMultiLayerTree();
|
||||
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
|
||||
/* The layer tree looks like this:
|
||||
|
||||
0
|
||||
|----|--+--|----|
|
||||
1 2 4 5
|
||||
| /|\
|
||||
3 6 8 9
|
||||
|
|
||||
7
|
||||
|
||||
Layers 1,2 have the same APZC
|
||||
Layers 4,6,8 have the same APZC
|
||||
Layer 7 has an APZC
|
||||
Layer 9 has an APZC
|
||||
*/
|
||||
|
||||
TestAsyncPanZoomController* nullAPZC = nullptr;
|
||||
// Ensure all the scrollable layers have an APZC
|
||||
EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
|
||||
EXPECT_NE(nullAPZC, ApzcOf(layers[1]));
|
||||
EXPECT_NE(nullAPZC, ApzcOf(layers[2]));
|
||||
EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics());
|
||||
EXPECT_NE(nullAPZC, ApzcOf(layers[4]));
|
||||
EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics());
|
||||
EXPECT_NE(nullAPZC, ApzcOf(layers[6]));
|
||||
EXPECT_NE(nullAPZC, ApzcOf(layers[7]));
|
||||
EXPECT_NE(nullAPZC, ApzcOf(layers[8]));
|
||||
EXPECT_NE(nullAPZC, ApzcOf(layers[9]));
|
||||
// Ensure those that scroll together have the same APZCs
|
||||
EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
|
||||
EXPECT_EQ(ApzcOf(layers[4]), ApzcOf(layers[6]));
|
||||
EXPECT_EQ(ApzcOf(layers[8]), ApzcOf(layers[6]));
|
||||
// Ensure those that don't scroll together have different APZCs
|
||||
EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[4]));
|
||||
EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[7]));
|
||||
EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[9]));
|
||||
EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[7]));
|
||||
EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[9]));
|
||||
EXPECT_NE(ApzcOf(layers[7]), ApzcOf(layers[9]));
|
||||
// Ensure the APZC parent chains are set up correctly
|
||||
TestAsyncPanZoomController* layers1_2 = ApzcOf(layers[1]);
|
||||
TestAsyncPanZoomController* layers4_6_8 = ApzcOf(layers[4]);
|
||||
TestAsyncPanZoomController* layer7 = ApzcOf(layers[7]);
|
||||
TestAsyncPanZoomController* layer9 = ApzcOf(layers[9]);
|
||||
EXPECT_EQ(nullptr, layers1_2->GetParent());
|
||||
EXPECT_EQ(nullptr, layers4_6_8->GetParent());
|
||||
EXPECT_EQ(layers4_6_8, layer7->GetParent());
|
||||
EXPECT_EQ(nullptr, layer9->GetParent());
|
||||
// Ensure the hit-testing tree looks like the layer tree
|
||||
RefPtr<HitTestingTreeNode> root = manager->GetRootNode();
|
||||
RefPtr<HitTestingTreeNode> node5 = root->GetLastChild();
|
||||
RefPtr<HitTestingTreeNode> node4 = node5->GetPrevSibling();
|
||||
RefPtr<HitTestingTreeNode> node2 = node4->GetPrevSibling();
|
||||
RefPtr<HitTestingTreeNode> node1 = node2->GetPrevSibling();
|
||||
RefPtr<HitTestingTreeNode> node3 = node2->GetLastChild();
|
||||
RefPtr<HitTestingTreeNode> node9 = node5->GetLastChild();
|
||||
RefPtr<HitTestingTreeNode> node8 = node9->GetPrevSibling();
|
||||
RefPtr<HitTestingTreeNode> node6 = node8->GetPrevSibling();
|
||||
RefPtr<HitTestingTreeNode> node7 = node6->GetLastChild();
|
||||
EXPECT_EQ(nullptr, node1->GetPrevSibling());
|
||||
EXPECT_EQ(nullptr, node3->GetPrevSibling());
|
||||
EXPECT_EQ(nullptr, node6->GetPrevSibling());
|
||||
EXPECT_EQ(nullptr, node7->GetPrevSibling());
|
||||
EXPECT_EQ(nullptr, node1->GetLastChild());
|
||||
EXPECT_EQ(nullptr, node3->GetLastChild());
|
||||
EXPECT_EQ(nullptr, node4->GetLastChild());
|
||||
EXPECT_EQ(nullptr, node7->GetLastChild());
|
||||
EXPECT_EQ(nullptr, node8->GetLastChild());
|
||||
EXPECT_EQ(nullptr, node9->GetLastChild());
|
||||
|
||||
RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(25, 25));
|
||||
EXPECT_EQ(ApzcOf(layers[1]), hit.get());
|
||||
hit = GetTargetAPZC(ScreenPoint(275, 375));
|
||||
EXPECT_EQ(ApzcOf(layers[9]), hit.get());
|
||||
hit = GetTargetAPZC(ScreenPoint(250, 100));
|
||||
EXPECT_EQ(ApzcOf(layers[7]), hit.get());
|
||||
}
|
||||
|
||||
TEST_F(APZHitTestingTester, TestRepaintFlushOnNewInputBlock) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
|
||||
|
||||
// The main purpose of this test is to verify that touch-start events (or anything
|
||||
// that starts a new input block) don't ever get untransformed. This should always
|
||||
// hold because the APZ code should flush repaints when we start a new input block
|
||||
// and the transform to gecko space should be empty.
|
||||
|
||||
CreateSimpleScrollingLayer();
|
||||
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
TestAsyncPanZoomController* apzcroot = ApzcOf(root);
|
||||
|
||||
// At this point, the following holds (all coordinates in screen pixels):
|
||||
// layers[0] has content from (0,0)-(500,500), clipped by composition bounds (0,0)-(200,200)
|
||||
|
||||
MockFunction<void(std::string checkPointName)> check;
|
||||
|
||||
{
|
||||
InSequence s;
|
||||
|
||||
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1));
|
||||
EXPECT_CALL(check, Call("post-first-touch-start"));
|
||||
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1));
|
||||
EXPECT_CALL(check, Call("post-second-fling"));
|
||||
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1));
|
||||
EXPECT_CALL(check, Call("post-second-touch-start"));
|
||||
}
|
||||
|
||||
// This first pan will move the APZC by 50 pixels, and dispatch a paint request.
|
||||
ApzcPanNoFling(apzcroot, mcc, 100, 50);
|
||||
|
||||
// Verify that a touch start doesn't get untransformed
|
||||
ScreenIntPoint touchPoint(50, 50);
|
||||
MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
|
||||
mti.mTouches.AppendElement(SingleTouchData(0, touchPoint, ScreenSize(0, 0), 0, 0));
|
||||
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
|
||||
EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
|
||||
check.Call("post-first-touch-start");
|
||||
|
||||
// Send a touchend to clear state
|
||||
mti.mType = MultiTouchInput::MULTITOUCH_END;
|
||||
manager->ReceiveInputEvent(mti, nullptr, nullptr);
|
||||
|
||||
mcc->AdvanceByMillis(1000);
|
||||
|
||||
// Now do two pans. The first of these will dispatch a repaint request, as above.
|
||||
// The second will get stuck in the paint throttler because the first one doesn't
|
||||
// get marked as "completed", so this will result in a non-empty LD transform.
|
||||
// (Note that any outstanding repaint requests from the first half of this test
|
||||
// don't impact this half because we advance the time by 1 second, which will trigger
|
||||
// the max-wait-exceeded codepath in the paint throttler).
|
||||
ApzcPanNoFling(apzcroot, mcc, 100, 50);
|
||||
check.Call("post-second-fling");
|
||||
ApzcPanNoFling(apzcroot, mcc, 100, 50);
|
||||
|
||||
// Ensure that a touch start again doesn't get untransformed by flushing
|
||||
// a repaint
|
||||
mti.mType = MultiTouchInput::MULTITOUCH_START;
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
|
||||
EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
|
||||
check.Call("post-second-touch-start");
|
||||
|
||||
mti.mType = MultiTouchInput::MULTITOUCH_END;
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
|
||||
EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
|
||||
}
|
||||
|
||||
TEST_F(APZHitTestingTester, TestRepaintFlushOnWheelEvents) {
|
||||
// The purpose of this test is to ensure that wheel events trigger a repaint
|
||||
// flush as per bug 1166871, and that the wheel event untransform is a no-op.
|
||||
|
||||
CreateSimpleScrollingLayer();
|
||||
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
TestAsyncPanZoomController* apzcroot = ApzcOf(root);
|
||||
|
||||
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(3));
|
||||
ScreenPoint origin(100, 50);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0,
|
||||
ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
|
||||
origin, 0, 10);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr));
|
||||
EXPECT_EQ(origin, swi.mOrigin);
|
||||
|
||||
AsyncTransform viewTransform;
|
||||
ParentLayerPoint point;
|
||||
apzcroot->SampleContentTransformForFrame(&viewTransform, point);
|
||||
EXPECT_EQ(0, point.x);
|
||||
EXPECT_EQ((i + 1) * 10, point.y);
|
||||
EXPECT_EQ(0, viewTransform.mTranslation.x);
|
||||
EXPECT_EQ((i + 1) * -10, viewTransform.mTranslation.y);
|
||||
|
||||
mcc->AdvanceByMillis(5);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(APZHitTestingTester, Bug1148350) {
|
||||
CreateBug1148350LayerTree();
|
||||
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
|
||||
MockFunction<void(std::string checkPointName)> check;
|
||||
{
|
||||
InSequence s;
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(100, 100), 0, ApzcOf(layers[1])->GetGuid())).Times(1);
|
||||
EXPECT_CALL(check, Call("Tapped without transform"));
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(100, 100), 0, ApzcOf(layers[1])->GetGuid())).Times(1);
|
||||
EXPECT_CALL(check, Call("Tapped with interleaved transform"));
|
||||
}
|
||||
|
||||
Tap(manager, 100, 100, mcc, TimeDuration::FromMilliseconds(100));
|
||||
mcc->RunThroughDelayedTasks();
|
||||
check.Call("Tapped without transform");
|
||||
|
||||
uint64_t blockId;
|
||||
TouchDown(manager, 100, 100, mcc->Time(), &blockId);
|
||||
if (gfxPrefs::TouchActionEnabled()) {
|
||||
SetDefaultAllowedTouchBehavior(manager, blockId);
|
||||
}
|
||||
mcc->AdvanceByMillis(100);
|
||||
|
||||
layers[0]->SetVisibleRegion(LayerIntRegion(LayerIntRect(0,50,200,150)));
|
||||
layers[0]->SetBaseTransform(Matrix4x4::Translation(0, 50, 0));
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
|
||||
TouchUp(manager, 100, 100, mcc->Time());
|
||||
mcc->RunThroughDelayedTasks();
|
||||
check.Call("Tapped with interleaved transform");
|
||||
}
|
||||
|
|
@ -0,0 +1,430 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* 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/. */
|
||||
|
||||
#include "APZCTreeManagerTester.h"
|
||||
#include "APZTestCommon.h"
|
||||
#include "InputUtils.h"
|
||||
|
||||
class APZOverscrollHandoffTester : public APZCTreeManagerTester {
|
||||
protected:
|
||||
UniquePtr<ScopedLayerTreeRegistration> registration;
|
||||
TestAsyncPanZoomController* rootApzc;
|
||||
|
||||
void CreateOverscrollHandoffLayerTree1() {
|
||||
const char* layerTreeSyntax = "c(t)";
|
||||
nsIntRegion layerVisibleRegion[] = {
|
||||
nsIntRegion(IntRect(0, 0, 100, 100)),
|
||||
nsIntRegion(IntRect(0, 50, 100, 50))
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
|
||||
SetScrollHandoff(layers[1], root);
|
||||
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
rootApzc = ApzcOf(root);
|
||||
}
|
||||
|
||||
void CreateOverscrollHandoffLayerTree2() {
|
||||
const char* layerTreeSyntax = "c(c(t))";
|
||||
nsIntRegion layerVisibleRegion[] = {
|
||||
nsIntRegion(IntRect(0, 0, 100, 100)),
|
||||
nsIntRegion(IntRect(0, 0, 100, 100)),
|
||||
nsIntRegion(IntRect(0, 50, 100, 50))
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 2, CSSRect(-100, -100, 200, 200));
|
||||
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
|
||||
SetScrollHandoff(layers[1], root);
|
||||
SetScrollHandoff(layers[2], layers[1]);
|
||||
// No ScopedLayerTreeRegistration as that just needs to be done once per test
|
||||
// and this is the second layer tree for a particular test.
|
||||
MOZ_ASSERT(registration);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
rootApzc = ApzcOf(root);
|
||||
}
|
||||
|
||||
void CreateOverscrollHandoffLayerTree3() {
|
||||
const char* layerTreeSyntax = "c(c(t)c(t))";
|
||||
nsIntRegion layerVisibleRegion[] = {
|
||||
nsIntRegion(IntRect(0, 0, 100, 100)), // root
|
||||
nsIntRegion(IntRect(0, 0, 100, 50)), // scrolling parent 1
|
||||
nsIntRegion(IntRect(0, 0, 100, 50)), // scrolling child 1
|
||||
nsIntRegion(IntRect(0, 50, 100, 50)), // scrolling parent 2
|
||||
nsIntRegion(IntRect(0, 50, 100, 50)) // scrolling child 2
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, 100));
|
||||
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
|
||||
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 50, 100, 100));
|
||||
SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 3, CSSRect(0, 50, 100, 100));
|
||||
SetScrollHandoff(layers[2], layers[1]);
|
||||
SetScrollHandoff(layers[4], layers[3]);
|
||||
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
}
|
||||
|
||||
void CreateScrollgrabLayerTree(bool makeParentScrollable = true) {
|
||||
const char* layerTreeSyntax = "c(t)";
|
||||
nsIntRegion layerVisibleRegion[] = {
|
||||
nsIntRegion(IntRect(0, 0, 100, 100)), // scroll-grabbing parent
|
||||
nsIntRegion(IntRect(0, 20, 100, 80)) // child
|
||||
};
|
||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
|
||||
float parentHeight = makeParentScrollable ? 120 : 100;
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, parentHeight));
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 200));
|
||||
SetScrollHandoff(layers[1], root);
|
||||
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
rootApzc = ApzcOf(root);
|
||||
rootApzc->GetFrameMetrics().SetHasScrollgrab(true);
|
||||
}
|
||||
|
||||
void TestFlingAcceleration() {
|
||||
// Jack up the fling acceleration multiplier so we can easily determine
|
||||
// whether acceleration occured.
|
||||
const float kAcceleration = 100.0f;
|
||||
SCOPED_GFX_PREF(APZFlingAccelBaseMultiplier, float, kAcceleration);
|
||||
|
||||
RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
|
||||
|
||||
// Pan once, enough to fully scroll the scrollgrab parent and then scroll
|
||||
// and fling the child.
|
||||
Pan(manager, mcc, 70, 40);
|
||||
|
||||
// Give the fling animation a chance to start.
|
||||
SampleAnimationsOnce();
|
||||
|
||||
float childVelocityAfterFling1 = childApzc->GetVelocityVector().y;
|
||||
|
||||
// Pan again.
|
||||
Pan(manager, mcc, 70, 40);
|
||||
|
||||
// Give the fling animation a chance to start.
|
||||
// This time it should be accelerated.
|
||||
SampleAnimationsOnce();
|
||||
|
||||
float childVelocityAfterFling2 = childApzc->GetVelocityVector().y;
|
||||
|
||||
// We should have accelerated once.
|
||||
// The division by 2 is to account for friction.
|
||||
EXPECT_GT(childVelocityAfterFling2,
|
||||
childVelocityAfterFling1 * kAcceleration / 2);
|
||||
|
||||
// We should not have accelerated twice.
|
||||
// The division by 4 is to account for friction.
|
||||
EXPECT_LE(childVelocityAfterFling2,
|
||||
childVelocityAfterFling1 * kAcceleration * kAcceleration / 4);
|
||||
}
|
||||
};
|
||||
|
||||
// Here we test that if the processing of a touch block is deferred while we
|
||||
// wait for content to send a prevent-default message, overscroll is still
|
||||
// handed off correctly when the block is processed.
|
||||
TEST_F(APZOverscrollHandoffTester, DeferredInputEventProcessing) {
|
||||
// Set up the APZC tree.
|
||||
CreateOverscrollHandoffLayerTree1();
|
||||
|
||||
TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]);
|
||||
|
||||
// Enable touch-listeners so that we can separate the queueing of input
|
||||
// events from them being processed.
|
||||
childApzc->SetWaitForMainThread();
|
||||
|
||||
// Queue input events for a pan.
|
||||
uint64_t blockId = 0;
|
||||
ApzcPanNoFling(childApzc, mcc, 90, 30, &blockId);
|
||||
|
||||
// Allow the pan to be processed.
|
||||
childApzc->ContentReceivedInputBlock(blockId, false);
|
||||
childApzc->ConfirmTarget(blockId);
|
||||
|
||||
// Make sure overscroll was handed off correctly.
|
||||
EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y);
|
||||
EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
|
||||
}
|
||||
|
||||
// Here we test that if the layer structure changes in between two input
|
||||
// blocks being queued, and the first block is only processed after the second
|
||||
// one has been queued, overscroll handoff for the first block follows
|
||||
// the original layer structure while overscroll handoff for the second block
|
||||
// follows the new layer structure.
|
||||
TEST_F(APZOverscrollHandoffTester, LayerStructureChangesWhileEventsArePending) {
|
||||
// Set up an initial APZC tree.
|
||||
CreateOverscrollHandoffLayerTree1();
|
||||
|
||||
TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]);
|
||||
|
||||
// Enable touch-listeners so that we can separate the queueing of input
|
||||
// events from them being processed.
|
||||
childApzc->SetWaitForMainThread();
|
||||
|
||||
// Queue input events for a pan.
|
||||
uint64_t blockId = 0;
|
||||
ApzcPanNoFling(childApzc, mcc, 90, 30, &blockId);
|
||||
|
||||
// Modify the APZC tree to insert a new APZC 'middle' into the handoff chain
|
||||
// between the child and the root.
|
||||
CreateOverscrollHandoffLayerTree2();
|
||||
RefPtr<Layer> middle = layers[1];
|
||||
childApzc->SetWaitForMainThread();
|
||||
TestAsyncPanZoomController* middleApzc = ApzcOf(middle);
|
||||
|
||||
// Queue input events for another pan.
|
||||
uint64_t secondBlockId = 0;
|
||||
ApzcPanNoFling(childApzc, mcc, 30, 90, &secondBlockId);
|
||||
|
||||
// Allow the first pan to be processed.
|
||||
childApzc->ContentReceivedInputBlock(blockId, false);
|
||||
childApzc->ConfirmTarget(blockId);
|
||||
|
||||
// Make sure things have scrolled according to the handoff chain in
|
||||
// place at the time the touch-start of the first pan was queued.
|
||||
EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y);
|
||||
EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
|
||||
EXPECT_EQ(0, middleApzc->GetFrameMetrics().GetScrollOffset().y);
|
||||
|
||||
// Allow the second pan to be processed.
|
||||
childApzc->ContentReceivedInputBlock(secondBlockId, false);
|
||||
childApzc->ConfirmTarget(secondBlockId);
|
||||
|
||||
// Make sure things have scrolled according to the handoff chain in
|
||||
// place at the time the touch-start of the second pan was queued.
|
||||
EXPECT_EQ(0, childApzc->GetFrameMetrics().GetScrollOffset().y);
|
||||
EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
|
||||
EXPECT_EQ(-10, middleApzc->GetFrameMetrics().GetScrollOffset().y);
|
||||
}
|
||||
|
||||
// Test that putting a second finger down on an APZC while a down-chain APZC
|
||||
// is overscrolled doesn't result in being stuck in overscroll.
|
||||
TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1073250) {
|
||||
// Enable overscrolling.
|
||||
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
|
||||
|
||||
CreateOverscrollHandoffLayerTree1();
|
||||
|
||||
TestAsyncPanZoomController* child = ApzcOf(layers[1]);
|
||||
|
||||
// Pan, causing the parent APZC to overscroll.
|
||||
Pan(manager, mcc, 10, 40, true /* keep finger down */);
|
||||
EXPECT_FALSE(child->IsOverscrolled());
|
||||
EXPECT_TRUE(rootApzc->IsOverscrolled());
|
||||
|
||||
// Put a second finger down.
|
||||
MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
|
||||
// Use the same touch identifier for the first touch (0) as Pan(). (A bit hacky.)
|
||||
secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0));
|
||||
secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0));
|
||||
manager->ReceiveInputEvent(secondFingerDown, nullptr, nullptr);
|
||||
|
||||
// Release the fingers.
|
||||
MultiTouchInput fingersUp = secondFingerDown;
|
||||
fingersUp.mType = MultiTouchInput::MULTITOUCH_END;
|
||||
manager->ReceiveInputEvent(fingersUp, nullptr, nullptr);
|
||||
|
||||
// Allow any animations to run their course.
|
||||
child->AdvanceAnimationsUntilEnd();
|
||||
rootApzc->AdvanceAnimationsUntilEnd();
|
||||
|
||||
// Make sure nothing is overscrolled.
|
||||
EXPECT_FALSE(child->IsOverscrolled());
|
||||
EXPECT_FALSE(rootApzc->IsOverscrolled());
|
||||
}
|
||||
|
||||
// This is almost exactly like StuckInOverscroll_Bug1073250, except the
|
||||
// APZC receiving the input events for the first touch block is the child
|
||||
// (and thus not the same APZC that overscrolls, which is the parent).
|
||||
TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1231228) {
|
||||
// Enable overscrolling.
|
||||
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
|
||||
|
||||
CreateOverscrollHandoffLayerTree1();
|
||||
|
||||
TestAsyncPanZoomController* child = ApzcOf(layers[1]);
|
||||
|
||||
// Pan, causing the parent APZC to overscroll.
|
||||
Pan(manager, mcc, 60, 90, true /* keep finger down */);
|
||||
EXPECT_FALSE(child->IsOverscrolled());
|
||||
EXPECT_TRUE(rootApzc->IsOverscrolled());
|
||||
|
||||
// Put a second finger down.
|
||||
MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
|
||||
// Use the same touch identifier for the first touch (0) as Pan(). (A bit hacky.)
|
||||
secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0));
|
||||
secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0));
|
||||
manager->ReceiveInputEvent(secondFingerDown, nullptr, nullptr);
|
||||
|
||||
// Release the fingers.
|
||||
MultiTouchInput fingersUp = secondFingerDown;
|
||||
fingersUp.mType = MultiTouchInput::MULTITOUCH_END;
|
||||
manager->ReceiveInputEvent(fingersUp, nullptr, nullptr);
|
||||
|
||||
// Allow any animations to run their course.
|
||||
child->AdvanceAnimationsUntilEnd();
|
||||
rootApzc->AdvanceAnimationsUntilEnd();
|
||||
|
||||
// Make sure nothing is overscrolled.
|
||||
EXPECT_FALSE(child->IsOverscrolled());
|
||||
EXPECT_FALSE(rootApzc->IsOverscrolled());
|
||||
}
|
||||
|
||||
// Test that flinging in a direction where one component of the fling goes into
|
||||
// overscroll but the other doesn't, results in just the one component being
|
||||
// handed off to the parent, while the original APZC continues flinging in the
|
||||
// other direction.
|
||||
TEST_F(APZOverscrollHandoffTester, PartialFlingHandoff) {
|
||||
CreateOverscrollHandoffLayerTree1();
|
||||
|
||||
// Fling up and to the left. The child APZC has room to scroll up, but not
|
||||
// to the left, so the horizontal component of the fling should be handed
|
||||
// off to the parent APZC.
|
||||
Pan(manager, mcc, ScreenPoint(90, 90), ScreenPoint(55, 55));
|
||||
|
||||
RefPtr<TestAsyncPanZoomController> parent = ApzcOf(root);
|
||||
RefPtr<TestAsyncPanZoomController> child = ApzcOf(layers[1]);
|
||||
|
||||
// Advance the child's fling animation once to give the partial handoff
|
||||
// a chance to occur.
|
||||
mcc->AdvanceByMillis(10);
|
||||
child->AdvanceAnimations(mcc->Time());
|
||||
|
||||
// Assert that partial handoff has occurred.
|
||||
child->AssertStateIsFling();
|
||||
parent->AssertStateIsFling();
|
||||
}
|
||||
|
||||
// Here we test that if two flings are happening simultaneously, overscroll
|
||||
// is handed off correctly for each.
|
||||
TEST_F(APZOverscrollHandoffTester, SimultaneousFlings) {
|
||||
// Set up an initial APZC tree.
|
||||
CreateOverscrollHandoffLayerTree3();
|
||||
|
||||
RefPtr<TestAsyncPanZoomController> parent1 = ApzcOf(layers[1]);
|
||||
RefPtr<TestAsyncPanZoomController> child1 = ApzcOf(layers[2]);
|
||||
RefPtr<TestAsyncPanZoomController> parent2 = ApzcOf(layers[3]);
|
||||
RefPtr<TestAsyncPanZoomController> child2 = ApzcOf(layers[4]);
|
||||
|
||||
// Pan on the lower child.
|
||||
Pan(child2, mcc, 45, 5);
|
||||
|
||||
// Pan on the upper child.
|
||||
Pan(child1, mcc, 95, 55);
|
||||
|
||||
// Check that child1 and child2 are in a FLING state.
|
||||
child1->AssertStateIsFling();
|
||||
child2->AssertStateIsFling();
|
||||
|
||||
// Advance the animations on child1 and child2 until their end.
|
||||
child1->AdvanceAnimationsUntilEnd();
|
||||
child2->AdvanceAnimationsUntilEnd();
|
||||
|
||||
// Check that the flings have been handed off to the parents.
|
||||
child1->AssertStateIsReset();
|
||||
parent1->AssertStateIsFling();
|
||||
child2->AssertStateIsReset();
|
||||
parent2->AssertStateIsFling();
|
||||
}
|
||||
|
||||
TEST_F(APZOverscrollHandoffTester, Scrollgrab) {
|
||||
// Set up the layer tree
|
||||
CreateScrollgrabLayerTree();
|
||||
|
||||
RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
|
||||
|
||||
// Pan on the child, enough to fully scroll the scrollgrab parent (20 px)
|
||||
// and leave some more (another 15 px) for the child.
|
||||
Pan(childApzc, mcc, 80, 45);
|
||||
|
||||
// Check that the parent and child have scrolled as much as we expect.
|
||||
EXPECT_EQ(20, rootApzc->GetFrameMetrics().GetScrollOffset().y);
|
||||
EXPECT_EQ(15, childApzc->GetFrameMetrics().GetScrollOffset().y);
|
||||
}
|
||||
|
||||
TEST_F(APZOverscrollHandoffTester, ScrollgrabFling) {
|
||||
// Set up the layer tree
|
||||
CreateScrollgrabLayerTree();
|
||||
|
||||
RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
|
||||
|
||||
// Pan on the child, not enough to fully scroll the scrollgrab parent.
|
||||
Pan(childApzc, mcc, 80, 70);
|
||||
|
||||
// Check that it is the scrollgrab parent that's in a fling, not the child.
|
||||
rootApzc->AssertStateIsFling();
|
||||
childApzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
TEST_F(APZOverscrollHandoffTester, ScrollgrabFlingAcceleration1) {
|
||||
CreateScrollgrabLayerTree(true /* make parent scrollable */);
|
||||
TestFlingAcceleration();
|
||||
}
|
||||
|
||||
TEST_F(APZOverscrollHandoffTester, ScrollgrabFlingAcceleration2) {
|
||||
CreateScrollgrabLayerTree(false /* do not make parent scrollable */);
|
||||
TestFlingAcceleration();
|
||||
}
|
||||
|
||||
TEST_F(APZOverscrollHandoffTester, ImmediateHandoffDisallowed_Pan) {
|
||||
SCOPED_GFX_PREF(APZAllowImmediateHandoff, bool, false);
|
||||
|
||||
CreateOverscrollHandoffLayerTree1();
|
||||
|
||||
RefPtr<TestAsyncPanZoomController> parentApzc = ApzcOf(root);
|
||||
RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
|
||||
|
||||
// Pan on the child, enough to scroll it to its end and have scroll
|
||||
// left to hand off. Since immediate handoff is disallowed, we expect
|
||||
// the leftover scroll not to be handed off.
|
||||
Pan(childApzc, mcc, 60, 5);
|
||||
|
||||
// Verify that the parent has not scrolled.
|
||||
EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y);
|
||||
EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetScrollOffset().y);
|
||||
|
||||
// Pan again on the child. This time, since the child was scrolled to
|
||||
// its end when the gesture began, we expect the scroll to be handed off.
|
||||
Pan(childApzc, mcc, 60, 50);
|
||||
|
||||
// Verify that the parent scrolled.
|
||||
EXPECT_EQ(10, parentApzc->GetFrameMetrics().GetScrollOffset().y);
|
||||
}
|
||||
|
||||
TEST_F(APZOverscrollHandoffTester, ImmediateHandoffDisallowed_Fling) {
|
||||
SCOPED_GFX_PREF(APZAllowImmediateHandoff, bool, false);
|
||||
|
||||
CreateOverscrollHandoffLayerTree1();
|
||||
|
||||
RefPtr<TestAsyncPanZoomController> parentApzc = ApzcOf(root);
|
||||
RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
|
||||
|
||||
// Pan on the child, enough to get very close to the end, so that the
|
||||
// subsequent fling reaches the end and has leftover velocity to hand off.
|
||||
Pan(childApzc, mcc, 60, 12);
|
||||
|
||||
// Allow the fling to run its course.
|
||||
childApzc->AdvanceAnimationsUntilEnd();
|
||||
parentApzc->AdvanceAnimationsUntilEnd();
|
||||
|
||||
// Verify that the parent has not scrolled.
|
||||
// The first comparison needs to be an ASSERT_NEAR because the fling
|
||||
// computations are such that the final scroll position can be within
|
||||
// COORDINATE_EPSILON of the end rather than right at the end.
|
||||
ASSERT_NEAR(50, childApzc->GetFrameMetrics().GetScrollOffset().y, COORDINATE_EPSILON);
|
||||
EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetScrollOffset().y);
|
||||
|
||||
// Pan again on the child. This time, since the child was scrolled to
|
||||
// its end when the gesture began, we expect the scroll to be handed off.
|
||||
Pan(childApzc, mcc, 60, 50);
|
||||
|
||||
// Allow the fling to run its course. The fling should also be handed off.
|
||||
childApzc->AdvanceAnimationsUntilEnd();
|
||||
parentApzc->AdvanceAnimationsUntilEnd();
|
||||
|
||||
// Verify that the parent scrolled from the fling.
|
||||
EXPECT_GT(parentApzc->GetFrameMetrics().GetScrollOffset().y, 10);
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* 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/. */
|
||||
|
||||
#include "APZCBasicTester.h"
|
||||
#include "APZTestCommon.h"
|
||||
#include "InputUtils.h"
|
||||
|
||||
class APZCPanningTester : public APZCBasicTester {
|
||||
protected:
|
||||
void DoPanTest(bool aShouldTriggerScroll, bool aShouldBeConsumed, uint32_t aBehavior)
|
||||
{
|
||||
if (aShouldTriggerScroll) {
|
||||
// One repaint request for each pan.
|
||||
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2);
|
||||
} else {
|
||||
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
|
||||
}
|
||||
|
||||
int touchStart = 50;
|
||||
int touchEnd = 10;
|
||||
ParentLayerPoint pointOut;
|
||||
AsyncTransform viewTransformOut;
|
||||
|
||||
nsTArray<uint32_t> allowedTouchBehaviors;
|
||||
allowedTouchBehaviors.AppendElement(aBehavior);
|
||||
|
||||
// Pan down
|
||||
PanAndCheckStatus(apzc, mcc, touchStart, touchEnd, aShouldBeConsumed, &allowedTouchBehaviors);
|
||||
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
|
||||
|
||||
if (aShouldTriggerScroll) {
|
||||
EXPECT_EQ(ParentLayerPoint(0, -(touchEnd-touchStart)), pointOut);
|
||||
EXPECT_NE(AsyncTransform(), viewTransformOut);
|
||||
} else {
|
||||
EXPECT_EQ(ParentLayerPoint(), pointOut);
|
||||
EXPECT_EQ(AsyncTransform(), viewTransformOut);
|
||||
}
|
||||
|
||||
// Clear the fling from the previous pan, or stopping it will
|
||||
// consume the next touchstart
|
||||
apzc->CancelAnimation();
|
||||
|
||||
// Pan back
|
||||
PanAndCheckStatus(apzc, mcc, touchEnd, touchStart, aShouldBeConsumed, &allowedTouchBehaviors);
|
||||
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
|
||||
|
||||
EXPECT_EQ(ParentLayerPoint(), pointOut);
|
||||
EXPECT_EQ(AsyncTransform(), viewTransformOut);
|
||||
}
|
||||
|
||||
void DoPanWithPreventDefaultTest()
|
||||
{
|
||||
MakeApzcWaitForMainThread();
|
||||
|
||||
int touchStart = 50;
|
||||
int touchEnd = 10;
|
||||
ParentLayerPoint pointOut;
|
||||
AsyncTransform viewTransformOut;
|
||||
uint64_t blockId = 0;
|
||||
|
||||
// Pan down
|
||||
nsTArray<uint32_t> allowedTouchBehaviors;
|
||||
allowedTouchBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
|
||||
PanAndCheckStatus(apzc, mcc, touchStart, touchEnd, true, &allowedTouchBehaviors, &blockId);
|
||||
|
||||
// Send the signal that content has handled and preventDefaulted the touch
|
||||
// events. This flushes the event queue.
|
||||
apzc->ContentReceivedInputBlock(blockId, true);
|
||||
|
||||
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
|
||||
EXPECT_EQ(ParentLayerPoint(), pointOut);
|
||||
EXPECT_EQ(AsyncTransform(), viewTransformOut);
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(APZCPanningTester, Pan) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
|
||||
DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::NONE);
|
||||
}
|
||||
|
||||
// In the each of the following 4 pan tests we are performing two pan gestures: vertical pan from top
|
||||
// to bottom and back - from bottom to top.
|
||||
// According to the pointer-events/touch-action spec AUTO and PAN_Y touch-action values allow vertical
|
||||
// scrolling while NONE and PAN_X forbid it. The first parameter of DoPanTest method specifies this
|
||||
// behavior.
|
||||
// However, the events will be marked as consumed even if the behavior in PAN_X, because the user could
|
||||
// move their finger horizontally too - APZ has no way of knowing beforehand and so must consume the
|
||||
// events.
|
||||
TEST_F(APZCPanningTester, PanWithTouchActionAuto) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
|
||||
DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
|
||||
| mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
|
||||
}
|
||||
|
||||
TEST_F(APZCPanningTester, PanWithTouchActionNone) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
|
||||
DoPanTest(false, false, 0);
|
||||
}
|
||||
|
||||
TEST_F(APZCPanningTester, PanWithTouchActionPanX) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
|
||||
DoPanTest(false, true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN);
|
||||
}
|
||||
|
||||
TEST_F(APZCPanningTester, PanWithTouchActionPanY) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
|
||||
DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
|
||||
}
|
||||
|
||||
TEST_F(APZCPanningTester, PanWithPreventDefaultAndTouchAction) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
|
||||
DoPanWithPreventDefaultTest();
|
||||
}
|
||||
|
||||
TEST_F(APZCPanningTester, PanWithPreventDefault) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
|
||||
DoPanWithPreventDefaultTest();
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* 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/. */
|
||||
|
||||
#include "APZCBasicTester.h"
|
||||
#include "APZTestCommon.h"
|
||||
#include "InputUtils.h"
|
||||
|
||||
class APZCPinchTester : public APZCBasicTester {
|
||||
public:
|
||||
explicit APZCPinchTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES)
|
||||
: APZCBasicTester(aGestureBehavior)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
FrameMetrics GetPinchableFrameMetrics()
|
||||
{
|
||||
FrameMetrics fm;
|
||||
fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200));
|
||||
fm.SetScrollableRect(CSSRect(0, 0, 980, 1000));
|
||||
fm.SetScrollOffset(CSSPoint(300, 300));
|
||||
fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0));
|
||||
// APZC only allows zooming on the root scrollable frame.
|
||||
fm.SetIsRootContent(true);
|
||||
// the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100
|
||||
return fm;
|
||||
}
|
||||
|
||||
void DoPinchTest(bool aShouldTriggerPinch,
|
||||
nsTArray<uint32_t> *aAllowedTouchBehaviors = nullptr)
|
||||
{
|
||||
apzc->SetFrameMetrics(GetPinchableFrameMetrics());
|
||||
MakeApzcZoomable();
|
||||
|
||||
if (aShouldTriggerPinch) {
|
||||
// One repaint request for each gesture.
|
||||
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2);
|
||||
} else {
|
||||
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
|
||||
}
|
||||
|
||||
int touchInputId = 0;
|
||||
if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) {
|
||||
PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 1.25, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
|
||||
} else {
|
||||
PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 1.25, aShouldTriggerPinch);
|
||||
}
|
||||
|
||||
FrameMetrics fm = apzc->GetFrameMetrics();
|
||||
|
||||
if (aShouldTriggerPinch) {
|
||||
// the visible area of the document in CSS pixels is now x=305 y=310 w=40 h=80
|
||||
EXPECT_EQ(2.5f, fm.GetZoom().ToScaleFactor().scale);
|
||||
EXPECT_EQ(305, fm.GetScrollOffset().x);
|
||||
EXPECT_EQ(310, fm.GetScrollOffset().y);
|
||||
} else {
|
||||
// The frame metrics should stay the same since touch-action:none makes
|
||||
// apzc ignore pinch gestures.
|
||||
EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale);
|
||||
EXPECT_EQ(300, fm.GetScrollOffset().x);
|
||||
EXPECT_EQ(300, fm.GetScrollOffset().y);
|
||||
}
|
||||
|
||||
// part 2 of the test, move to the top-right corner of the page and pinch and
|
||||
// make sure we stay in the correct spot
|
||||
fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0));
|
||||
fm.SetScrollOffset(CSSPoint(930, 5));
|
||||
apzc->SetFrameMetrics(fm);
|
||||
// the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100
|
||||
|
||||
if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) {
|
||||
PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 0.5, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
|
||||
} else {
|
||||
PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 0.5, aShouldTriggerPinch);
|
||||
}
|
||||
|
||||
fm = apzc->GetFrameMetrics();
|
||||
|
||||
if (aShouldTriggerPinch) {
|
||||
// the visible area of the document in CSS pixels is now x=880 y=0 w=100 h=200
|
||||
EXPECT_EQ(1.0f, fm.GetZoom().ToScaleFactor().scale);
|
||||
EXPECT_EQ(880, fm.GetScrollOffset().x);
|
||||
EXPECT_EQ(0, fm.GetScrollOffset().y);
|
||||
} else {
|
||||
EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale);
|
||||
EXPECT_EQ(930, fm.GetScrollOffset().x);
|
||||
EXPECT_EQ(5, fm.GetScrollOffset().y);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class APZCPinchGestureDetectorTester : public APZCPinchTester {
|
||||
public:
|
||||
APZCPinchGestureDetectorTester()
|
||||
: APZCPinchTester(AsyncPanZoomController::USE_GESTURE_DETECTOR)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(APZCPinchTester, Pinch_DefaultGestures_NoTouchAction) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
|
||||
DoPinchTest(true);
|
||||
}
|
||||
|
||||
TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_NoTouchAction) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
|
||||
DoPinchTest(true);
|
||||
}
|
||||
|
||||
TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNone) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
|
||||
nsTArray<uint32_t> behaviors = { mozilla::layers::AllowedTouchBehavior::NONE,
|
||||
mozilla::layers::AllowedTouchBehavior::NONE };
|
||||
DoPinchTest(false, &behaviors);
|
||||
}
|
||||
|
||||
TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionZoom) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
|
||||
nsTArray<uint32_t> behaviors;
|
||||
behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
|
||||
behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
|
||||
DoPinchTest(true, &behaviors);
|
||||
}
|
||||
|
||||
TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNotAllowZoom) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
|
||||
nsTArray<uint32_t> behaviors;
|
||||
behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
|
||||
behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
|
||||
DoPinchTest(false, &behaviors);
|
||||
}
|
||||
|
||||
TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) {
|
||||
FrameMetrics originalMetrics = GetPinchableFrameMetrics();
|
||||
apzc->SetFrameMetrics(originalMetrics);
|
||||
|
||||
MakeApzcWaitForMainThread();
|
||||
MakeApzcZoomable();
|
||||
|
||||
int touchInputId = 0;
|
||||
uint64_t blockId = 0;
|
||||
PinchWithTouchInput(apzc, 250, 300, 1.25, touchInputId, nullptr, nullptr, &blockId);
|
||||
|
||||
// Send the prevent-default notification for the touch block
|
||||
apzc->ContentReceivedInputBlock(blockId, true);
|
||||
|
||||
// verify the metrics didn't change (i.e. the pinch was ignored)
|
||||
FrameMetrics fm = apzc->GetFrameMetrics();
|
||||
EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom());
|
||||
EXPECT_EQ(originalMetrics.GetScrollOffset().x, fm.GetScrollOffset().x);
|
||||
EXPECT_EQ(originalMetrics.GetScrollOffset().y, fm.GetScrollOffset().y);
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
TEST_F(APZCPinchTester, Panning_TwoFinger_ZoomDisabled) {
|
||||
// set up APZ
|
||||
apzc->SetFrameMetrics(GetPinchableFrameMetrics());
|
||||
MakeApzcUnzoomable();
|
||||
|
||||
nsEventStatus statuses[3]; // scalebegin, scale, scaleend
|
||||
PinchWithPinchInput(apzc, 250, 350, 200, 300, 10, &statuses);
|
||||
|
||||
FrameMetrics fm = apzc->GetFrameMetrics();
|
||||
|
||||
// It starts from (300, 300), then moves the focus point from (250, 350) to
|
||||
// (200, 300) pans by (50, 50) screen pixels, but there is a 2x zoom, which
|
||||
// causes the scroll offset to change by half of that (25, 25) pixels.
|
||||
EXPECT_EQ(325, fm.GetScrollOffset().x);
|
||||
EXPECT_EQ(325, fm.GetScrollOffset().y);
|
||||
EXPECT_EQ(2.0, fm.GetZoom().ToScaleFactor().scale);
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* 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/. */
|
||||
|
||||
#include "APZCTreeManagerTester.h"
|
||||
#include "APZTestCommon.h"
|
||||
#include "InputUtils.h"
|
||||
|
||||
TEST_F(APZCTreeManagerTester, ScrollablePaintedLayers) {
|
||||
CreateSimpleMultiLayerTree();
|
||||
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
|
||||
|
||||
// both layers have the same scrollId
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
|
||||
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
|
||||
TestAsyncPanZoomController* nullAPZC = nullptr;
|
||||
// so they should have the same APZC
|
||||
EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
|
||||
EXPECT_NE(nullAPZC, ApzcOf(layers[1]));
|
||||
EXPECT_NE(nullAPZC, ApzcOf(layers[2]));
|
||||
EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
|
||||
|
||||
// Change the scrollId of layers[1], and verify the APZC changes
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[2]));
|
||||
|
||||
// Change the scrollId of layers[2] to match that of layers[1], ensure we get the same
|
||||
// APZC for both again
|
||||
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
|
||||
}
|
||||
|
||||
TEST_F(APZCTreeManagerTester, Bug1068268) {
|
||||
CreatePotentiallyLeakingTree();
|
||||
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
|
||||
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
RefPtr<HitTestingTreeNode> root = manager->GetRootNode();
|
||||
RefPtr<HitTestingTreeNode> node2 = root->GetFirstChild()->GetFirstChild();
|
||||
RefPtr<HitTestingTreeNode> node5 = root->GetLastChild()->GetLastChild();
|
||||
|
||||
EXPECT_EQ(ApzcOf(layers[2]), node5->GetApzc());
|
||||
EXPECT_EQ(ApzcOf(layers[2]), node2->GetApzc());
|
||||
EXPECT_EQ(ApzcOf(layers[0]), ApzcOf(layers[2])->GetParent());
|
||||
EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[5]));
|
||||
|
||||
EXPECT_EQ(node2->GetFirstChild(), node2->GetLastChild());
|
||||
EXPECT_EQ(ApzcOf(layers[3]), node2->GetLastChild()->GetApzc());
|
||||
EXPECT_EQ(node5->GetFirstChild(), node5->GetLastChild());
|
||||
EXPECT_EQ(ApzcOf(layers[6]), node5->GetLastChild()->GetApzc());
|
||||
EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[3])->GetParent());
|
||||
EXPECT_EQ(ApzcOf(layers[5]), ApzcOf(layers[6])->GetParent());
|
||||
}
|
||||
|
||||
TEST_F(APZCTreeManagerTester, Bug1194876) {
|
||||
CreateBug1194876Tree();
|
||||
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
|
||||
uint64_t blockId;
|
||||
nsTArray<ScrollableLayerGuid> targets;
|
||||
|
||||
// First touch goes down, APZCTM will hit layers[1] because it is on top of
|
||||
// layers[0], but we tell it the real target APZC is layers[0].
|
||||
MultiTouchInput mti;
|
||||
mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
|
||||
mti.mTouches.AppendElement(SingleTouchData(0, ParentLayerPoint(25, 50), ScreenSize(0, 0), 0, 0));
|
||||
manager->ReceiveInputEvent(mti, nullptr, &blockId);
|
||||
manager->ContentReceivedInputBlock(blockId, false);
|
||||
targets.AppendElement(ApzcOf(layers[0])->GetGuid());
|
||||
manager->SetTargetAPZC(blockId, targets);
|
||||
|
||||
// Around here, the above touch will get processed by ApzcOf(layers[0])
|
||||
|
||||
// Second touch goes down (first touch remains down), APZCTM will again hit
|
||||
// layers[1]. Again we tell it both touches landed on layers[0], but because
|
||||
// layers[1] is the RCD layer, it will end up being the multitouch target.
|
||||
mti.mTouches.AppendElement(SingleTouchData(1, ParentLayerPoint(75, 50), ScreenSize(0, 0), 0, 0));
|
||||
manager->ReceiveInputEvent(mti, nullptr, &blockId);
|
||||
manager->ContentReceivedInputBlock(blockId, false);
|
||||
targets.AppendElement(ApzcOf(layers[0])->GetGuid());
|
||||
manager->SetTargetAPZC(blockId, targets);
|
||||
|
||||
// Around here, the above multi-touch will get processed by ApzcOf(layers[1]).
|
||||
// We want to ensure that ApzcOf(layers[0]) has had its state cleared, because
|
||||
// otherwise it will do things like dispatch spurious long-tap events.
|
||||
|
||||
EXPECT_CALL(*mcc, HandleLongTap(_, _, _, _)).Times(0);
|
||||
}
|
||||
|
||||
TEST_F(APZCTreeManagerTester, Bug1198900) {
|
||||
// This is just a test that cancels a wheel event to make sure it doesn't
|
||||
// crash.
|
||||
CreateSimpleDTCScrollingLayer();
|
||||
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
|
||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||
|
||||
ScreenPoint origin(100, 50);
|
||||
ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0,
|
||||
ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
|
||||
origin, 0, 10);
|
||||
uint64_t blockId;
|
||||
manager->ReceiveInputEvent(swi, nullptr, &blockId);
|
||||
manager->ContentReceivedInputBlock(blockId, /* preventDefault= */ true);
|
||||
}
|
||||
|
|
@ -5,7 +5,14 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'TestAsyncPanZoomController.cpp',
|
||||
'TestBasic.cpp',
|
||||
'TestEventRegions.cpp',
|
||||
'TestGestureDetector.cpp',
|
||||
'TestHitTesting.cpp',
|
||||
'TestOverscrollHandoff.cpp',
|
||||
'TestPanning.cpp',
|
||||
'TestPinching.cpp',
|
||||
'TestTreeManager.cpp',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
|
|
@ -536,6 +536,7 @@ window.onload = function() {
|
|||
});
|
||||
}
|
||||
|
||||
SimpleTest.testInChaosMode();
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -44,6 +44,7 @@ window.onload = function() {
|
|||
});
|
||||
}
|
||||
|
||||
SimpleTest.testInChaosMode();
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "gfxPrefs.h"
|
||||
#include "LayersLogging.h"
|
||||
#include "mozilla/BasicEvents.h"
|
||||
#include "mozilla/IntegerPrintfMacros.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/TouchEvents.h"
|
||||
|
|
|
@ -441,6 +441,9 @@ ImageHost::SetCompositor(Compositor* aCompositor)
|
|||
img.mFrontBuffer->SetCompositor(aCompositor);
|
||||
}
|
||||
}
|
||||
if (mImageHostOverlay) {
|
||||
mImageHostOverlay->SetCompositor(aCompositor);
|
||||
}
|
||||
CompositableHost::SetCompositor(aCompositor);
|
||||
}
|
||||
|
||||
|
@ -586,6 +589,9 @@ ImageHostOverlay::ImageHostOverlay()
|
|||
|
||||
ImageHostOverlay::~ImageHostOverlay()
|
||||
{
|
||||
if (mCompositor) {
|
||||
mCompositor->RemoveImageHostOverlay(this);
|
||||
}
|
||||
MOZ_COUNT_DTOR(ImageHostOverlay);
|
||||
}
|
||||
|
||||
|
@ -601,6 +607,18 @@ ImageHostOverlay::IsValid(OverlaySource aOverlay)
|
|||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
ImageHostOverlay::SetCompositor(Compositor* aCompositor)
|
||||
{
|
||||
if (mCompositor && (mCompositor != aCompositor)) {
|
||||
mCompositor->RemoveImageHostOverlay(this);
|
||||
}
|
||||
if (aCompositor) {
|
||||
aCompositor->AddImageHostOverlay(this);
|
||||
}
|
||||
mCompositor = aCompositor;
|
||||
}
|
||||
|
||||
void
|
||||
ImageHostOverlay::Composite(Compositor* aCompositor,
|
||||
uint32_t aFlashCounter,
|
||||
|
@ -612,6 +630,8 @@ ImageHostOverlay::Composite(Compositor* aCompositor,
|
|||
const gfx::Rect& aClipRect,
|
||||
const nsIntRegion* aVisibleRegion)
|
||||
{
|
||||
MOZ_ASSERT(mCompositor == aCompositor);
|
||||
|
||||
if (mOverlay.handle().type() == OverlayHandle::Tnull_t) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -158,6 +158,8 @@ public:
|
|||
|
||||
static bool IsValid(OverlaySource aOverlay);
|
||||
|
||||
void SetCompositor(Compositor* aCompositor);
|
||||
|
||||
virtual void Composite(Compositor* aCompositor,
|
||||
uint32_t aFlashCounter,
|
||||
LayerComposite* aLayer,
|
||||
|
@ -173,6 +175,7 @@ public:
|
|||
virtual gfx::IntSize GetImageSize() const;
|
||||
virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix);
|
||||
protected:
|
||||
RefPtr<Compositor> mCompositor;
|
||||
gfx::IntRect mPictureRect;
|
||||
OverlaySource mOverlay;
|
||||
};
|
||||
|
|
|
@ -809,7 +809,9 @@ LayerManagerComposite::Render(const nsIntRegion& aInvalidRegion)
|
|||
if (!mTarget && !haveLayerEffects &&
|
||||
gfxPrefs::Composer2DCompositionEnabled() &&
|
||||
composer2D && composer2D->HasHwc() && composer2D->TryRenderWithHwc(mRoot,
|
||||
mCompositor->GetWidget(), mGeometryChanged))
|
||||
mCompositor->GetWidget(),
|
||||
mGeometryChanged,
|
||||
mCompositor->HasImageHostOverlays()))
|
||||
{
|
||||
LayerScope::SetHWComposed();
|
||||
if (mFPS) {
|
||||
|
|
|
@ -49,8 +49,10 @@ public:
|
|||
* Currently, when TryRender() returns true, the entire framebuffer
|
||||
* must have been rendered.
|
||||
*/
|
||||
virtual bool TryRenderWithHwc(Layer* aRoot, nsIWidget* aWidget,
|
||||
bool aGeometryChanged) = 0;
|
||||
virtual bool TryRenderWithHwc(Layer* aRoot,
|
||||
nsIWidget* aWidget,
|
||||
bool aGeometryChanged,
|
||||
bool aHasImageHostOverlays) = 0;
|
||||
|
||||
/**
|
||||
* Return true if Composer2D does composition. Return false if Composer2D
|
||||
|
|
|
@ -71,6 +71,18 @@ BindMaskForProgram(ShaderProgramOGL* aProgram, TextureSourceOGL* aSourceMask,
|
|||
aProgram->SetMaskLayerTransform(aTransform);
|
||||
}
|
||||
|
||||
void
|
||||
CompositorOGL::BindBackdrop(ShaderProgramOGL* aProgram, GLuint aBackdrop, GLenum aTexUnit)
|
||||
{
|
||||
MOZ_ASSERT(aBackdrop);
|
||||
|
||||
mGLContext->fActiveTexture(aTexUnit);
|
||||
mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, aBackdrop);
|
||||
mGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
|
||||
mGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
|
||||
aProgram->SetBackdropTextureUnit(aTexUnit - LOCAL_GL_TEXTURE0);
|
||||
}
|
||||
|
||||
CompositorOGL::CompositorOGL(nsIWidget *aWidget, int aSurfaceWidth,
|
||||
int aSurfaceHeight, bool aUseExternalSurfaceSize)
|
||||
: mWidget(aWidget)
|
||||
|
@ -474,7 +486,7 @@ CompositorOGL::PrepareViewport(CompositingRenderTargetOGL* aRenderTarget)
|
|||
// Set the viewport correctly.
|
||||
mGLContext->fViewport(0, 0, size.width, size.height);
|
||||
|
||||
mRenderBound = Rect(0, 0, size.width, size.height);
|
||||
mRenderBounds = Rect(0, 0, size.width, size.height);
|
||||
|
||||
mViewportSize = size;
|
||||
|
||||
|
@ -707,9 +719,16 @@ CompositorOGL::BeginFrame(const nsIntRegion& aInvalidRegion,
|
|||
}
|
||||
|
||||
void
|
||||
CompositorOGL::CreateFBOWithTexture(const IntRect& aRect, bool aCopyFromSource,
|
||||
CompositorOGL::CreateFBOWithTexture(const gfx::IntRect& aRect, bool aCopyFromSource,
|
||||
GLuint aSourceFrameBuffer,
|
||||
GLuint *aFBO, GLuint *aTexture)
|
||||
{
|
||||
*aTexture = CreateTexture(aRect, aCopyFromSource, aSourceFrameBuffer);
|
||||
mGLContext->fGenFramebuffers(1, aFBO);
|
||||
}
|
||||
|
||||
GLuint
|
||||
CompositorOGL::CreateTexture(const IntRect& aRect, bool aCopyFromSource, GLuint aSourceFrameBuffer)
|
||||
{
|
||||
// we're about to create a framebuffer backed by textures to use as an intermediate
|
||||
// surface. What to do if its size (as given by aRect) would exceed the
|
||||
|
@ -722,7 +741,7 @@ CompositorOGL::CreateFBOWithTexture(const IntRect& aRect, bool aCopyFromSource,
|
|||
clampedRect.width = std::min(clampedRect.width, maxTexSize);
|
||||
clampedRect.height = std::min(clampedRect.height, maxTexSize);
|
||||
|
||||
GLuint tex, fbo;
|
||||
GLuint tex;
|
||||
|
||||
mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0);
|
||||
mGLContext->fGenTextures(1, &tex);
|
||||
|
@ -774,6 +793,7 @@ CompositorOGL::CreateFBOWithTexture(const IntRect& aRect, bool aCopyFromSource,
|
|||
LOCAL_GL_UNSIGNED_BYTE,
|
||||
buf.get());
|
||||
}
|
||||
|
||||
GLenum error = mGLContext->fGetError();
|
||||
if (error != LOCAL_GL_NO_ERROR) {
|
||||
nsAutoCString msg;
|
||||
|
@ -801,10 +821,7 @@ CompositorOGL::CreateFBOWithTexture(const IntRect& aRect, bool aCopyFromSource,
|
|||
LOCAL_GL_CLAMP_TO_EDGE);
|
||||
mGLContext->fBindTexture(mFBOTextureTarget, 0);
|
||||
|
||||
mGLContext->fGenFramebuffers(1, &fbo);
|
||||
|
||||
*aFBO = fbo;
|
||||
*aTexture = tex;
|
||||
return tex;
|
||||
}
|
||||
|
||||
ShaderConfigOGL
|
||||
|
@ -855,6 +872,9 @@ CompositorOGL::GetShaderConfigFor(Effect *aEffect,
|
|||
source->GetFormat() == gfx::SurfaceFormat::R5G6B5_UINT16);
|
||||
config = ShaderConfigFromTargetAndFormat(source->GetTextureTarget(),
|
||||
source->GetFormat());
|
||||
if (!texturedEffect->mPremultiplied) {
|
||||
config.SetNoPremultipliedAlpha();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -862,6 +882,7 @@ CompositorOGL::GetShaderConfigFor(Effect *aEffect,
|
|||
config.SetMask2D(aMask == MaskType::Mask2d);
|
||||
config.SetMask3D(aMask == MaskType::Mask3d);
|
||||
config.SetDEAA(aDEAAEnabled);
|
||||
config.SetCompositionOp(aOp);
|
||||
return config;
|
||||
}
|
||||
|
||||
|
@ -898,10 +919,15 @@ CompositorOGL::ResetProgram()
|
|||
mCurrentProgram = nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static bool SetBlendMode(GLContext* aGL, gfx::CompositionOp aBlendMode, bool aIsPremultiplied = true)
|
||||
{
|
||||
if (BlendOpIsMixBlendMode(aBlendMode)) {
|
||||
// Mix-blend modes require an extra step (or more) that cannot be expressed
|
||||
// in the fixed-function blending capabilities of opengl. We handle them
|
||||
// separately in shaders, and the shaders assume we will use our default
|
||||
// blend function for compositing (premultiplied OP_OVER).
|
||||
return false;
|
||||
}
|
||||
if (aBlendMode == gfx::CompositionOp::OP_OVER && aIsPremultiplied) {
|
||||
return false;
|
||||
}
|
||||
|
@ -981,7 +1007,7 @@ CompositorOGL::DrawQuad(const Rect& aRect,
|
|||
}
|
||||
|
||||
IntPoint offset = mCurrentRenderTarget->GetOrigin();
|
||||
Rect renderBound = mRenderBound;
|
||||
Rect renderBound = mRenderBounds;
|
||||
renderBound.IntersectRect(renderBound, aClipRect);
|
||||
renderBound.MoveBy(offset);
|
||||
|
||||
|
@ -995,7 +1021,7 @@ CompositorOGL::DrawQuad(const Rect& aRect,
|
|||
// Inflate a small size to avoid some numerical imprecision issue.
|
||||
destRect.Inflate(1, 1);
|
||||
destRect.MoveBy(-offset);
|
||||
if (!mRenderBound.Intersects(destRect)) {
|
||||
if (!mRenderBounds.Intersects(destRect)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1066,7 +1092,9 @@ CompositorOGL::DrawQuad(const Rect& aRect,
|
|||
aOpacity = 1.f;
|
||||
}
|
||||
|
||||
GLuint mixBlendBackdrop = 0;
|
||||
gfx::CompositionOp blendMode = gfx::CompositionOp::OP_OVER;
|
||||
|
||||
if (aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE]) {
|
||||
EffectBlendMode *blendEffect =
|
||||
static_cast<EffectBlendMode*>(aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE].get());
|
||||
|
@ -1094,6 +1122,20 @@ CompositorOGL::DrawQuad(const Rect& aRect,
|
|||
program->SetColorMatrix(effectColorMatrix->mColorMatrix);
|
||||
}
|
||||
|
||||
if (BlendOpIsMixBlendMode(blendMode)) {
|
||||
gfx::IntRect rect = ComputeBackdropCopyRect(aRect, aClipRect, aTransform);
|
||||
mixBlendBackdrop = CreateTexture(rect, true, mCurrentRenderTarget->GetFBO());
|
||||
|
||||
// Create a transform from adjusted clip space to render target space,
|
||||
// translate it for the backdrop rect, then transform it into the backdrop's
|
||||
// uv-space.
|
||||
gfx::Matrix4x4 transform;
|
||||
transform.PostScale(mRenderBounds.width, mRenderBounds.height, 1.0);
|
||||
transform.PostTranslate(-rect.x, -rect.y, 0.0);
|
||||
transform.PostScale(1 / float(rect.width), 1 / float(rect.height), 1.0);
|
||||
program->SetBackdropTransform(transform);
|
||||
}
|
||||
|
||||
program->SetRenderOffset(offset.x, offset.y);
|
||||
LayerScope::SetRenderOffset(offset.x, offset.y);
|
||||
|
||||
|
@ -1184,6 +1226,9 @@ CompositorOGL::DrawQuad(const Rect& aRect,
|
|||
if (maskType != MaskType::MaskNone) {
|
||||
BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE0, maskQuadTransform);
|
||||
}
|
||||
if (mixBlendBackdrop) {
|
||||
BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE1);
|
||||
}
|
||||
|
||||
didSetBlendMode = SetBlendMode(gl(), blendMode);
|
||||
|
||||
|
@ -1221,6 +1266,9 @@ CompositorOGL::DrawQuad(const Rect& aRect,
|
|||
if (maskType != MaskType::MaskNone) {
|
||||
BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE1, maskQuadTransform);
|
||||
}
|
||||
if (mixBlendBackdrop) {
|
||||
BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE2);
|
||||
}
|
||||
|
||||
BindAndDrawQuadWithTextureRect(program, aRect, texturedEffect->mTextureCoords, source);
|
||||
}
|
||||
|
@ -1249,6 +1297,9 @@ CompositorOGL::DrawQuad(const Rect& aRect,
|
|||
if (maskType != MaskType::MaskNone) {
|
||||
BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE3, maskQuadTransform);
|
||||
}
|
||||
if (mixBlendBackdrop) {
|
||||
BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE4);
|
||||
}
|
||||
didSetBlendMode = SetBlendMode(gl(), blendMode);
|
||||
BindAndDrawQuadWithTextureRect(program,
|
||||
aRect,
|
||||
|
@ -1283,6 +1334,9 @@ CompositorOGL::DrawQuad(const Rect& aRect,
|
|||
if (maskType != MaskType::MaskNone) {
|
||||
BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE2, maskQuadTransform);
|
||||
}
|
||||
if (mixBlendBackdrop) {
|
||||
BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE3);
|
||||
}
|
||||
didSetBlendMode = SetBlendMode(gl(), blendMode);
|
||||
BindAndDrawQuadWithTextureRect(program,
|
||||
aRect,
|
||||
|
@ -1309,6 +1363,9 @@ CompositorOGL::DrawQuad(const Rect& aRect,
|
|||
if (maskType != MaskType::MaskNone) {
|
||||
BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE1, maskQuadTransform);
|
||||
}
|
||||
if (mixBlendBackdrop) {
|
||||
BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE2);
|
||||
}
|
||||
|
||||
if (config.mFeatures & ENABLE_TEXTURE_RECT) {
|
||||
// 2DRect case, get the multiplier right for a sampler2DRect
|
||||
|
@ -1392,6 +1449,9 @@ CompositorOGL::DrawQuad(const Rect& aRect,
|
|||
gl()->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA,
|
||||
LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
if (mixBlendBackdrop) {
|
||||
gl()->fDeleteTextures(1, &mixBlendBackdrop);
|
||||
}
|
||||
|
||||
// in case rendering has used some other GL context
|
||||
MakeCurrent();
|
||||
|
@ -1451,22 +1511,12 @@ CompositorOGL::EndFrame()
|
|||
mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
|
||||
|
||||
// Unbind all textures
|
||||
mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0);
|
||||
mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, 0);
|
||||
if (!mGLContext->IsGLES()) {
|
||||
mGLContext->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, 0);
|
||||
}
|
||||
|
||||
mGLContext->fActiveTexture(LOCAL_GL_TEXTURE1);
|
||||
mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, 0);
|
||||
if (!mGLContext->IsGLES()) {
|
||||
mGLContext->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, 0);
|
||||
}
|
||||
|
||||
mGLContext->fActiveTexture(LOCAL_GL_TEXTURE2);
|
||||
mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, 0);
|
||||
if (!mGLContext->IsGLES()) {
|
||||
mGLContext->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, 0);
|
||||
for (GLuint i = 0; i <= 4; i++) {
|
||||
mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
|
||||
mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, 0);
|
||||
if (!mGLContext->IsGLES()) {
|
||||
mGLContext->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,10 @@
|
|||
#include "nscore.h" // for NS_IMETHOD
|
||||
#include "gfxVR.h"
|
||||
|
||||
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
|
||||
#include "nsTHashtable.h" // for nsTHashtable
|
||||
#endif
|
||||
|
||||
class nsIWidget;
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -48,6 +52,11 @@ class TextureSource;
|
|||
struct Effect;
|
||||
struct EffectChain;
|
||||
class GLBlitTextureImageHelper;
|
||||
|
||||
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
|
||||
class ImageHostOverlay;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Interface for pools of temporary gl textures for the compositor.
|
||||
* The textures are fully owned by the pool, so the latter is responsible
|
||||
|
@ -207,6 +216,11 @@ public:
|
|||
mFBOTextureTarget == LOCAL_GL_TEXTURE_2D,
|
||||
SupportsPartialTextureUpdate());
|
||||
result.mSupportedBlendModes += gfx::CompositionOp::OP_SOURCE;
|
||||
for (uint8_t op = 0; op < uint8_t(gfx::CompositionOp::OP_COUNT); op++) {
|
||||
if (BlendOpIsMixBlendMode(gfx::CompositionOp(op))) {
|
||||
result.mSupportedBlendModes += gfx::CompositionOp(op);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -268,6 +282,29 @@ public:
|
|||
|
||||
virtual nsIWidget* GetWidget() const override { return mWidget; }
|
||||
|
||||
virtual bool HasImageHostOverlays() override
|
||||
{
|
||||
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
|
||||
return mImageHostOverlays.Count() > 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void AddImageHostOverlay(ImageHostOverlay* aOverlay) override
|
||||
{
|
||||
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
|
||||
mImageHostOverlays.PutEntry(aOverlay);
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void RemoveImageHostOverlay(ImageHostOverlay* aOverlay) override
|
||||
{
|
||||
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
|
||||
mImageHostOverlays.RemoveEntry(aOverlay);
|
||||
#endif
|
||||
}
|
||||
|
||||
GLContext* gl() const { return mGLContext; }
|
||||
/**
|
||||
* Clear the program state. This must be called
|
||||
|
@ -390,6 +427,7 @@ private:
|
|||
void CreateFBOWithTexture(const gfx::IntRect& aRect, bool aCopyFromSource,
|
||||
GLuint aSourceFrameBuffer,
|
||||
GLuint *aFBO, GLuint *aTexture);
|
||||
GLuint CreateTexture(const gfx::IntRect& aRect, bool aCopyFromSource, GLuint aSourceFrameBuffer);
|
||||
|
||||
void BindAndDrawQuads(ShaderProgramOGL *aProg,
|
||||
int aQuads,
|
||||
|
@ -413,6 +451,12 @@ private:
|
|||
void ActivateProgram(ShaderProgramOGL *aProg);
|
||||
void CleanupResources();
|
||||
|
||||
/**
|
||||
* Bind the texture behind the current render target as the backdrop for a
|
||||
* mix-blend shader.
|
||||
*/
|
||||
void BindBackdrop(ShaderProgramOGL* aProgram, GLuint aBackdrop, GLenum aTexUnit);
|
||||
|
||||
/**
|
||||
* Copies the content of our backbuffer to the set transaction target.
|
||||
* Does not restore the target FBO, so only call from EndFrame.
|
||||
|
@ -444,9 +488,12 @@ private:
|
|||
|
||||
ShaderProgramOGL *mCurrentProgram;
|
||||
|
||||
gfx::Rect mRenderBound;
|
||||
|
||||
CompositorOGLVRObjects mVR;
|
||||
|
||||
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
|
||||
nsTHashtable<nsPtrHashKey<ImageHostOverlay> > mImageHostOverlays;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
} // namespace layers
|
||||
|
|
|
@ -30,6 +30,7 @@ AddUniforms(ProgramProfileOGL& aProfile)
|
|||
"uLayerTransform",
|
||||
"uLayerTransformInverse",
|
||||
"uMaskTransform",
|
||||
"uBackdropTransform",
|
||||
"uLayerRects",
|
||||
"uMatrixProj",
|
||||
"uTextureTransform",
|
||||
|
@ -43,6 +44,7 @@ AddUniforms(ProgramProfileOGL& aProfile)
|
|||
"uBlackTexture",
|
||||
"uWhiteTexture",
|
||||
"uMaskTexture",
|
||||
"uBackdropTexture",
|
||||
"uRenderColor",
|
||||
"uTexCoordMultiplier",
|
||||
"uCbCrTexCoordMultiplier",
|
||||
|
@ -148,9 +150,9 @@ ShaderConfigOGL::SetMask3D(bool aEnabled)
|
|||
}
|
||||
|
||||
void
|
||||
ShaderConfigOGL::SetPremultiply(bool aEnabled)
|
||||
ShaderConfigOGL::SetNoPremultipliedAlpha()
|
||||
{
|
||||
SetFeature(ENABLE_PREMULTIPLY, aEnabled);
|
||||
SetFeature(ENABLE_NO_PREMUL_ALPHA, true);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -159,6 +161,12 @@ ShaderConfigOGL::SetDEAA(bool aEnabled)
|
|||
SetFeature(ENABLE_DEAA, aEnabled);
|
||||
}
|
||||
|
||||
void
|
||||
ShaderConfigOGL::SetCompositionOp(CompositionOp aOp)
|
||||
{
|
||||
mCompositionOp = aOp;
|
||||
}
|
||||
|
||||
/* static */ ProgramProfileOGL
|
||||
ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig)
|
||||
{
|
||||
|
@ -167,6 +175,8 @@ ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig)
|
|||
|
||||
AddUniforms(result);
|
||||
|
||||
CompositionOp blendOp = aConfig.mCompositionOp;
|
||||
|
||||
vs << "#ifdef GL_ES" << endl;
|
||||
vs << "#define EDGE_PRECISION mediump" << endl;
|
||||
vs << "#else" << endl;
|
||||
|
@ -190,6 +200,11 @@ ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig)
|
|||
vs << "varying vec2 vTexCoord;" << endl;
|
||||
}
|
||||
|
||||
if (BlendOpIsMixBlendMode(blendOp)) {
|
||||
vs << "uniform mat4 uBackdropTransform;" << endl;
|
||||
vs << "varying vec2 vBackdropCoord;" << endl;
|
||||
}
|
||||
|
||||
if (aConfig.mFeatures & ENABLE_MASK_2D ||
|
||||
aConfig.mFeatures & ENABLE_MASK_3D) {
|
||||
vs << "uniform mat4 uMaskTransform;" << endl;
|
||||
|
@ -273,6 +288,14 @@ ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig)
|
|||
}
|
||||
vs << " finalPosition.xy -= uRenderTargetOffset * finalPosition.w;" << endl;
|
||||
vs << " finalPosition = uMatrixProj * finalPosition;" << endl;
|
||||
if (BlendOpIsMixBlendMode(blendOp)) {
|
||||
// Translate from clip space (-1, 1) to (0..1), apply the backdrop
|
||||
// transform, then invert the y-axis.
|
||||
vs << " vBackdropCoord.x = (finalPosition.x + 1.0) / 2.0;" << endl;
|
||||
vs << " vBackdropCoord.y = 1.0 - (finalPosition.y + 1.0) / 2.0;" << endl;
|
||||
vs << " vBackdropCoord = (uBackdropTransform * vec4(vBackdropCoord.xy, 0.0, 1.0)).xy;" << endl;
|
||||
vs << " vBackdropCoord.y = 1.0 - vBackdropCoord.y;" << endl;
|
||||
}
|
||||
vs << " gl_Position = finalPosition;" << endl;
|
||||
vs << "}" << endl;
|
||||
|
||||
|
@ -309,6 +332,9 @@ ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig)
|
|||
fs << "uniform COLOR_PRECISION float uLayerOpacity;" << endl;
|
||||
}
|
||||
}
|
||||
if (BlendOpIsMixBlendMode(blendOp)) {
|
||||
fs << "varying vec2 vBackdropCoord;" << endl;
|
||||
}
|
||||
|
||||
const char *sampler2D = "sampler2D";
|
||||
const char *texture2D = "texture2D";
|
||||
|
@ -342,6 +368,13 @@ ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig)
|
|||
fs << "uniform " << sampler2D << " uTexture;" << endl;
|
||||
}
|
||||
|
||||
if (BlendOpIsMixBlendMode(blendOp)) {
|
||||
// Component alpha should be flattened away inside blend containers.
|
||||
MOZ_ASSERT(!(aConfig.mFeatures & ENABLE_TEXTURE_COMPONENT_ALPHA));
|
||||
|
||||
fs << "uniform sampler2D uBackdropTexture;" << endl;
|
||||
}
|
||||
|
||||
if (aConfig.mFeatures & ENABLE_MASK_2D ||
|
||||
aConfig.mFeatures & ENABLE_MASK_3D) {
|
||||
fs << "varying vec3 vMaskCoord;" << endl;
|
||||
|
@ -352,6 +385,10 @@ ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig)
|
|||
fs << "uniform EDGE_PRECISION vec3 uSSEdges[4];" << endl;
|
||||
}
|
||||
|
||||
if (BlendOpIsMixBlendMode(blendOp)) {
|
||||
BuildMixBlender(aConfig, fs);
|
||||
}
|
||||
|
||||
if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) {
|
||||
fs << "vec4 sample(vec2 coord) {" << endl;
|
||||
fs << " vec4 color;" << endl;
|
||||
|
@ -459,9 +496,6 @@ For [0,1] instead of [0,255], and to 5 places:
|
|||
if (aConfig.mFeatures & ENABLE_OPACITY) {
|
||||
fs << " color *= uLayerOpacity;" << endl;
|
||||
}
|
||||
if (aConfig.mFeatures & ENABLE_PREMULTIPLY) {
|
||||
fs << " color.rgb *= color.a;" << endl;
|
||||
}
|
||||
}
|
||||
if (aConfig.mFeatures & ENABLE_DEAA) {
|
||||
// Calculate the sub-pixel coverage of the pixel and modulate its opacity
|
||||
|
@ -473,6 +507,10 @@ For [0,1] instead of [0,255], and to 5 places:
|
|||
fs << " deaaCoverage *= clamp(dot(uSSEdges[3], ssPos), 0.0, 1.0);" << endl;
|
||||
fs << " color *= deaaCoverage;" << endl;
|
||||
}
|
||||
if (BlendOpIsMixBlendMode(blendOp)) {
|
||||
fs << " vec4 backdrop = texture2D(uBackdropTexture, vBackdropCoord);" << endl;
|
||||
fs << " color = mixAndBlend(backdrop, color);" << endl;
|
||||
}
|
||||
if (aConfig.mFeatures & ENABLE_MASK_3D) {
|
||||
fs << " vec2 maskCoords = vMaskCoord.xy / vMaskCoord.z;" << endl;
|
||||
fs << " COLOR_PRECISION float mask = texture2D(uMaskTexture, maskCoords).r;" << endl;
|
||||
|
@ -507,10 +545,226 @@ For [0,1] instead of [0,255], and to 5 places:
|
|||
aConfig.mFeatures & ENABLE_MASK_3D) {
|
||||
result.mTextureCount = 1;
|
||||
}
|
||||
if (BlendOpIsMixBlendMode(blendOp)) {
|
||||
result.mTextureCount += 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
ProgramProfileOGL::BuildMixBlender(const ShaderConfigOGL& aConfig, std::ostringstream& fs)
|
||||
{
|
||||
// From the "Compositing and Blending Level 1" spec.
|
||||
// Generate helper functions first.
|
||||
switch (aConfig.mCompositionOp) {
|
||||
case gfx::CompositionOp::OP_OVERLAY:
|
||||
case gfx::CompositionOp::OP_HARD_LIGHT:
|
||||
// Note: we substitute (2*src-1) into the screen formula below.
|
||||
fs << "float hardlight(float dest, float src) {" << endl;
|
||||
fs << " if (src <= 0.5) {" << endl;
|
||||
fs << " return dest * (2.0 * src);" << endl;
|
||||
fs << " } else {" << endl;
|
||||
fs << " return 2.0*dest + 2.0*src - 1.0 - 2.0*dest*src;" << endl;
|
||||
fs << " }" << endl;
|
||||
fs << "}" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_COLOR_DODGE:
|
||||
fs << "float dodge(float dest, float src) {" << endl;
|
||||
fs << " if (dest == 0.0) {" << endl;
|
||||
fs << " return 0.0;" << endl;
|
||||
fs << " } else if (src == 1.0) {" << endl;
|
||||
fs << " return 1.0;" << endl;
|
||||
fs << " } else {" << endl;
|
||||
fs << " return min(1.0, dest / (1.0 - src));" << endl;
|
||||
fs << " }" << endl;
|
||||
fs << "}" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_COLOR_BURN:
|
||||
fs << "float burn(float dest, float src) {" << endl;
|
||||
fs << " if (dest == 1.0) {" << endl;
|
||||
fs << " return 1.0;" << endl;
|
||||
fs << " } else if (src == 0.0) {" << endl;
|
||||
fs << " return 0.0;" << endl;
|
||||
fs << " } else {" << endl;
|
||||
fs << " return 1.0 - min(1.0, (1.0 - dest) / src);" << endl;
|
||||
fs << " }" << endl;
|
||||
fs << "}" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_SOFT_LIGHT:
|
||||
fs << "float darken(float dest) {" << endl;
|
||||
fs << " if (dest <= 0.25) {" << endl;
|
||||
fs << " return ((16.0 * dest - 12.0) * dest + 4.0) * dest;" << endl;
|
||||
fs << " } else {" << endl;
|
||||
fs << " return sqrt(dest);" << endl;
|
||||
fs << " }" << endl;
|
||||
fs << "}" << endl;
|
||||
fs << "float softlight(float dest, float src) {" << endl;
|
||||
fs << " if (src <= 0.5) {" << endl;
|
||||
fs << " return dest - (1.0 - 2.0 * src) * dest * (1.0 - dest);" << endl;
|
||||
fs << " } else {" << endl;
|
||||
fs << " return dest + (2.0 * src - 1.0) * (darken(dest) - dest);" << endl;
|
||||
fs << " }" << endl;
|
||||
fs << "}" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_HUE:
|
||||
case gfx::CompositionOp::OP_SATURATION:
|
||||
case gfx::CompositionOp::OP_COLOR:
|
||||
case gfx::CompositionOp::OP_LUMINOSITY:
|
||||
fs << "float Lum(vec3 c) {" << endl;
|
||||
fs << " return dot(vec3(0.3, 0.59, 0.11), c);" << endl;
|
||||
fs << "}" << endl;
|
||||
fs << "vec3 ClipColor(vec3 c) {" << endl;
|
||||
fs << " float L = Lum(c);" << endl;
|
||||
fs << " float n = min(min(c.r, c.g), c.b);" << endl;
|
||||
fs << " float x = max(max(c.r, c.g), c.b);" << endl;
|
||||
fs << " if (n < 0.0) {" << endl;
|
||||
fs << " c = L + (((c - L) * L) / (L - n));" << endl;
|
||||
fs << " }" << endl;
|
||||
fs << " if (x > 1.0) {" << endl;
|
||||
fs << " c = L + (((c - L) * (1.0 - L)) / (x - L));" << endl;
|
||||
fs << " }" << endl;
|
||||
fs << " return c;" << endl;
|
||||
fs << "}" << endl;
|
||||
fs << "vec3 SetLum(vec3 c, float L) {" << endl;
|
||||
fs << " float d = L - Lum(c);" << endl;
|
||||
fs << " return ClipColor(vec3(" << endl;
|
||||
fs << " c.r + d," << endl;
|
||||
fs << " c.g + d," << endl;
|
||||
fs << " c.b + d));" << endl;
|
||||
fs << "}" << endl;
|
||||
fs << "float Sat(vec3 c) {" << endl;
|
||||
fs << " return max(max(c.r, c.g), c.b) - min(min(c.r, c.g), c.b);" << endl;
|
||||
fs << "}" << endl;
|
||||
|
||||
// To use this helper, re-arrange rgb such that r=min, g=mid, and b=max.
|
||||
fs << "vec3 SetSatInner(vec3 c, float s) {" << endl;
|
||||
fs << " if (c.b > c.r) {" << endl;
|
||||
fs << " c.g = (((c.g - c.r) * s) / (c.b - c.r));" << endl;
|
||||
fs << " c.b = s;" << endl;
|
||||
fs << " } else {" << endl;
|
||||
fs << " c.gb = vec2(0.0, 0.0);" << endl;
|
||||
fs << " }" << endl;
|
||||
fs << " return vec3(0.0, c.gb);" << endl;
|
||||
fs << "}" << endl;
|
||||
|
||||
fs << "vec3 SetSat(vec3 c, float s) {" << endl;
|
||||
fs << " if (c.r <= c.g) {" << endl;
|
||||
fs << " if (c.g <= c.b) {" << endl;
|
||||
fs << " c.rgb = SetSatInner(c.rgb, s);" << endl;
|
||||
fs << " } else if (c.r <= c.b) {" << endl;
|
||||
fs << " c.rbg = SetSatInner(c.rbg, s);" << endl;
|
||||
fs << " } else {" << endl;
|
||||
fs << " c.brg = SetSatInner(c.brg, s);" << endl;
|
||||
fs << " }" << endl;
|
||||
fs << " } else if (c.r <= c.b) {" << endl;
|
||||
fs << " c.grb = SetSatInner(c.grb, s);" << endl;
|
||||
fs << " } else if (c.g <= c.b) {" << endl;
|
||||
fs << " c.gbr = SetSatInner(c.gbr, s);" << endl;
|
||||
fs << " } else {" << endl;
|
||||
fs << " c.bgr = SetSatInner(c.bgr, s);" << endl;
|
||||
fs << " }" << endl;
|
||||
fs << " return c;" << endl;
|
||||
fs << "}" << endl;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Generate the main blending helper.
|
||||
fs << "vec3 blend(vec3 dest, vec3 src) {" << endl;
|
||||
switch (aConfig.mCompositionOp) {
|
||||
case gfx::CompositionOp::OP_MULTIPLY:
|
||||
fs << " return dest * src;" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_SCREEN:
|
||||
fs << " return dest + src - (dest * src);" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_OVERLAY:
|
||||
fs << " return vec3(" << endl;
|
||||
fs << " hardlight(src.r, dest.r)," << endl;
|
||||
fs << " hardlight(src.g, dest.g)," << endl;
|
||||
fs << " hardlight(src.b, dest.b));" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_DARKEN:
|
||||
fs << " return min(dest, src);" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_LIGHTEN:
|
||||
fs << " return max(dest, src);" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_COLOR_DODGE:
|
||||
fs << " return vec3(" << endl;
|
||||
fs << " dodge(dest.r, src.r)," << endl;
|
||||
fs << " dodge(dest.g, src.g)," << endl;
|
||||
fs << " dodge(dest.b, src.b));" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_COLOR_BURN:
|
||||
fs << " return vec3(" << endl;
|
||||
fs << " burn(dest.r, src.r)," << endl;
|
||||
fs << " burn(dest.g, src.g)," << endl;
|
||||
fs << " burn(dest.b, src.b));" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_HARD_LIGHT:
|
||||
fs << " return vec3(" << endl;
|
||||
fs << " hardlight(dest.r, src.r)," << endl;
|
||||
fs << " hardlight(dest.g, src.g)," << endl;
|
||||
fs << " hardlight(dest.b, src.b));" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_SOFT_LIGHT:
|
||||
fs << " return vec3(" << endl;
|
||||
fs << " softlight(dest.r, src.r)," << endl;
|
||||
fs << " softlight(dest.g, src.g)," << endl;
|
||||
fs << " softlight(dest.b, src.b));" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_DIFFERENCE:
|
||||
fs << " return abs(dest - src);" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_EXCLUSION:
|
||||
fs << " return dest + src - 2.0*dest*src;" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_HUE:
|
||||
fs << " return SetLum(SetSat(src, Sat(dest)), Lum(dest));" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_SATURATION:
|
||||
fs << " return SetLum(SetSat(dest, Sat(src)), Lum(dest));" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_COLOR:
|
||||
fs << " return SetLum(src, Lum(dest));" << endl;
|
||||
break;
|
||||
case gfx::CompositionOp::OP_LUMINOSITY:
|
||||
fs << " return SetLum(dest, Lum(src));" << endl;
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("unknown blend mode");
|
||||
}
|
||||
fs << "}" << endl;
|
||||
|
||||
// Generate the mix-blend function the fragment shader will call.
|
||||
fs << "vec4 mixAndBlend(vec4 backdrop, vec4 color) {" << endl;
|
||||
|
||||
// Shortcut when the backdrop or source alpha is 0, otherwise we may leak
|
||||
// Infinity into the blend function and return incorrect results.
|
||||
fs << " if (backdrop.a == 0.0) {" << endl;
|
||||
fs << " return color;" << endl;
|
||||
fs << " }" << endl;
|
||||
fs << " if (color.a == 0.0) {" << endl;
|
||||
fs << " return backdrop;" << endl;
|
||||
fs << " }" << endl;
|
||||
|
||||
// The spec assumes there is no premultiplied alpha. The backdrop is always
|
||||
// premultiplied, so undo the premultiply. If the source is premultiplied we
|
||||
// must fix that as well.
|
||||
fs << " backdrop.rgb /= backdrop.a;" << endl;
|
||||
if (!(aConfig.mFeatures & ENABLE_NO_PREMUL_ALPHA)) {
|
||||
fs << " color.rgb /= color.a;" << endl;
|
||||
}
|
||||
fs << " vec3 blended = blend(backdrop.rgb, color.rgb);" << endl;
|
||||
fs << " color.rgb = (1.0 - backdrop.a) * color.rgb + backdrop.a * blended.rgb;" << endl;
|
||||
fs << " color.rgb *= color.a;" << endl;
|
||||
fs << " return color;" << endl;
|
||||
fs << "}" << endl;
|
||||
}
|
||||
|
||||
ShaderProgramOGL::ShaderProgramOGL(GLContext* aGL, const ProgramProfileOGL& aProfile)
|
||||
: mGL(aGL)
|
||||
, mProgram(0)
|
||||
|
|
|
@ -39,7 +39,7 @@ enum ShaderFeatures {
|
|||
ENABLE_COLOR_MATRIX=0x400,
|
||||
ENABLE_MASK_2D=0x800,
|
||||
ENABLE_MASK_3D=0x1000,
|
||||
ENABLE_PREMULTIPLY=0x2000,
|
||||
ENABLE_NO_PREMUL_ALPHA=0x2000,
|
||||
ENABLE_DEAA=0x4000
|
||||
};
|
||||
|
||||
|
@ -52,6 +52,7 @@ public:
|
|||
LayerTransform = 0,
|
||||
LayerTransformInverse,
|
||||
MaskTransform,
|
||||
BackdropTransform,
|
||||
LayerRects,
|
||||
MatrixProj,
|
||||
TextureTransform,
|
||||
|
@ -65,6 +66,7 @@ public:
|
|||
BlackTexture,
|
||||
WhiteTexture,
|
||||
MaskTexture,
|
||||
BackdropTexture,
|
||||
RenderColor,
|
||||
TexCoordMultiplier,
|
||||
CbCrTexCoordMultiplier,
|
||||
|
@ -207,7 +209,9 @@ class ShaderConfigOGL
|
|||
{
|
||||
public:
|
||||
ShaderConfigOGL() :
|
||||
mFeatures(0) {}
|
||||
mFeatures(0),
|
||||
mCompositionOp(gfx::CompositionOp::OP_OVER)
|
||||
{}
|
||||
|
||||
void SetRenderColor(bool aEnabled);
|
||||
void SetTextureTarget(GLenum aTarget);
|
||||
|
@ -221,11 +225,14 @@ public:
|
|||
void SetBlur(bool aEnabled);
|
||||
void SetMask2D(bool aEnabled);
|
||||
void SetMask3D(bool aEnabled);
|
||||
void SetPremultiply(bool aEnabled);
|
||||
void SetDEAA(bool aEnabled);
|
||||
void SetCompositionOp(gfx::CompositionOp aOp);
|
||||
void SetNoPremultipliedAlpha();
|
||||
|
||||
bool operator< (const ShaderConfigOGL& other) const {
|
||||
return mFeatures < other.mFeatures;
|
||||
return mFeatures < other.mFeatures ||
|
||||
(mFeatures == other.mFeatures &&
|
||||
(int)mCompositionOp < (int)other.mCompositionOp);
|
||||
}
|
||||
|
||||
public:
|
||||
|
@ -237,6 +244,7 @@ public:
|
|||
}
|
||||
|
||||
int mFeatures;
|
||||
gfx::CompositionOp mCompositionOp;
|
||||
};
|
||||
|
||||
static inline ShaderConfigOGL
|
||||
|
@ -278,6 +286,9 @@ struct ProgramProfileOGL
|
|||
ProgramProfileOGL() :
|
||||
mTextureCount(0)
|
||||
{}
|
||||
|
||||
private:
|
||||
static void BuildMixBlender(const ShaderConfigOGL& aConfig, std::ostringstream& fs);
|
||||
};
|
||||
|
||||
|
||||
|
@ -342,6 +353,10 @@ public:
|
|||
SetMatrixUniform(KnownUniform::MaskTransform, aMatrix);
|
||||
}
|
||||
|
||||
void SetBackdropTransform(const gfx::Matrix4x4& aMatrix) {
|
||||
SetMatrixUniform(KnownUniform::BackdropTransform, aMatrix);
|
||||
}
|
||||
|
||||
void SetDEAAEdges(const gfx::Point3D* aEdges) {
|
||||
SetArrayUniform(KnownUniform::SSEdges, 4, aEdges);
|
||||
}
|
||||
|
@ -433,6 +448,10 @@ public:
|
|||
SetUniform(KnownUniform::MaskTexture, aUnit);
|
||||
}
|
||||
|
||||
void SetBackdropTextureUnit(GLint aUnit) {
|
||||
SetUniform(KnownUniform::BackdropTexture, aUnit);
|
||||
}
|
||||
|
||||
void SetRenderColor(const gfx::Color& aColor) {
|
||||
SetUniform(KnownUniform::RenderColor, aColor);
|
||||
}
|
||||
|
|
|
@ -233,14 +233,16 @@ nsBMPDecoder::FinishInternal()
|
|||
if (!IsMetadataDecode() && HasSize()) {
|
||||
|
||||
// If it was truncated, fill in the missing pixels as black.
|
||||
while (mCurrentRow > 0) {
|
||||
uint32_t* dst = RowBuffer();
|
||||
while (mCurrentPos < mH.mWidth) {
|
||||
SetPixel(dst, 0, 0, 0);
|
||||
mCurrentPos++;
|
||||
if (mImageData) {
|
||||
while (mCurrentRow > 0) {
|
||||
uint32_t* dst = RowBuffer();
|
||||
while (mCurrentPos < mH.mWidth) {
|
||||
SetPixel(dst, 0, 0, 0);
|
||||
mCurrentPos++;
|
||||
}
|
||||
mCurrentPos = 0;
|
||||
FinishRow();
|
||||
}
|
||||
mCurrentPos = 0;
|
||||
FinishRow();
|
||||
}
|
||||
|
||||
// Invalidate.
|
||||
|
@ -554,6 +556,11 @@ nsBMPDecoder::ReadInfoHeaderRest(const char* aData, size_t aLength)
|
|||
(mH.mCompression == Compression::RLE8 && mH.mBpp == 8) ||
|
||||
(mH.mCompression == Compression::RLE4 && mH.mBpp == 4) ||
|
||||
(mH.mCompression == Compression::BITFIELDS &&
|
||||
// For BITFIELDS compression we require an exact match for one of the
|
||||
// WinBMP BIH sizes; this clearly isn't an OS2 BMP.
|
||||
(mH.mBIHSize == InfoHeaderLength::WIN_V3 ||
|
||||
mH.mBIHSize == InfoHeaderLength::WIN_V4 ||
|
||||
mH.mBIHSize == InfoHeaderLength::WIN_V5) &&
|
||||
(mH.mBpp == 16 || mH.mBpp == 32));
|
||||
if (!bppCompressionOk) {
|
||||
PostDataError();
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 78 B |
|
@ -10,3 +10,8 @@
|
|||
# Tests for RLE8 with an invalid BPP
|
||||
== wrapper.html?invalid-compression-RLE8.bmp about:blank
|
||||
|
||||
# Test for BITFIELDS with an invalid BIH size. (This is the obscure
|
||||
# BITMAPV3INFOHEADER variant mentioned in
|
||||
# https://en.wikipedia.org/wiki/BMP_file_format which we don't accept.)
|
||||
== wrapper.html?invalid-compression-BITFIELDS.bmp about:blank
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ namespace base {
|
|||
typedef ::Lock Lock;
|
||||
typedef ::AutoLock AutoLock;
|
||||
|
||||
using mozilla::OffTheBooksMutexAutoLock;
|
||||
|
||||
// Static table of checksums for all possible 8 bit bytes.
|
||||
const uint32_t Histogram::kCrcTable[256] = {0x0, 0x77073096L, 0xee0e612cL,
|
||||
0x990951baL, 0x76dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0xedb8832L,
|
||||
|
@ -172,20 +174,25 @@ void Histogram::WriteAscii(bool graph_it, const std::string& newline,
|
|||
// are consistent across our output activities.
|
||||
SampleSet snapshot;
|
||||
SnapshotSample(&snapshot);
|
||||
Count sample_count = snapshot.TotalCount();
|
||||
|
||||
WriteAsciiHeader(snapshot, sample_count, output);
|
||||
// For the rest of the routine, we hold |snapshot|'s lock so as to
|
||||
// be able to examine it atomically.
|
||||
OffTheBooksMutexAutoLock locker(snapshot.mutex());
|
||||
|
||||
Count sample_count = snapshot.TotalCount(locker);
|
||||
|
||||
WriteAsciiHeader(snapshot, locker, sample_count, output);
|
||||
output->append(newline);
|
||||
|
||||
// Prepare to normalize graphical rendering of bucket contents.
|
||||
double max_size = 0;
|
||||
if (graph_it)
|
||||
max_size = GetPeakBucketSize(snapshot);
|
||||
max_size = GetPeakBucketSize(snapshot, locker);
|
||||
|
||||
// Calculate space needed to print bucket range numbers. Leave room to print
|
||||
// nearly the largest bucket range without sliding over the histogram.
|
||||
size_t largest_non_empty_bucket = bucket_count() - 1;
|
||||
while (0 == snapshot.counts(largest_non_empty_bucket)) {
|
||||
while (0 == snapshot.counts(locker, largest_non_empty_bucket)) {
|
||||
if (0 == largest_non_empty_bucket)
|
||||
break; // All buckets are empty.
|
||||
--largest_non_empty_bucket;
|
||||
|
@ -194,7 +201,7 @@ void Histogram::WriteAscii(bool graph_it, const std::string& newline,
|
|||
// Calculate largest print width needed for any of our bucket range displays.
|
||||
size_t print_width = 1;
|
||||
for (size_t i = 0; i < bucket_count(); ++i) {
|
||||
if (snapshot.counts(i)) {
|
||||
if (snapshot.counts(locker, i)) {
|
||||
size_t width = GetAsciiBucketRange(i).size() + 1;
|
||||
if (width > print_width)
|
||||
print_width = width;
|
||||
|
@ -205,7 +212,7 @@ void Histogram::WriteAscii(bool graph_it, const std::string& newline,
|
|||
int64_t past = 0;
|
||||
// Output the actual histogram graph.
|
||||
for (size_t i = 0; i < bucket_count(); ++i) {
|
||||
Count current = snapshot.counts(i);
|
||||
Count current = snapshot.counts(locker, i);
|
||||
if (!current && !PrintEmptyBucket(i))
|
||||
continue;
|
||||
remaining -= current;
|
||||
|
@ -213,8 +220,9 @@ void Histogram::WriteAscii(bool graph_it, const std::string& newline,
|
|||
output->append(range);
|
||||
for (size_t j = 0; range.size() + j < print_width + 1; ++j)
|
||||
output->push_back(' ');
|
||||
if (0 == current && i < bucket_count() - 1 && 0 == snapshot.counts(i + 1)) {
|
||||
while (i < bucket_count() - 1 && 0 == snapshot.counts(i + 1))
|
||||
if (0 == current &&
|
||||
i < bucket_count() - 1 && 0 == snapshot.counts(locker, i + 1)) {
|
||||
while (i < bucket_count() - 1 && 0 == snapshot.counts(locker, i + 1))
|
||||
++i;
|
||||
output->append("... ");
|
||||
output->append(newline);
|
||||
|
@ -328,12 +336,13 @@ bool Histogram::DeserializeHistogramInfo(const std::string& histogram_info) {
|
|||
//------------------------------------------------------------------------------
|
||||
|
||||
Histogram::Inconsistencies Histogram::FindCorruption(
|
||||
const SampleSet& snapshot) const {
|
||||
const SampleSet& snapshot,
|
||||
const OffTheBooksMutexAutoLock& snapshotLockEvidence) const {
|
||||
int inconsistencies = NO_INCONSISTENCIES;
|
||||
Sample previous_range = -1; // Bottom range is always 0.
|
||||
int64_t count = 0;
|
||||
for (size_t index = 0; index < bucket_count(); ++index) {
|
||||
count += snapshot.counts(index);
|
||||
count += snapshot.counts(snapshotLockEvidence, index);
|
||||
int new_range = ranges(index);
|
||||
if (previous_range >= new_range)
|
||||
inconsistencies |= BUCKET_ORDER_ERROR;
|
||||
|
@ -343,7 +352,7 @@ Histogram::Inconsistencies Histogram::FindCorruption(
|
|||
if (!HasValidRangeChecksum())
|
||||
inconsistencies |= RANGE_CHECKSUM_ERROR;
|
||||
|
||||
int64_t delta64 = snapshot.redundant_count() - count;
|
||||
int64_t delta64 = snapshot.redundant_count(snapshotLockEvidence) - count;
|
||||
if (delta64 != 0) {
|
||||
int delta = static_cast<int>(delta64);
|
||||
if (delta != delta64)
|
||||
|
@ -383,10 +392,8 @@ size_t Histogram::bucket_count() const {
|
|||
return bucket_count_;
|
||||
}
|
||||
|
||||
// Do a safe atomic snapshot of sample data.
|
||||
// This implementation assumes we are on a safe single thread.
|
||||
void Histogram::SnapshotSample(SampleSet* sample) const {
|
||||
// Note locking not done in this version!!!
|
||||
OffTheBooksMutexAutoLock locker(sample_.mutex());
|
||||
*sample = sample_;
|
||||
}
|
||||
|
||||
|
@ -422,6 +429,7 @@ size_t Histogram::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
|
|||
|
||||
size_t Histogram::SampleSet::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
|
||||
{
|
||||
OffTheBooksMutexAutoLock locker(mutex_);
|
||||
// We're not allowed to do deep dives into STL data structures. This
|
||||
// is as close as we can get to measuring this array.
|
||||
return aMallocSizeOf(&counts_[0]);
|
||||
|
@ -559,7 +567,6 @@ const std::string Histogram::GetAsciiBucketRange(size_t i) const {
|
|||
|
||||
// Update histogram data with new sample.
|
||||
void Histogram::Accumulate(Sample value, Count count, size_t index) {
|
||||
// Note locking not done in this version!!!
|
||||
sample_.AccumulateWithLinearStats(value, count, index);
|
||||
}
|
||||
|
||||
|
@ -640,10 +647,13 @@ uint32_t Histogram::Crc32(uint32_t sum, Histogram::Sample range) {
|
|||
//------------------------------------------------------------------------------
|
||||
// Private methods
|
||||
|
||||
double Histogram::GetPeakBucketSize(const SampleSet& snapshot) const {
|
||||
double Histogram::GetPeakBucketSize(const SampleSet& snapshot,
|
||||
const OffTheBooksMutexAutoLock&
|
||||
snapshotLockEvidence) const {
|
||||
double max = 0;
|
||||
for (size_t i = 0; i < bucket_count() ; ++i) {
|
||||
double current_size = GetBucketSize(snapshot.counts(i), i);
|
||||
double current_size
|
||||
= GetBucketSize(snapshot.counts(snapshotLockEvidence, i), i);
|
||||
if (current_size > max)
|
||||
max = current_size;
|
||||
}
|
||||
|
@ -651,16 +661,19 @@ double Histogram::GetPeakBucketSize(const SampleSet& snapshot) const {
|
|||
}
|
||||
|
||||
void Histogram::WriteAsciiHeader(const SampleSet& snapshot,
|
||||
const OffTheBooksMutexAutoLock&
|
||||
snapshotLockEvidence,
|
||||
Count sample_count,
|
||||
std::string* output) const {
|
||||
StringAppendF(output,
|
||||
"Histogram: %s recorded %d samples",
|
||||
histogram_name().c_str(),
|
||||
sample_count);
|
||||
int64_t snapshot_sum = snapshot.sum(snapshotLockEvidence);
|
||||
if (0 == sample_count) {
|
||||
DCHECK_EQ(snapshot.sum(), 0);
|
||||
DCHECK_EQ(snapshot_sum, 0);
|
||||
} else {
|
||||
double average = static_cast<float>(snapshot.sum()) / sample_count;
|
||||
double average = static_cast<float>(snapshot_sum) / sample_count;
|
||||
|
||||
StringAppendF(output, ", average = %.1f", average);
|
||||
}
|
||||
|
@ -710,22 +723,21 @@ Histogram::SampleSet::SampleSet()
|
|||
sum_squares_(0),
|
||||
log_sum_(0),
|
||||
log_sum_squares_(0),
|
||||
redundant_count_(0) {
|
||||
redundant_count_(0),
|
||||
mutex_("Histogram::SampleSet::SampleSet") {
|
||||
}
|
||||
|
||||
Histogram::SampleSet::~SampleSet() {
|
||||
}
|
||||
|
||||
void Histogram::SampleSet::Resize(const Histogram& histogram) {
|
||||
OffTheBooksMutexAutoLock locker(mutex_);
|
||||
counts_.resize(histogram.bucket_count(), 0);
|
||||
}
|
||||
|
||||
void Histogram::SampleSet::CheckSize(const Histogram& histogram) const {
|
||||
DCHECK_EQ(histogram.bucket_count(), counts_.size());
|
||||
}
|
||||
|
||||
void Histogram::SampleSet::Accumulate(Sample value, Count count,
|
||||
size_t index) {
|
||||
void Histogram::SampleSet::Accumulate(const OffTheBooksMutexAutoLock& ev,
|
||||
Sample value, Count count,
|
||||
size_t index) {
|
||||
DCHECK(count == 1 || count == -1);
|
||||
counts_[index] += count;
|
||||
redundant_count_ += count;
|
||||
|
@ -738,11 +750,13 @@ void Histogram::SampleSet::Accumulate(Sample value, Count count,
|
|||
void Histogram::SampleSet::AccumulateWithLinearStats(Sample value,
|
||||
Count count,
|
||||
size_t index) {
|
||||
Accumulate(value, count, index);
|
||||
OffTheBooksMutexAutoLock locker(mutex_);
|
||||
Accumulate(locker, value, count, index);
|
||||
sum_squares_ += static_cast<int64_t>(count) * value * value;
|
||||
}
|
||||
|
||||
Count Histogram::SampleSet::TotalCount() const {
|
||||
Count Histogram::SampleSet::TotalCount(const OffTheBooksMutexAutoLock& ev)
|
||||
const {
|
||||
Count total = 0;
|
||||
for (Counts::const_iterator it = counts_.begin();
|
||||
it != counts_.end();
|
||||
|
@ -753,6 +767,7 @@ Count Histogram::SampleSet::TotalCount() const {
|
|||
}
|
||||
|
||||
void Histogram::SampleSet::Add(const SampleSet& other) {
|
||||
OffTheBooksMutexAutoLock locker(mutex_);
|
||||
DCHECK_EQ(counts_.size(), other.counts_.size());
|
||||
sum_ += other.sum_;
|
||||
sum_squares_ += other.sum_squares_;
|
||||
|
@ -763,23 +778,8 @@ void Histogram::SampleSet::Add(const SampleSet& other) {
|
|||
counts_[index] += other.counts_[index];
|
||||
}
|
||||
|
||||
void Histogram::SampleSet::Subtract(const SampleSet& other) {
|
||||
DCHECK_EQ(counts_.size(), other.counts_.size());
|
||||
// Note: Race conditions in snapshotting a sum may lead to (temporary)
|
||||
// negative values when snapshots are later combined (and deltas calculated).
|
||||
// As a result, we don't currently CHCEK() for positive values.
|
||||
sum_ -= other.sum_;
|
||||
sum_squares_ -= other.sum_squares_;
|
||||
log_sum_ -= other.log_sum_;
|
||||
log_sum_squares_ -= other.log_sum_squares_;
|
||||
redundant_count_ -= other.redundant_count_;
|
||||
for (size_t index = 0; index < counts_.size(); ++index) {
|
||||
counts_[index] -= other.counts_[index];
|
||||
DCHECK_GE(counts_[index], 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool Histogram::SampleSet::Serialize(Pickle* pickle) const {
|
||||
OffTheBooksMutexAutoLock locker(mutex_);
|
||||
pickle->WriteInt64(sum_);
|
||||
pickle->WriteInt64(redundant_count_);
|
||||
pickle->WriteSize(counts_.size());
|
||||
|
@ -792,6 +792,7 @@ bool Histogram::SampleSet::Serialize(Pickle* pickle) const {
|
|||
}
|
||||
|
||||
bool Histogram::SampleSet::Deserialize(void** iter, const Pickle& pickle) {
|
||||
OffTheBooksMutexAutoLock locker(mutex_);
|
||||
DCHECK_EQ(counts_.size(), 0u);
|
||||
DCHECK_EQ(sum_, 0);
|
||||
DCHECK_EQ(redundant_count_, 0);
|
||||
|
@ -1013,7 +1014,8 @@ FlagHistogram::Accumulate(Sample value, Count count, size_t index)
|
|||
|
||||
void
|
||||
FlagHistogram::AddSampleSet(const SampleSet& sample) {
|
||||
DCHECK_EQ(bucket_count(), sample.size());
|
||||
OffTheBooksMutexAutoLock locker(sample.mutex());
|
||||
DCHECK_EQ(bucket_count(), sample.size(locker));
|
||||
// We can't be sure the SampleSet provided came from another FlagHistogram,
|
||||
// so we take the following steps:
|
||||
// - If our flag has already been set do nothing.
|
||||
|
@ -1027,12 +1029,12 @@ FlagHistogram::AddSampleSet(const SampleSet& sample) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (sample.sum() != 1) {
|
||||
if (sample.sum(locker) != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t one_index = BucketIndex(1);
|
||||
if (sample.counts(one_index) == 1) {
|
||||
if (sample.counts(locker, one_index) == 1) {
|
||||
Accumulate(1, 1, one_index);
|
||||
}
|
||||
}
|
||||
|
@ -1084,18 +1086,20 @@ CountHistogram::Accumulate(Sample value, Count count, size_t index)
|
|||
|
||||
void
|
||||
CountHistogram::AddSampleSet(const SampleSet& sample) {
|
||||
DCHECK_EQ(bucket_count(), sample.size());
|
||||
OffTheBooksMutexAutoLock locker(sample.mutex());
|
||||
DCHECK_EQ(bucket_count(), sample.size(locker));
|
||||
// We can't be sure the SampleSet provided came from another CountHistogram,
|
||||
// so we at least check that the unused buckets are empty.
|
||||
|
||||
const size_t indices[] = { BucketIndex(0), BucketIndex(1), BucketIndex(2) };
|
||||
|
||||
if (sample.counts(indices[1]) != 0 || sample.counts(indices[2]) != 0) {
|
||||
if (sample.counts(locker, indices[1]) != 0 ||
|
||||
sample.counts(locker, indices[2]) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sample.counts(indices[0]) != 0) {
|
||||
Accumulate(1, sample.counts(indices[0]), indices[0]);
|
||||
if (sample.counts(locker, indices[0]) != 0) {
|
||||
Accumulate(1, sample.counts(locker, indices[0]), indices[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
@ -54,6 +55,10 @@
|
|||
class Pickle;
|
||||
|
||||
namespace base {
|
||||
|
||||
using mozilla::OffTheBooksMutex;
|
||||
using mozilla::OffTheBooksMutexAutoLock;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Provide easy general purpose histogram in a macro, just like stats counters.
|
||||
// The first four macros use 50 buckets.
|
||||
|
@ -329,32 +334,79 @@ class Histogram {
|
|||
explicit SampleSet();
|
||||
~SampleSet();
|
||||
|
||||
// This class contains a mozilla::OffTheBooksMutex, |mutex_|.
|
||||
// Most of the methods are thread-safe: they acquire and release
|
||||
// the mutex themselves. A few are not thread-safe, and require
|
||||
// the caller to provide evidence that the object is locked, by
|
||||
// supplying a const OffTheBooksMutexAutoLock& parameter. The
|
||||
// parameter is ignored but must be present. |mutex_| must be an
|
||||
// OffTheBooks variant because some of the containing SampleSet
|
||||
// objects are leaked until shutdown, so a standard Mutex can't be
|
||||
// used, since that does leak checking, and causes test failures.
|
||||
|
||||
//---------------- THREAD SAFE METHODS ----------------//
|
||||
//
|
||||
// The caller must not already hold |this.mutex_|, otherwise we
|
||||
// will end up deadlocking.
|
||||
|
||||
// Adjust size of counts_ for use with given histogram.
|
||||
void Resize(const Histogram& histogram);
|
||||
void CheckSize(const Histogram& histogram) const;
|
||||
|
||||
// Accessor for histogram to make routine additions.
|
||||
void AccumulateWithLinearStats(Sample value, Count count, size_t index);
|
||||
|
||||
// Accessor methods.
|
||||
Count counts(size_t i) const { return counts_[i]; }
|
||||
Count TotalCount() const;
|
||||
int64_t sum() const { return sum_; }
|
||||
uint64_t sum_squares() const { return sum_squares_; }
|
||||
double log_sum() const { return log_sum_; }
|
||||
double log_sum_squares() const { return log_sum_squares_; }
|
||||
int64_t redundant_count() const { return redundant_count_; }
|
||||
size_t size() const { return counts_.size(); }
|
||||
|
||||
// Arithmetic manipulation of corresponding elements of the set.
|
||||
void Add(const SampleSet& other);
|
||||
void Subtract(const SampleSet& other);
|
||||
|
||||
bool Serialize(Pickle* pickle) const;
|
||||
bool Deserialize(void** iter, const Pickle& pickle);
|
||||
|
||||
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
|
||||
|
||||
//---------------- THREAD UNSAFE METHODS ----------------//
|
||||
//
|
||||
// The caller must hold |this.mutex_|, and must supply evidence by passing
|
||||
// a const reference to the relevant OffTheBooksMutexAutoLock used.
|
||||
|
||||
Count counts(const OffTheBooksMutexAutoLock& ev, size_t i) const {
|
||||
return counts_[i];
|
||||
}
|
||||
Count TotalCount(const OffTheBooksMutexAutoLock& ev) const;
|
||||
int64_t sum(const OffTheBooksMutexAutoLock& ev) const {
|
||||
return sum_;
|
||||
}
|
||||
uint64_t sum_squares(const OffTheBooksMutexAutoLock& ev) const {
|
||||
return sum_squares_;
|
||||
}
|
||||
double log_sum(const OffTheBooksMutexAutoLock& ev) const {
|
||||
return log_sum_;
|
||||
}
|
||||
double log_sum_squares(const OffTheBooksMutexAutoLock& ev) const {
|
||||
return log_sum_squares_;
|
||||
}
|
||||
int64_t redundant_count(const OffTheBooksMutexAutoLock& ev) const {
|
||||
return redundant_count_;
|
||||
}
|
||||
size_t size(const OffTheBooksMutexAutoLock& ev) const {
|
||||
return counts_.size();
|
||||
}
|
||||
|
||||
// An assignment operator. The presence of mozilla::OffTheBooksMutex
|
||||
// in this class causes the default assignment operator to be deleted.
|
||||
const SampleSet& operator=(const SampleSet& other) {
|
||||
counts_ = other.counts_;
|
||||
sum_ = other.sum_;
|
||||
sum_squares_ = other.sum_squares_;
|
||||
log_sum_ = other.log_sum_;
|
||||
log_sum_squares_ = other.log_sum_squares_;
|
||||
redundant_count_ = other.redundant_count_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
void Accumulate(const OffTheBooksMutexAutoLock& ev,
|
||||
Sample value, Count count, size_t index);
|
||||
|
||||
protected:
|
||||
// Actual histogram data is stored in buckets, showing the count of values
|
||||
// that fit into each bucket.
|
||||
|
@ -371,9 +423,6 @@ class Histogram {
|
|||
double log_sum_; // sum of logs of samples.
|
||||
double log_sum_squares_; // sum of squares of logs of samples
|
||||
|
||||
private:
|
||||
void Accumulate(Sample value, Count count, size_t index);
|
||||
|
||||
// To help identify memory corruption, we reduntantly save the number of
|
||||
// samples we've accumulated into all of our buckets. We can compare this
|
||||
// count to the sum of the counts in all buckets, and detect problems. Note
|
||||
|
@ -382,6 +431,13 @@ class Histogram {
|
|||
// and also the snapshotting code may asynchronously get a mismatch (though
|
||||
// generally either race based mismatch cause is VERY rare).
|
||||
int64_t redundant_count_;
|
||||
|
||||
private:
|
||||
// Protects all data fields.
|
||||
mutable OffTheBooksMutex mutex_;
|
||||
|
||||
public:
|
||||
OffTheBooksMutex& mutex() const { return mutex_; }
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
@ -454,7 +510,9 @@ class Histogram {
|
|||
// produce a false-alarm if a race occurred in the reading of the data during
|
||||
// a SnapShot process, but should otherwise be false at all times (unless we
|
||||
// have memory over-writes, or DRAM failures).
|
||||
virtual Inconsistencies FindCorruption(const SampleSet& snapshot) const;
|
||||
virtual Inconsistencies FindCorruption(const SampleSet& snapshot,
|
||||
const OffTheBooksMutexAutoLock&
|
||||
snapshotLockEvidence) const;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Accessors for factory constuction, serialization and testing.
|
||||
|
@ -466,8 +524,10 @@ class Histogram {
|
|||
virtual Sample ranges(size_t i) const;
|
||||
uint32_t range_checksum() const { return range_checksum_; }
|
||||
virtual size_t bucket_count() const;
|
||||
// Snapshot the current complete set of sample data.
|
||||
// Override with atomic/locked snapshot if needed.
|
||||
|
||||
// Do a safe atomic snapshot of sample data. The caller is assumed to
|
||||
// have exclusive access to the destination, |*sample|, and no locking
|
||||
// of it is done here. This routine does lock the source sample though.
|
||||
virtual void SnapshotSample(SampleSet* sample) const;
|
||||
|
||||
virtual bool HasConstructorArguments(Sample minimum, Sample maximum,
|
||||
|
@ -543,10 +603,13 @@ class Histogram {
|
|||
// Helpers for emitting Ascii graphic. Each method appends data to output.
|
||||
|
||||
// Find out how large the (graphically) the largest bucket will appear to be.
|
||||
double GetPeakBucketSize(const SampleSet& snapshot) const;
|
||||
double GetPeakBucketSize(const SampleSet& snapshot,
|
||||
const OffTheBooksMutexAutoLock&
|
||||
snapshotLockEvidence) const;
|
||||
|
||||
// Write a common header message describing this histogram.
|
||||
void WriteAsciiHeader(const SampleSet& snapshot,
|
||||
const OffTheBooksMutexAutoLock& snapshotLockEvidence,
|
||||
Count sample_count, std::string* output) const;
|
||||
|
||||
// Write information about previous, current, and next buckets.
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -29,15 +29,150 @@ namespace wasm {
|
|||
|
||||
enum class Expr : uint8_t
|
||||
{
|
||||
Ret,
|
||||
|
||||
// Control opcodes
|
||||
Nop,
|
||||
Block,
|
||||
|
||||
IfThen,
|
||||
Loop,
|
||||
If,
|
||||
IfElse,
|
||||
Switch,
|
||||
Select,
|
||||
Br,
|
||||
BrIf,
|
||||
TableSwitch,
|
||||
Return,
|
||||
Unreachable,
|
||||
|
||||
While,
|
||||
// Calls
|
||||
CallInternal,
|
||||
CallIndirect,
|
||||
CallImport,
|
||||
|
||||
// Constants and calls
|
||||
I8Const,
|
||||
I32Const,
|
||||
I64Const,
|
||||
F64Const,
|
||||
F32Const,
|
||||
GetLocal,
|
||||
SetLocal,
|
||||
LoadGlobal,
|
||||
StoreGlobal,
|
||||
|
||||
// I32 opcodes
|
||||
I32Add,
|
||||
I32Sub,
|
||||
I32Mul,
|
||||
I32DivS,
|
||||
I32DivU,
|
||||
I32RemS,
|
||||
I32RemU,
|
||||
I32Ior,
|
||||
I32And,
|
||||
I32Xor,
|
||||
I32Shl,
|
||||
I32ShrU,
|
||||
I32ShrS,
|
||||
I32Eq,
|
||||
I32Ne,
|
||||
I32LtS,
|
||||
I32LeS,
|
||||
I32LtU,
|
||||
I32LeU,
|
||||
I32GtS,
|
||||
I32GeS,
|
||||
I32GtU,
|
||||
I32GeU,
|
||||
I32Clz,
|
||||
I32Ctz,
|
||||
I32Popcnt,
|
||||
|
||||
// F32 opcodes
|
||||
F32Add,
|
||||
F32Sub,
|
||||
F32Mul,
|
||||
F32Div,
|
||||
F32Min,
|
||||
F32Max,
|
||||
F32Abs,
|
||||
F32Neg,
|
||||
F32CopySign,
|
||||
F32Ceil,
|
||||
F32Floor,
|
||||
F32Trunc,
|
||||
F32NearestInt,
|
||||
F32Sqrt,
|
||||
F32Eq,
|
||||
F32Ne,
|
||||
F32Lt,
|
||||
F32Le,
|
||||
F32Gt,
|
||||
F32Ge,
|
||||
|
||||
// F64 opcodes
|
||||
F64Add,
|
||||
F64Sub,
|
||||
F64Mul,
|
||||
F64Div,
|
||||
F64Min,
|
||||
F64Max,
|
||||
F64Abs,
|
||||
F64Neg,
|
||||
F64CopySign,
|
||||
F64Ceil,
|
||||
F64Floor,
|
||||
F64Trunc,
|
||||
F64NearestInt,
|
||||
F64Sqrt,
|
||||
F64Eq,
|
||||
F64Ne,
|
||||
F64Lt,
|
||||
F64Le,
|
||||
F64Gt,
|
||||
F64Ge,
|
||||
|
||||
// Conversions
|
||||
I32SConvertF32,
|
||||
I32SConvertF64,
|
||||
I32UConvertF32,
|
||||
I32UConvertF64,
|
||||
I32ConvertI64,
|
||||
I64SConvertF32,
|
||||
I64SConvertF64,
|
||||
I64UConvertF32,
|
||||
I64UConvertF64,
|
||||
I64SConvertI32,
|
||||
I64UConvertI32,
|
||||
|
||||
// Load/store operations
|
||||
I32LoadMem8S,
|
||||
I32LoadMem8U,
|
||||
I32LoadMem16S,
|
||||
I32LoadMem16U,
|
||||
I32LoadMem,
|
||||
I64LoadMem8S,
|
||||
I64LoadMem8U,
|
||||
I64LoadMem16S,
|
||||
I64LoadMem16U,
|
||||
I64LoadMem32S,
|
||||
I64LoadMem32U,
|
||||
I64LoadMem,
|
||||
F32LoadMem,
|
||||
F64LoadMem,
|
||||
|
||||
I32StoreMem8,
|
||||
I32StoreMem16,
|
||||
I64StoreMem8,
|
||||
I64StoreMem16,
|
||||
I64StoreMem32,
|
||||
I32StoreMem,
|
||||
I64StoreMem,
|
||||
F32StoreMem,
|
||||
F64StoreMem,
|
||||
|
||||
// asm.js specific
|
||||
Ternary, // to be merged with IfElse
|
||||
|
||||
While, // all CFG ops to be deleted in favor of Loop/Br/BrIf
|
||||
DoWhile,
|
||||
|
||||
ForInitInc,
|
||||
|
@ -51,201 +186,97 @@ enum class Expr : uint8_t
|
|||
Break,
|
||||
BreakLabel,
|
||||
|
||||
GetLocal,
|
||||
SetLocal,
|
||||
|
||||
CallInternal,
|
||||
CallIndirect,
|
||||
CallImport,
|
||||
|
||||
AtomicsFence,
|
||||
|
||||
I32Expr,
|
||||
I32Expr, // to be removed
|
||||
F32Expr,
|
||||
F64Expr,
|
||||
I32X4Expr,
|
||||
F32X4Expr,
|
||||
B32X4Expr,
|
||||
|
||||
// asm.js specific
|
||||
Id,
|
||||
Noop,
|
||||
|
||||
LoadGlobal,
|
||||
StoreGlobal,
|
||||
|
||||
InterruptCheckHead,
|
||||
InterruptCheckLoop,
|
||||
|
||||
DebugCheckPoint,
|
||||
Bad,
|
||||
|
||||
// I32 opcodes
|
||||
I32CallInternal,
|
||||
I32CallIndirect,
|
||||
I32CallImport,
|
||||
|
||||
I32Conditional,
|
||||
I32Comma,
|
||||
|
||||
I32Literal,
|
||||
|
||||
// Binary arith opcodes
|
||||
I32Add,
|
||||
I32Sub,
|
||||
I32Mul,
|
||||
I32SDiv,
|
||||
I32SMod,
|
||||
I32UDiv,
|
||||
I32UMod,
|
||||
I32Min,
|
||||
I32Max,
|
||||
|
||||
// Unary arith opcodes
|
||||
I32Not,
|
||||
I32Neg,
|
||||
|
||||
// Bitwise opcodes
|
||||
I32BitOr,
|
||||
I32BitAnd,
|
||||
I32BitXor,
|
||||
I32BitNot,
|
||||
|
||||
I32Lsh,
|
||||
I32ArithRsh,
|
||||
I32LogicRsh,
|
||||
|
||||
// Conversion opcodes
|
||||
I32FromF32,
|
||||
I32FromF64,
|
||||
|
||||
// Math builtin opcodes
|
||||
I32Clz,
|
||||
I32Abs,
|
||||
|
||||
// Comparison opcodes
|
||||
// Ordering matters (EmitComparison expects signed opcodes to be placed
|
||||
// before unsigned opcodes)
|
||||
I32EqI32,
|
||||
I32NeI32,
|
||||
I32SLtI32,
|
||||
I32SLeI32,
|
||||
I32SGtI32,
|
||||
I32SGeI32,
|
||||
I32ULtI32,
|
||||
I32ULeI32,
|
||||
I32UGtI32,
|
||||
I32UGeI32,
|
||||
|
||||
I32EqF32,
|
||||
I32NeF32,
|
||||
I32LtF32,
|
||||
I32LeF32,
|
||||
I32GtF32,
|
||||
I32GeF32,
|
||||
|
||||
I32EqF64,
|
||||
I32NeF64,
|
||||
I32LtF64,
|
||||
I32LeF64,
|
||||
I32GtF64,
|
||||
I32GeF64,
|
||||
|
||||
// Heap accesses opcodes
|
||||
I32SLoad8,
|
||||
I32SLoad16,
|
||||
I32SLoad32,
|
||||
I32ULoad8,
|
||||
I32ULoad16,
|
||||
I32ULoad32,
|
||||
I32Store8,
|
||||
I32Store16,
|
||||
I32Store32,
|
||||
|
||||
// Atomics opcodes
|
||||
// Atomics
|
||||
AtomicsFence,
|
||||
I32AtomicsCompareExchange,
|
||||
I32AtomicsExchange,
|
||||
I32AtomicsLoad,
|
||||
I32AtomicsStore,
|
||||
I32AtomicsBinOp,
|
||||
|
||||
// SIMD opcodes
|
||||
// SIMD
|
||||
I32X4Const,
|
||||
B32X4Const,
|
||||
F32X4Const,
|
||||
|
||||
I32X4Expr,
|
||||
F32X4Expr,
|
||||
B32X4Expr,
|
||||
|
||||
I32I32X4ExtractLane,
|
||||
I32B32X4ExtractLane,
|
||||
I32B32X4AllTrue,
|
||||
I32B32X4AnyTrue,
|
||||
|
||||
// Specific to AsmJS
|
||||
I32Id,
|
||||
F32F32X4ExtractLane,
|
||||
|
||||
// F32 opcdoes
|
||||
// Common opcodes
|
||||
F32CallInternal,
|
||||
F32CallIndirect,
|
||||
F32CallImport,
|
||||
I32X4Ctor,
|
||||
I32X4Unary,
|
||||
I32X4Binary,
|
||||
I32X4BinaryBitwise,
|
||||
I32X4BinaryShift,
|
||||
I32X4ReplaceLane,
|
||||
I32X4FromF32X4,
|
||||
I32X4FromF32X4Bits,
|
||||
I32X4Swizzle,
|
||||
I32X4Shuffle,
|
||||
I32X4Select,
|
||||
I32X4Splat,
|
||||
I32X4Load,
|
||||
I32X4Store,
|
||||
|
||||
F32Conditional,
|
||||
F32Comma,
|
||||
F32X4Ctor,
|
||||
F32X4Unary,
|
||||
F32X4Binary,
|
||||
F32X4ReplaceLane,
|
||||
F32X4FromI32X4,
|
||||
F32X4FromI32X4Bits,
|
||||
F32X4Swizzle,
|
||||
F32X4Shuffle,
|
||||
F32X4Select,
|
||||
F32X4Splat,
|
||||
F32X4Load,
|
||||
F32X4Store,
|
||||
|
||||
F32Literal,
|
||||
B32X4Ctor,
|
||||
B32X4Unary,
|
||||
B32X4Binary,
|
||||
B32X4BinaryCompI32X4,
|
||||
B32X4BinaryCompF32X4,
|
||||
B32X4BinaryBitwise,
|
||||
B32X4ReplaceLane,
|
||||
B32X4Splat,
|
||||
|
||||
// Binary arith opcodes
|
||||
F32Add,
|
||||
F32Sub,
|
||||
F32Mul,
|
||||
F32Div,
|
||||
F32Min,
|
||||
F32Max,
|
||||
F32Neg,
|
||||
// I32 asm.js opcodes
|
||||
I32Not,
|
||||
I32Neg,
|
||||
I32BitNot,
|
||||
I32Abs,
|
||||
|
||||
// Math builtin opcodes
|
||||
F32Abs,
|
||||
F32Sqrt,
|
||||
F32Ceil,
|
||||
F32Floor,
|
||||
|
||||
// Conversion opcodes
|
||||
// F32 asm.js opcodes
|
||||
F32FromF64,
|
||||
F32FromS32,
|
||||
F32FromU32,
|
||||
|
||||
// Heap accesses opcodes
|
||||
F32Load,
|
||||
F32StoreF32,
|
||||
F32StoreF64,
|
||||
F32StoreMemF64,
|
||||
|
||||
// SIMD opcodes
|
||||
F32F32X4ExtractLane,
|
||||
|
||||
// asm.js specific
|
||||
F32Id,
|
||||
|
||||
// F64 opcodes
|
||||
// Common opcodes
|
||||
F64CallInternal,
|
||||
F64CallIndirect,
|
||||
F64CallImport,
|
||||
|
||||
F64Conditional,
|
||||
F64Comma,
|
||||
|
||||
F64Literal,
|
||||
|
||||
// Binary arith opcodes
|
||||
F64Add,
|
||||
F64Sub,
|
||||
F64Mul,
|
||||
F64Div,
|
||||
F64Min,
|
||||
F64Max,
|
||||
// F64 asm.js opcodes
|
||||
F64Mod,
|
||||
F64Neg,
|
||||
|
||||
// Math builtin opcodes
|
||||
F64Abs,
|
||||
F64Sqrt,
|
||||
F64Ceil,
|
||||
F64Floor,
|
||||
F64Sin,
|
||||
F64Cos,
|
||||
F64Tan,
|
||||
|
@ -257,115 +288,11 @@ enum class Expr : uint8_t
|
|||
F64Pow,
|
||||
F64Atan2,
|
||||
|
||||
// Conversions opcodes
|
||||
F64FromF32,
|
||||
F64FromS32,
|
||||
F64FromU32,
|
||||
|
||||
// Heap accesses opcodes
|
||||
F64Load,
|
||||
F64StoreF32,
|
||||
F64StoreF64,
|
||||
|
||||
// asm.js specific
|
||||
F64Id,
|
||||
|
||||
// I32X4 opcodes
|
||||
// Common opcodes
|
||||
I32X4CallInternal,
|
||||
I32X4CallIndirect,
|
||||
I32X4CallImport,
|
||||
|
||||
I32X4Conditional,
|
||||
I32X4Comma,
|
||||
|
||||
I32X4Literal,
|
||||
|
||||
// Specific opcodes
|
||||
I32X4Ctor,
|
||||
|
||||
I32X4Unary,
|
||||
|
||||
I32X4Binary,
|
||||
I32X4BinaryBitwise,
|
||||
I32X4BinaryShift,
|
||||
|
||||
I32X4ReplaceLane,
|
||||
|
||||
I32X4FromF32X4,
|
||||
I32X4FromF32X4Bits,
|
||||
|
||||
I32X4Swizzle,
|
||||
I32X4Shuffle,
|
||||
I32X4Select,
|
||||
I32X4Splat,
|
||||
|
||||
I32X4Load,
|
||||
I32X4Store,
|
||||
|
||||
// asm.js specific
|
||||
I32X4Id,
|
||||
|
||||
// F32X4 opcodes
|
||||
// Common opcodes
|
||||
F32X4CallInternal,
|
||||
F32X4CallIndirect,
|
||||
F32X4CallImport,
|
||||
|
||||
F32X4Conditional,
|
||||
F32X4Comma,
|
||||
|
||||
F32X4Literal,
|
||||
|
||||
// Specific opcodes
|
||||
F32X4Ctor,
|
||||
|
||||
F32X4Unary,
|
||||
|
||||
F32X4Binary,
|
||||
|
||||
F32X4ReplaceLane,
|
||||
|
||||
F32X4FromI32X4,
|
||||
F32X4FromI32X4Bits,
|
||||
F32X4Swizzle,
|
||||
F32X4Shuffle,
|
||||
F32X4Select,
|
||||
F32X4Splat,
|
||||
|
||||
F32X4Load,
|
||||
F32X4Store,
|
||||
|
||||
// asm.js specific
|
||||
F32X4Id,
|
||||
|
||||
// B32X4 opcodes
|
||||
// Common opcodes
|
||||
B32X4CallInternal,
|
||||
B32X4CallIndirect,
|
||||
B32X4CallImport,
|
||||
|
||||
B32X4Conditional,
|
||||
B32X4Comma,
|
||||
|
||||
B32X4Literal,
|
||||
|
||||
// Specific opcodes
|
||||
B32X4Ctor,
|
||||
|
||||
B32X4Unary,
|
||||
|
||||
B32X4Binary,
|
||||
B32X4BinaryCompI32X4,
|
||||
B32X4BinaryCompF32X4,
|
||||
B32X4BinaryBitwise,
|
||||
|
||||
B32X4ReplaceLane,
|
||||
|
||||
B32X4Splat,
|
||||
|
||||
// asm.js specific
|
||||
B32X4Id
|
||||
F64StoreMemF32
|
||||
};
|
||||
|
||||
enum NeedsBoundsCheck : uint8_t
|
||||
|
@ -382,7 +309,7 @@ typedef UniquePtr<Bytecode> UniqueBytecode;
|
|||
class Encoder
|
||||
{
|
||||
UniqueBytecode bytecode_;
|
||||
mozilla::DebugOnly<bool> done_;
|
||||
DebugOnly<bool> done_;
|
||||
|
||||
template<class T>
|
||||
MOZ_WARN_UNUSED_RESULT
|
||||
|
@ -398,9 +325,9 @@ class Encoder
|
|||
done_(false)
|
||||
{}
|
||||
|
||||
bool init(UniqueBytecode bytecode) {
|
||||
bool init(UniqueBytecode bytecode = UniqueBytecode()) {
|
||||
if (bytecode) {
|
||||
bytecode_ = mozilla::Move(bytecode);
|
||||
bytecode_ = Move(bytecode);
|
||||
bytecode_->clear();
|
||||
return true;
|
||||
}
|
||||
|
@ -414,7 +341,20 @@ class Encoder
|
|||
UniqueBytecode finish() {
|
||||
MOZ_ASSERT(!done_);
|
||||
done_ = true;
|
||||
return mozilla::Move(bytecode_);
|
||||
return Move(bytecode_);
|
||||
}
|
||||
|
||||
MOZ_WARN_UNUSED_RESULT bool
|
||||
writeVarU32(uint32_t i) {
|
||||
do {
|
||||
uint8_t byte = i & 0x7F;
|
||||
i >>= 7;
|
||||
if (i != 0)
|
||||
byte |= 0x80;
|
||||
if (!writeU8(byte))
|
||||
return false;
|
||||
} while(i != 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_WARN_UNUSED_RESULT bool
|
||||
|
@ -453,7 +393,7 @@ class Encoder
|
|||
bool pcIsPatchable(size_t pc, unsigned size) const {
|
||||
bool patchable = true;
|
||||
for (unsigned i = 0; patchable && i < size; i++)
|
||||
patchable &= Expr((*bytecode_)[pc]) == Expr::Bad;
|
||||
patchable &= Expr((*bytecode_)[pc]) == Expr::Unreachable;
|
||||
return patchable;
|
||||
}
|
||||
#endif
|
||||
|
@ -470,11 +410,6 @@ class Encoder
|
|||
MOZ_ASSERT(pcIsPatchable(pc, sizeof(uint32_t)));
|
||||
memcpy(&(*bytecode_)[pc], &i, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
void patchSig(size_t pc, const LifoSig* ptr) {
|
||||
MOZ_ASSERT(pcIsPatchable(pc, sizeof(LifoSig*)));
|
||||
memcpy(&(*bytecode_)[pc], &ptr, sizeof(LifoSig*));
|
||||
}
|
||||
};
|
||||
|
||||
class Decoder
|
||||
|
@ -525,7 +460,6 @@ class Decoder
|
|||
MOZ_WARN_UNUSED_RESULT bool readF32(float* f) { return read(f); }
|
||||
MOZ_WARN_UNUSED_RESULT bool readU32(uint32_t* u) { return read(u); }
|
||||
MOZ_WARN_UNUSED_RESULT bool readF64(double* d) { return read(d); }
|
||||
MOZ_WARN_UNUSED_RESULT bool readSig(const LifoSig* sig) { return read(sig); }
|
||||
|
||||
MOZ_WARN_UNUSED_RESULT bool readI32X4(jit::SimdConstant* c) {
|
||||
int32_t v[4] = { 0, 0, 0, 0 };
|
||||
|
@ -546,6 +480,26 @@ class Decoder
|
|||
return true;
|
||||
}
|
||||
|
||||
MOZ_WARN_UNUSED_RESULT bool readVarU32(uint32_t* decoded) {
|
||||
*decoded = 0;
|
||||
uint8_t byte;
|
||||
uint32_t shift = 0;
|
||||
do {
|
||||
if (!readU8(&byte))
|
||||
return false;
|
||||
if (!(byte & 0x80)) {
|
||||
*decoded |= uint32_t(byte & 0x7F) << shift;
|
||||
return true;
|
||||
}
|
||||
*decoded |= uint32_t(byte & 0x7F) << shift;
|
||||
shift += 7;
|
||||
} while (shift != 28);
|
||||
if (!readU8(&byte) || (byte & 0xF0))
|
||||
return false;
|
||||
*decoded |= uint32_t(byte) << 28;
|
||||
return true;
|
||||
}
|
||||
|
||||
// The infallible unpacking API should be used when we are sure that the
|
||||
// bytecode is well-formed.
|
||||
uint8_t uncheckedReadU8 () { return uncheckedRead<uint8_t>(); }
|
||||
|
@ -553,7 +507,6 @@ class Decoder
|
|||
float uncheckedReadF32() { return uncheckedRead<float>(); }
|
||||
uint32_t uncheckedReadU32() { return uncheckedRead<uint32_t>(); }
|
||||
double uncheckedReadF64() { return uncheckedRead<double>(); }
|
||||
const LifoSig* uncheckedReadSig() { return uncheckedRead<const LifoSig*>(); }
|
||||
|
||||
jit::SimdConstant uncheckedReadI32X4() {
|
||||
int32_t v[4] = { 0, 0, 0, 0 };
|
||||
|
@ -567,6 +520,25 @@ class Decoder
|
|||
v[i] = uncheckedReadF32();
|
||||
return jit::SimdConstant::CreateX4(v[0], v[1], v[2], v[3]);
|
||||
}
|
||||
|
||||
uint32_t uncheckedReadVarU32() {
|
||||
uint32_t decoded = 0;
|
||||
uint32_t shift = 0;
|
||||
uint8_t byte;
|
||||
do {
|
||||
byte = uncheckedReadU8();
|
||||
if (!(byte & 0x80)) {
|
||||
decoded |= uint32_t(byte & 0x7F) << shift;
|
||||
return decoded;
|
||||
}
|
||||
decoded |= uint32_t(byte & 0x7F) << shift;
|
||||
shift += 7;
|
||||
} while (shift != 28);
|
||||
byte = uncheckedReadU8();
|
||||
MOZ_ASSERT(!(byte & 0xF0));
|
||||
decoded |= uint32_t(byte) << 28;
|
||||
return decoded;
|
||||
}
|
||||
};
|
||||
|
||||
// Source coordinates for a call site. As they're read sequentially, we
|
||||
|
@ -579,7 +551,6 @@ struct SourceCoords {
|
|||
};
|
||||
|
||||
typedef Vector<SourceCoords, 0, SystemAllocPolicy> SourceCoordsVector;
|
||||
typedef Vector<ValType, 0, SystemAllocPolicy> ValTypeVector;
|
||||
|
||||
// The FuncBytecode class contains the intermediate representation of a
|
||||
// parsed/decoded and validated asm.js/WebAssembly function. The FuncBytecode
|
||||
|
@ -595,7 +566,7 @@ class FuncBytecode
|
|||
SourceCoordsVector callSourceCoords_;
|
||||
|
||||
uint32_t index_;
|
||||
const LifoSig& sig_;
|
||||
const DeclaredSig& sig_;
|
||||
UniqueBytecode bytecode_;
|
||||
ValTypeVector localVars_;
|
||||
unsigned generateTime_;
|
||||
|
@ -606,22 +577,22 @@ class FuncBytecode
|
|||
unsigned column,
|
||||
SourceCoordsVector&& sourceCoords,
|
||||
uint32_t index,
|
||||
const LifoSig& sig,
|
||||
const DeclaredSig& sig,
|
||||
UniqueBytecode bytecode,
|
||||
ValTypeVector&& localVars,
|
||||
unsigned generateTime)
|
||||
: name_(name),
|
||||
line_(line),
|
||||
column_(column),
|
||||
callSourceCoords_(mozilla::Move(sourceCoords)),
|
||||
callSourceCoords_(Move(sourceCoords)),
|
||||
index_(index),
|
||||
sig_(sig),
|
||||
bytecode_(mozilla::Move(bytecode)),
|
||||
localVars_(mozilla::Move(localVars)),
|
||||
bytecode_(Move(bytecode)),
|
||||
localVars_(Move(localVars)),
|
||||
generateTime_(generateTime)
|
||||
{}
|
||||
|
||||
UniqueBytecode recycleBytecode() { return mozilla::Move(bytecode_); }
|
||||
UniqueBytecode recycleBytecode() { return Move(bytecode_); }
|
||||
|
||||
PropertyName* name() const { return name_; }
|
||||
unsigned line() const { return line_; }
|
||||
|
@ -629,7 +600,7 @@ class FuncBytecode
|
|||
const SourceCoords& sourceCoords(size_t i) const { return callSourceCoords_[i]; }
|
||||
|
||||
uint32_t index() const { return index_; }
|
||||
const LifoSig& sig() const { return sig_; }
|
||||
const DeclaredSig& sig() const { return sig_; }
|
||||
const Bytecode& bytecode() const { return *bytecode_; }
|
||||
|
||||
size_t numLocalVars() const { return localVars_.length(); }
|
||||
|
|
|
@ -35,14 +35,11 @@ static const unsigned COMPILATION_LIFO_DEFAULT_CHUNK_SIZE = 64 * 1024;
|
|||
|
||||
ModuleGenerator::ModuleGenerator(ExclusiveContext* cx)
|
||||
: cx_(cx),
|
||||
jcx_(CompileRuntime::get(cx->compartment()->runtimeFromAnyThread())),
|
||||
slowFuncs_(cx),
|
||||
lifo_(GENERATOR_LIFO_DEFAULT_CHUNK_SIZE),
|
||||
jcx_(CompileRuntime::get(cx->compartment()->runtimeFromAnyThread())),
|
||||
alloc_(&lifo_),
|
||||
masm_(MacroAssembler::AsmJSToken(), alloc_),
|
||||
sigs_(cx),
|
||||
funcEntryOffsets_(cx),
|
||||
exportFuncIndices_(cx),
|
||||
funcIndexToExport_(cx),
|
||||
parallel_(false),
|
||||
outstanding_(0),
|
||||
|
@ -105,20 +102,26 @@ ParallelCompilationEnabled(ExclusiveContext* cx)
|
|||
}
|
||||
|
||||
bool
|
||||
ModuleGenerator::init()
|
||||
ModuleGenerator::init(UniqueModuleGeneratorData shared)
|
||||
{
|
||||
module_ = cx_->make_unique<ModuleData>();
|
||||
module_ = MakeUnique<ModuleData>();
|
||||
if (!module_)
|
||||
return false;
|
||||
|
||||
module_->globalBytes = InitialGlobalDataBytes;
|
||||
module_->compileArgs = CompileArgs(cx_);
|
||||
|
||||
link_ = cx_->make_unique<StaticLinkData>();
|
||||
link_ = MakeUnique<StaticLinkData>();
|
||||
if (!link_)
|
||||
return false;
|
||||
|
||||
if (!sigs_.init() || !funcIndexToExport_.init())
|
||||
shared_ = Move(shared);
|
||||
|
||||
threadView_ = MakeUnique<ModuleGeneratorThreadView>(*shared_);
|
||||
if (!threadView_)
|
||||
return false;
|
||||
|
||||
if (!funcIndexToExport_.init())
|
||||
return false;
|
||||
|
||||
uint32_t numTasks;
|
||||
|
@ -142,9 +145,9 @@ ModuleGenerator::init()
|
|||
|
||||
if (!tasks_.initCapacity(numTasks))
|
||||
return false;
|
||||
JSRuntime* runtime = cx_->compartment()->runtimeFromAnyThread();
|
||||
JSRuntime* rt = cx_->compartment()->runtimeFromAnyThread();
|
||||
for (size_t i = 0; i < numTasks; i++)
|
||||
tasks_.infallibleEmplaceBack(runtime, args(), COMPILATION_LIFO_DEFAULT_CHUNK_SIZE);
|
||||
tasks_.infallibleEmplaceBack(rt, args(), *threadView_, COMPILATION_LIFO_DEFAULT_CHUNK_SIZE);
|
||||
|
||||
if (!freeTasks_.reserve(numTasks))
|
||||
return false;
|
||||
|
@ -154,23 +157,6 @@ ModuleGenerator::init()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ModuleGenerator::allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* globalDataOffset)
|
||||
{
|
||||
uint32_t globalBytes = module_->globalBytes;
|
||||
|
||||
uint32_t pad = ComputeByteAlignment(globalBytes, align);
|
||||
if (UINT32_MAX - globalBytes < pad + bytes)
|
||||
return false;
|
||||
|
||||
globalBytes += pad;
|
||||
*globalDataOffset = globalBytes;
|
||||
globalBytes += bytes;
|
||||
|
||||
module_->globalBytes = globalBytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ModuleGenerator::finishOutstandingTask()
|
||||
{
|
||||
|
@ -210,6 +196,7 @@ ModuleGenerator::finishTask(IonCompileTask* task)
|
|||
results.offsets().offsetBy(offsetInWhole);
|
||||
|
||||
// Record the non-profiling entry for whole-module linking later.
|
||||
// Cannot simply append because funcIndex order is nonlinear.
|
||||
if (func.index() >= funcEntryOffsets_.length()) {
|
||||
if (!funcEntryOffsets_.resize(func.index() + 1))
|
||||
return false;
|
||||
|
@ -243,18 +230,21 @@ ModuleGenerator::finishTask(IonCompileTask* task)
|
|||
return true;
|
||||
}
|
||||
|
||||
const LifoSig*
|
||||
ModuleGenerator::newLifoSig(const MallocSig& sig)
|
||||
bool
|
||||
ModuleGenerator::allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* globalDataOffset)
|
||||
{
|
||||
SigSet::AddPtr p = sigs_.lookupForAdd(sig);
|
||||
if (p)
|
||||
return *p;
|
||||
uint32_t globalBytes = module_->globalBytes;
|
||||
|
||||
LifoSig* lifoSig = LifoSig::new_(lifo_, sig);
|
||||
if (!lifoSig || !sigs_.add(p, lifoSig))
|
||||
return nullptr;
|
||||
uint32_t pad = ComputeByteAlignment(globalBytes, align);
|
||||
if (UINT32_MAX - globalBytes < pad + bytes)
|
||||
return false;
|
||||
|
||||
return lifoSig;
|
||||
globalBytes += pad;
|
||||
*globalDataOffset = globalBytes;
|
||||
globalBytes += bytes;
|
||||
|
||||
module_->globalBytes = globalBytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -279,17 +269,35 @@ ModuleGenerator::allocateGlobalVar(ValType type, uint32_t* globalDataOffset)
|
|||
return allocateGlobalBytes(width, width, globalDataOffset);
|
||||
}
|
||||
|
||||
bool
|
||||
ModuleGenerator::declareImport(MallocSig&& sig, unsigned* index)
|
||||
void
|
||||
ModuleGenerator::initSig(uint32_t sigIndex, Sig&& sig)
|
||||
{
|
||||
static_assert(Module::SizeOfImportExit % sizeof(void*) == 0, "word aligned");
|
||||
MOZ_ASSERT(shared_->sigs[sigIndex] == Sig());
|
||||
shared_->sigs[sigIndex] = Move(sig);
|
||||
}
|
||||
|
||||
uint32_t globalDataOffset;
|
||||
if (!allocateGlobalBytes(Module::SizeOfImportExit, sizeof(void*), &globalDataOffset))
|
||||
const DeclaredSig&
|
||||
ModuleGenerator::sig(uint32_t index) const
|
||||
{
|
||||
return shared_->sigs[index];
|
||||
}
|
||||
|
||||
bool
|
||||
ModuleGenerator::initImport(uint32_t importIndex, uint32_t sigIndex, uint32_t globalDataOffset)
|
||||
{
|
||||
MOZ_ASSERT(importIndex == module_->imports.length());
|
||||
|
||||
Sig copy;
|
||||
if (!copy.clone(sig(sigIndex)))
|
||||
return false;
|
||||
if (!module_->imports.emplaceBack(Move(copy), globalDataOffset))
|
||||
return false;
|
||||
|
||||
*index = unsigned(module_->imports.length());
|
||||
return module_->imports.emplaceBack(Move(sig), globalDataOffset);
|
||||
ModuleImportGeneratorData& import = shared_->imports[importIndex];
|
||||
MOZ_ASSERT(!import.sig);
|
||||
import.sig = &shared_->sigs[sigIndex];
|
||||
import.globalDataOffset = globalDataOffset;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
|
@ -298,16 +306,11 @@ ModuleGenerator::numImports() const
|
|||
return module_->imports.length();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ModuleGenerator::importExitGlobalDataOffset(uint32_t index) const
|
||||
const ModuleImportGeneratorData&
|
||||
ModuleGenerator::import(uint32_t index) const
|
||||
{
|
||||
return module_->imports[index].exitGlobalDataOffset();
|
||||
}
|
||||
|
||||
const MallocSig&
|
||||
ModuleGenerator::importSig(uint32_t index) const
|
||||
{
|
||||
return module_->imports[index].sig();
|
||||
MOZ_ASSERT(shared_->imports[index].sig);
|
||||
return shared_->imports[index];
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -321,7 +324,7 @@ ModuleGenerator::defineImport(uint32_t index, ProfilingOffsets interpExit, Profi
|
|||
}
|
||||
|
||||
bool
|
||||
ModuleGenerator::declareExport(MallocSig&& sig, uint32_t funcIndex, uint32_t* exportIndex)
|
||||
ModuleGenerator::declareExport(uint32_t funcIndex, uint32_t* exportIndex)
|
||||
{
|
||||
FuncIndexMap::AddPtr p = funcIndexToExport_.lookupForAdd(funcIndex);
|
||||
if (p) {
|
||||
|
@ -329,9 +332,13 @@ ModuleGenerator::declareExport(MallocSig&& sig, uint32_t funcIndex, uint32_t* ex
|
|||
return true;
|
||||
}
|
||||
|
||||
Sig copy;
|
||||
if (!copy.clone(funcSig(funcIndex)))
|
||||
return false;
|
||||
|
||||
*exportIndex = module_->exports.length();
|
||||
return funcIndexToExport_.add(p, funcIndex, *exportIndex) &&
|
||||
module_->exports.append(Move(sig)) &&
|
||||
module_->exports.append(Move(copy)) &&
|
||||
exportFuncIndices_.append(funcIndex);
|
||||
}
|
||||
|
||||
|
@ -341,7 +348,7 @@ ModuleGenerator::exportFuncIndex(uint32_t index) const
|
|||
return exportFuncIndices_[index];
|
||||
}
|
||||
|
||||
const MallocSig&
|
||||
const Sig&
|
||||
ModuleGenerator::exportSig(uint32_t index) const
|
||||
{
|
||||
return module_->exports[index].sig();
|
||||
|
@ -360,6 +367,21 @@ ModuleGenerator::defineExport(uint32_t index, Offsets offsets)
|
|||
return module_->codeRanges.emplaceBack(CodeRange::Entry, offsets);
|
||||
}
|
||||
|
||||
bool
|
||||
ModuleGenerator::initFuncSig(uint32_t funcIndex, uint32_t sigIndex)
|
||||
{
|
||||
MOZ_ASSERT(!shared_->funcSigs[funcIndex]);
|
||||
shared_->funcSigs[funcIndex] = &shared_->sigs[sigIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
const DeclaredSig&
|
||||
ModuleGenerator::funcSig(uint32_t funcIndex) const
|
||||
{
|
||||
MOZ_ASSERT(shared_->funcSigs[funcIndex]);
|
||||
return *shared_->funcSigs[funcIndex];
|
||||
}
|
||||
|
||||
bool
|
||||
ModuleGenerator::startFunc(PropertyName* name, unsigned line, unsigned column,
|
||||
UniqueBytecode* recycled, FunctionGenerator* fg)
|
||||
|
@ -384,21 +406,21 @@ ModuleGenerator::startFunc(PropertyName* name, unsigned line, unsigned column,
|
|||
}
|
||||
|
||||
bool
|
||||
ModuleGenerator::finishFunc(uint32_t funcIndex, const LifoSig& sig, UniqueBytecode bytecode,
|
||||
unsigned generateTime, FunctionGenerator* fg)
|
||||
ModuleGenerator::finishFunc(uint32_t funcIndex, UniqueBytecode bytecode, unsigned generateTime,
|
||||
FunctionGenerator* fg)
|
||||
{
|
||||
MOZ_ASSERT(activeFunc_ == fg);
|
||||
|
||||
UniqueFuncBytecode func = cx_->make_unique<FuncBytecode>(fg->name_,
|
||||
fg->line_,
|
||||
fg->column_,
|
||||
Move(fg->callSourceCoords_),
|
||||
funcIndex,
|
||||
sig,
|
||||
Move(bytecode),
|
||||
Move(fg->localVars_),
|
||||
generateTime
|
||||
);
|
||||
UniqueFuncBytecode func =
|
||||
js::MakeUnique<FuncBytecode>(fg->name_,
|
||||
fg->line_,
|
||||
fg->column_,
|
||||
Move(fg->callSourceCoords_),
|
||||
funcIndex,
|
||||
funcSig(funcIndex),
|
||||
Move(bytecode),
|
||||
Move(fg->localVars_),
|
||||
generateTime);
|
||||
if (!func)
|
||||
return false;
|
||||
|
||||
|
|
|
@ -28,9 +28,11 @@ namespace js {
|
|||
namespace wasm {
|
||||
|
||||
class FunctionGenerator;
|
||||
typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
|
||||
|
||||
// A slow function describes a function that took longer than msThreshold to
|
||||
// validate and compile.
|
||||
|
||||
struct SlowFunction
|
||||
{
|
||||
SlowFunction(PropertyName* name, unsigned ms, unsigned line, unsigned column)
|
||||
|
@ -46,53 +48,96 @@ struct SlowFunction
|
|||
};
|
||||
typedef Vector<SlowFunction> SlowFunctionVector;
|
||||
|
||||
// The ModuleGeneratorData holds all the state shared between the
|
||||
// ModuleGenerator and ModuleGeneratorThreadView. The ModuleGeneratorData is
|
||||
// encapsulated by ModuleGenerator/ModuleGeneratorThreadView classes which
|
||||
// present a race-free interface to the code in each thread assuming any given
|
||||
// element is initialized by the ModuleGenerator thread before an index to that
|
||||
// element is written to Bytecode sent to a ModuleGeneratorThreadView thread.
|
||||
// Once created, the Vectors are never resized.
|
||||
|
||||
struct ModuleImportGeneratorData
|
||||
{
|
||||
DeclaredSig* sig;
|
||||
uint32_t globalDataOffset;
|
||||
};
|
||||
|
||||
typedef Vector<ModuleImportGeneratorData, 0, SystemAllocPolicy> ModuleImportGeneratorDataVector;
|
||||
|
||||
struct ModuleGeneratorData
|
||||
{
|
||||
DeclaredSigVector sigs;
|
||||
DeclaredSigPtrVector funcSigs;
|
||||
ModuleImportGeneratorDataVector imports;
|
||||
};
|
||||
|
||||
typedef UniquePtr<ModuleGeneratorData> UniqueModuleGeneratorData;
|
||||
|
||||
// The ModuleGeneratorThreadView class presents a restricted, read-only view of
|
||||
// the shared state needed by helper threads. There is only one
|
||||
// ModuleGeneratorThreadView object owned by ModuleGenerator and referenced by
|
||||
// all compile tasks.
|
||||
|
||||
class ModuleGeneratorThreadView
|
||||
{
|
||||
const ModuleGeneratorData& shared_;
|
||||
|
||||
public:
|
||||
explicit ModuleGeneratorThreadView(const ModuleGeneratorData& shared)
|
||||
: shared_(shared)
|
||||
{}
|
||||
const DeclaredSig& sig(uint32_t sigIndex) const {
|
||||
return shared_.sigs[sigIndex];
|
||||
}
|
||||
const DeclaredSig& funcSig(uint32_t funcIndex) const {
|
||||
MOZ_ASSERT(shared_.funcSigs[funcIndex]);
|
||||
return *shared_.funcSigs[funcIndex];
|
||||
}
|
||||
const ModuleImportGeneratorData& import(uint32_t importIndex) const {
|
||||
MOZ_ASSERT(shared_.imports[importIndex].sig);
|
||||
return shared_.imports[importIndex];
|
||||
}
|
||||
};
|
||||
|
||||
// A ModuleGenerator encapsulates the creation of a wasm module. During the
|
||||
// lifetime of a ModuleGenerator, a sequence of FunctionGenerators are created
|
||||
// and destroyed to compile the individual function bodies. After generating all
|
||||
// functions, ModuleGenerator::finish() must be called to complete the
|
||||
// compilation and extract the resulting wasm module.
|
||||
|
||||
class MOZ_STACK_CLASS ModuleGenerator
|
||||
{
|
||||
typedef Vector<uint32_t> FuncOffsetVector;
|
||||
typedef Vector<uint32_t> FuncIndexVector;
|
||||
typedef UniquePtr<ModuleGeneratorThreadView> UniqueModuleGeneratorThreadView;
|
||||
typedef HashMap<uint32_t, uint32_t> FuncIndexMap;
|
||||
|
||||
struct SigHashPolicy
|
||||
{
|
||||
typedef const MallocSig& Lookup;
|
||||
static HashNumber hash(Lookup l) { return l.hash(); }
|
||||
static bool match(const LifoSig* lhs, Lookup rhs) { return *lhs == rhs; }
|
||||
};
|
||||
typedef HashSet<const LifoSig*, SigHashPolicy> SigSet;
|
||||
|
||||
ExclusiveContext* cx_;
|
||||
ExclusiveContext* cx_;
|
||||
jit::JitContext jcx_;
|
||||
|
||||
// Data handed back to the caller in finish()
|
||||
UniqueModuleData module_;
|
||||
UniqueStaticLinkData link_;
|
||||
SlowFunctionVector slowFuncs_;
|
||||
UniqueModuleData module_;
|
||||
UniqueStaticLinkData link_;
|
||||
SlowFunctionVector slowFuncs_;
|
||||
|
||||
// Data scoped to the ModuleGenerator's lifetime
|
||||
LifoAlloc lifo_;
|
||||
jit::JitContext jcx_;
|
||||
jit::TempAllocator alloc_;
|
||||
jit::MacroAssembler masm_;
|
||||
SigSet sigs_;
|
||||
FuncOffsetVector funcEntryOffsets_;
|
||||
FuncIndexVector exportFuncIndices_;
|
||||
FuncIndexMap funcIndexToExport_;
|
||||
UniqueModuleGeneratorData shared_;
|
||||
LifoAlloc lifo_;
|
||||
jit::TempAllocator alloc_;
|
||||
jit::MacroAssembler masm_;
|
||||
Uint32Vector funcEntryOffsets_;
|
||||
Uint32Vector exportFuncIndices_;
|
||||
FuncIndexMap funcIndexToExport_;
|
||||
|
||||
// Parallel compilation
|
||||
bool parallel_;
|
||||
uint32_t outstanding_;
|
||||
Vector<IonCompileTask> tasks_;
|
||||
Vector<IonCompileTask*> freeTasks_;
|
||||
bool parallel_;
|
||||
uint32_t outstanding_;
|
||||
UniqueModuleGeneratorThreadView threadView_;
|
||||
Vector<IonCompileTask> tasks_;
|
||||
Vector<IonCompileTask*> freeTasks_;
|
||||
|
||||
// Assertions
|
||||
DebugOnly<FunctionGenerator*> activeFunc_;
|
||||
DebugOnly<bool> finishedFuncs_;
|
||||
DebugOnly<FunctionGenerator*> activeFunc_;
|
||||
DebugOnly<bool> finishedFuncs_;
|
||||
|
||||
bool allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* globalDataOffset);
|
||||
bool finishOutstandingTask();
|
||||
bool finishTask(IonCompileTask* task);
|
||||
|
||||
|
@ -100,36 +145,40 @@ class MOZ_STACK_CLASS ModuleGenerator
|
|||
explicit ModuleGenerator(ExclusiveContext* cx);
|
||||
~ModuleGenerator();
|
||||
|
||||
bool init();
|
||||
bool init(UniqueModuleGeneratorData shared);
|
||||
|
||||
CompileArgs args() const { return module_->compileArgs; }
|
||||
jit::MacroAssembler& masm() { return masm_; }
|
||||
const FuncOffsetVector& funcEntryOffsets() const { return funcEntryOffsets_; }
|
||||
|
||||
const LifoSig* newLifoSig(const MallocSig& sig);
|
||||
const Uint32Vector& funcEntryOffsets() const { return funcEntryOffsets_; }
|
||||
|
||||
// Global data:
|
||||
bool allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* globalDataOffset);
|
||||
bool allocateGlobalVar(ValType type, uint32_t* globalDataOffset);
|
||||
|
||||
// Signatures:
|
||||
void initSig(uint32_t sigIndex, Sig&& sig);
|
||||
const DeclaredSig& sig(uint32_t sigIndex) const;
|
||||
|
||||
// Imports:
|
||||
bool declareImport(MallocSig&& sig, uint32_t* index);
|
||||
bool initImport(uint32_t importIndex, uint32_t sigIndex, uint32_t globalDataOffset);
|
||||
uint32_t numImports() const;
|
||||
uint32_t importExitGlobalDataOffset(uint32_t index) const;
|
||||
const MallocSig& importSig(uint32_t index) const;
|
||||
const ModuleImportGeneratorData& import(uint32_t index) const;
|
||||
bool defineImport(uint32_t index, ProfilingOffsets interpExit, ProfilingOffsets jitExit);
|
||||
|
||||
// Exports:
|
||||
bool declareExport(MallocSig&& sig, uint32_t funcIndex, uint32_t* exportIndex);
|
||||
bool declareExport(uint32_t funcIndex, uint32_t* exportIndex);
|
||||
uint32_t numExports() const;
|
||||
uint32_t exportFuncIndex(uint32_t index) const;
|
||||
const MallocSig& exportSig(uint32_t index) const;
|
||||
const Sig& exportSig(uint32_t index) const;
|
||||
bool defineExport(uint32_t index, Offsets offsets);
|
||||
|
||||
// Functions:
|
||||
bool initFuncSig(uint32_t funcIndex, uint32_t sigIndex);
|
||||
const DeclaredSig& funcSig(uint32_t funcIndex) const;
|
||||
bool startFunc(PropertyName* name, unsigned line, unsigned column, UniqueBytecode* recycled,
|
||||
FunctionGenerator* fg);
|
||||
bool finishFunc(uint32_t funcIndex, const LifoSig& sig, UniqueBytecode bytecode,
|
||||
unsigned generateTime, FunctionGenerator* fg);
|
||||
bool finishFunc(uint32_t funcIndex, UniqueBytecode bytecode, unsigned generateTime,
|
||||
FunctionGenerator* fg);
|
||||
bool finishFuncs();
|
||||
|
||||
// Function-pointer tables:
|
||||
|
@ -160,6 +209,7 @@ class MOZ_STACK_CLASS ModuleGenerator
|
|||
// anything else. After the body is complete, ModuleGenerator::finishFunc must
|
||||
// be called before the FunctionGenerator is destroyed and the next function is
|
||||
// started.
|
||||
|
||||
class MOZ_STACK_CLASS FunctionGenerator
|
||||
{
|
||||
friend class ModuleGenerator;
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -25,6 +25,12 @@
|
|||
namespace js {
|
||||
namespace wasm {
|
||||
|
||||
class ModuleGeneratorThreadView;
|
||||
|
||||
typedef Vector<jit::MIRType, 8, SystemAllocPolicy> MIRTypeVector;
|
||||
typedef jit::ABIArgIter<MIRTypeVector> ABIArgMIRTypeIter;
|
||||
typedef jit::ABIArgIter<ValTypeVector> ABIArgValTypeIter;
|
||||
|
||||
// The FuncCompileResults contains the results of compiling a single function
|
||||
// body, ready to be merged into the whole-module MacroAssembler.
|
||||
class FuncCompileResults
|
||||
|
@ -62,6 +68,7 @@ class IonCompileTask
|
|||
{
|
||||
JSRuntime* const runtime_;
|
||||
const CompileArgs args_;
|
||||
ModuleGeneratorThreadView& mg_;
|
||||
LifoAlloc lifo_;
|
||||
UniqueFuncBytecode func_;
|
||||
mozilla::Maybe<FuncCompileResults> results_;
|
||||
|
@ -70,9 +77,10 @@ class IonCompileTask
|
|||
IonCompileTask& operator=(const IonCompileTask&) = delete;
|
||||
|
||||
public:
|
||||
IonCompileTask(JSRuntime* runtime, CompileArgs args, size_t defaultChunkSize)
|
||||
: runtime_(runtime),
|
||||
IonCompileTask(JSRuntime* rt, CompileArgs args, ModuleGeneratorThreadView& mg, size_t defaultChunkSize)
|
||||
: runtime_(rt),
|
||||
args_(args),
|
||||
mg_(mg),
|
||||
lifo_(defaultChunkSize),
|
||||
func_(nullptr)
|
||||
{}
|
||||
|
@ -85,6 +93,9 @@ class IonCompileTask
|
|||
CompileArgs args() const {
|
||||
return args_;
|
||||
}
|
||||
ModuleGeneratorThreadView& mg() const {
|
||||
return mg_;
|
||||
}
|
||||
void init(UniqueFuncBytecode func) {
|
||||
MOZ_ASSERT(!func_);
|
||||
func_ = mozilla::Move(func);
|
||||
|
|
|
@ -243,14 +243,14 @@ StaticLinkData::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
|
|||
}
|
||||
|
||||
static size_t
|
||||
SerializedSigSize(const MallocSig& sig)
|
||||
SerializedSigSize(const Sig& sig)
|
||||
{
|
||||
return sizeof(ExprType) +
|
||||
SerializedPodVectorSize(sig.args());
|
||||
}
|
||||
|
||||
static uint8_t*
|
||||
SerializeSig(uint8_t* cursor, const MallocSig& sig)
|
||||
SerializeSig(uint8_t* cursor, const Sig& sig)
|
||||
{
|
||||
cursor = WriteScalar<ExprType>(cursor, sig.ret());
|
||||
cursor = SerializePodVector(cursor, sig.args());
|
||||
|
@ -258,33 +258,22 @@ SerializeSig(uint8_t* cursor, const MallocSig& sig)
|
|||
}
|
||||
|
||||
static const uint8_t*
|
||||
DeserializeSig(ExclusiveContext* cx, const uint8_t* cursor, MallocSig* sig)
|
||||
DeserializeSig(ExclusiveContext* cx, const uint8_t* cursor, Sig* sig)
|
||||
{
|
||||
ExprType ret;
|
||||
cursor = ReadScalar<ExprType>(cursor, &ret);
|
||||
|
||||
MallocSig::ArgVector args;
|
||||
ValTypeVector args;
|
||||
cursor = DeserializePodVector(cx, cursor, &args);
|
||||
if (!cursor)
|
||||
return nullptr;
|
||||
|
||||
sig->init(Move(args), ret);
|
||||
*sig = Sig(Move(args), ret);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
static bool
|
||||
CloneSig(JSContext* cx, const MallocSig& sig, MallocSig* out)
|
||||
{
|
||||
MallocSig::ArgVector args;
|
||||
if (!ClonePodVector(cx, sig.args(), &args))
|
||||
return false;
|
||||
|
||||
out->init(Move(args), sig.ret());
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t
|
||||
SizeOfSigExcludingThis(const MallocSig& sig, MallocSizeOf mallocSizeOf)
|
||||
SizeOfSigExcludingThis(const Sig& sig, MallocSizeOf mallocSizeOf)
|
||||
{
|
||||
return sig.args().sizeOfExcludingThis(mallocSizeOf);
|
||||
}
|
||||
|
@ -316,7 +305,7 @@ bool
|
|||
Export::clone(JSContext* cx, Export* out) const
|
||||
{
|
||||
out->pod = pod;
|
||||
return CloneSig(cx, sig_, &out->sig_);
|
||||
return out->sig_.clone(sig_);
|
||||
}
|
||||
|
||||
size_t
|
||||
|
@ -352,7 +341,7 @@ bool
|
|||
Import::clone(JSContext* cx, Import* out) const
|
||||
{
|
||||
out->pod = pod;
|
||||
return CloneSig(cx, sig_, &out->sig_);
|
||||
return out->sig_.clone(sig_);
|
||||
}
|
||||
|
||||
size_t
|
||||
|
|
|
@ -103,14 +103,14 @@ typedef UniquePtr<StaticLinkData> UniqueStaticLinkData;
|
|||
|
||||
class Export
|
||||
{
|
||||
MallocSig sig_;
|
||||
Sig sig_;
|
||||
struct CacheablePod {
|
||||
uint32_t stubOffset_;
|
||||
} pod;
|
||||
|
||||
public:
|
||||
Export() = default;
|
||||
explicit Export(MallocSig&& sig)
|
||||
explicit Export(Sig&& sig)
|
||||
: sig_(Move(sig))
|
||||
{
|
||||
pod.stubOffset_ = UINT32_MAX;
|
||||
|
@ -128,7 +128,7 @@ class Export
|
|||
uint32_t stubOffset() const {
|
||||
return pod.stubOffset_;
|
||||
}
|
||||
const MallocSig& sig() const {
|
||||
const Sig& sig() const {
|
||||
return sig_;
|
||||
}
|
||||
|
||||
|
@ -143,7 +143,7 @@ typedef Vector<Export, 0, SystemAllocPolicy> ExportVector;
|
|||
|
||||
class Import
|
||||
{
|
||||
MallocSig sig_;
|
||||
Sig sig_;
|
||||
struct CacheablePod {
|
||||
uint32_t exitGlobalDataOffset_;
|
||||
uint32_t interpExitCodeOffset_;
|
||||
|
@ -153,7 +153,7 @@ class Import
|
|||
public:
|
||||
Import() {}
|
||||
Import(Import&& rhs) : sig_(Move(rhs.sig_)), pod(rhs.pod) {}
|
||||
Import(MallocSig&& sig, uint32_t exitGlobalDataOffset)
|
||||
Import(Sig&& sig, uint32_t exitGlobalDataOffset)
|
||||
: sig_(Move(sig))
|
||||
{
|
||||
pod.exitGlobalDataOffset_ = exitGlobalDataOffset;
|
||||
|
@ -170,7 +170,7 @@ class Import
|
|||
pod.jitExitCodeOffset_ = off;
|
||||
}
|
||||
|
||||
const MallocSig& sig() const {
|
||||
const Sig& sig() const {
|
||||
return sig_;
|
||||
}
|
||||
uint32_t exitGlobalDataOffset() const {
|
||||
|
|
|
@ -30,10 +30,6 @@ using namespace js::wasm;
|
|||
using mozilla::ArrayLength;
|
||||
using mozilla::MakeEnumeratedRange;
|
||||
|
||||
typedef Vector<MIRType, 8, SystemAllocPolicy> MIRTypeVector;
|
||||
typedef ABIArgIter<MIRTypeVector> ABIArgMIRTypeIter;
|
||||
typedef ABIArgIter<MallocSig::ArgVector> ABIArgValTypeIter;
|
||||
|
||||
static void
|
||||
AssertStackAlignment(MacroAssembler& masm, uint32_t alignment, uint32_t addBeforeAssert = 0)
|
||||
{
|
||||
|
@ -102,7 +98,7 @@ static bool
|
|||
GenerateEntry(ModuleGenerator& mg, unsigned exportIndex, bool usesHeap)
|
||||
{
|
||||
MacroAssembler& masm = mg.masm();
|
||||
const MallocSig& sig = mg.exportSig(exportIndex);
|
||||
const Sig& sig = mg.exportSig(exportIndex);
|
||||
|
||||
masm.haltingAlign(CodeAlignment);
|
||||
|
||||
|
@ -293,7 +289,7 @@ GenerateEntry(ModuleGenerator& mg, unsigned exportIndex, bool usesHeap)
|
|||
}
|
||||
|
||||
static void
|
||||
FillArgumentArray(MacroAssembler& masm, const MallocSig::ArgVector& args, unsigned argOffset,
|
||||
FillArgumentArray(MacroAssembler& masm, const ValTypeVector& args, unsigned argOffset,
|
||||
unsigned offsetToCallerStackArgs, Register scratch)
|
||||
{
|
||||
for (ABIArgValTypeIter i(args); !i.done(); i++) {
|
||||
|
@ -340,7 +336,7 @@ GenerateInterpExitStub(ModuleGenerator& mg, unsigned importIndex, Label* throwLa
|
|||
ProfilingOffsets* offsets)
|
||||
{
|
||||
MacroAssembler& masm = mg.masm();
|
||||
const MallocSig& sig = mg.importSig(importIndex);
|
||||
const Sig& sig = *mg.import(importIndex).sig;
|
||||
|
||||
masm.setFramePushed(0);
|
||||
|
||||
|
@ -445,7 +441,7 @@ GenerateJitExitStub(ModuleGenerator& mg, unsigned importIndex, bool usesHeap,
|
|||
Label* throwLabel, ProfilingOffsets* offsets)
|
||||
{
|
||||
MacroAssembler& masm = mg.masm();
|
||||
const MallocSig& sig = mg.importSig(importIndex);
|
||||
const Sig& sig = *mg.import(importIndex).sig;
|
||||
|
||||
masm.setFramePushed(0);
|
||||
|
||||
|
@ -475,7 +471,7 @@ GenerateJitExitStub(ModuleGenerator& mg, unsigned importIndex, bool usesHeap,
|
|||
Register scratch = ABIArgGenerator::NonArgReturnReg1; // repeatedly clobbered
|
||||
|
||||
// 2.1. Get ExitDatum
|
||||
unsigned globalDataOffset = mg.importExitGlobalDataOffset(importIndex);
|
||||
unsigned globalDataOffset = mg.import(importIndex).globalDataOffset;
|
||||
#if defined(JS_CODEGEN_X64)
|
||||
masm.append(AsmJSGlobalAccess(masm.leaRipRelative(callee), globalDataOffset));
|
||||
#elif defined(JS_CODEGEN_X86)
|
||||
|
|
|
@ -57,18 +57,14 @@ enum class ValType
|
|||
B32x4
|
||||
};
|
||||
|
||||
typedef Vector<ValType, 8, SystemAllocPolicy> ValTypeVector;
|
||||
|
||||
static inline bool
|
||||
IsSimdType(ValType vt)
|
||||
{
|
||||
return vt == ValType::I32x4 || vt == ValType::F32x4 || vt == ValType::B32x4;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
IsSimdBoolType(ValType vt)
|
||||
{
|
||||
return vt == ValType::B32x4;
|
||||
}
|
||||
|
||||
static inline jit::MIRType
|
||||
ToMIRType(ValType vt)
|
||||
{
|
||||
|
@ -179,6 +175,12 @@ IsSimdType(ExprType et)
|
|||
return IsVoid(et) ? false : IsSimdType(ValType(et));
|
||||
}
|
||||
|
||||
static inline bool
|
||||
IsSimdBoolType(ExprType vt)
|
||||
{
|
||||
return vt == ExprType::B32x4;
|
||||
}
|
||||
|
||||
static inline jit::MIRType
|
||||
ToMIRType(ExprType et)
|
||||
{
|
||||
|
@ -195,30 +197,32 @@ ToMIRType(ExprType et)
|
|||
// duration of module validation+compilation). Thus, long-lived objects like
|
||||
// WasmModule must use malloced allocation.
|
||||
|
||||
template <class AllocPolicy>
|
||||
class Sig
|
||||
{
|
||||
public:
|
||||
typedef Vector<ValType, 4, AllocPolicy> ArgVector;
|
||||
|
||||
private:
|
||||
ArgVector args_;
|
||||
ValTypeVector args_;
|
||||
ExprType ret_;
|
||||
|
||||
protected:
|
||||
explicit Sig(AllocPolicy alloc = AllocPolicy()) : args_(alloc) {}
|
||||
Sig(Sig&& rhs) : args_(Move(rhs.args_)), ret_(rhs.ret_) {}
|
||||
Sig(ArgVector&& args, ExprType ret) : args_(Move(args)), ret_(ret) {}
|
||||
Sig(const Sig&) = delete;
|
||||
Sig& operator=(const Sig&) = delete;
|
||||
|
||||
public:
|
||||
void init(ArgVector&& args, ExprType ret) {
|
||||
Sig() : args_(), ret_(ExprType::Void) {}
|
||||
Sig(Sig&& rhs) : args_(Move(rhs.args_)), ret_(rhs.ret_) {}
|
||||
Sig(ValTypeVector&& args, ExprType ret) : args_(Move(args)), ret_(ret) {}
|
||||
|
||||
bool clone(const Sig& rhs) {
|
||||
ret_ = rhs.ret_;
|
||||
MOZ_ASSERT(args_.empty());
|
||||
args_ = Move(args);
|
||||
ret_ = ret;
|
||||
return args_.appendAll(rhs.args_);
|
||||
}
|
||||
Sig& operator=(Sig&& rhs) {
|
||||
ret_ = rhs.ret_;
|
||||
args_ = Move(rhs.args_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ValType arg(unsigned i) const { return args_[i]; }
|
||||
const ArgVector& args() const { return args_; }
|
||||
const ValTypeVector& args() const { return args_; }
|
||||
const ExprType& ret() const { return ret_; }
|
||||
|
||||
HashNumber hash() const {
|
||||
|
@ -227,9 +231,7 @@ class Sig
|
|||
hn = mozilla::AddToHash(hn, HashNumber(args_[i]));
|
||||
return hn;
|
||||
}
|
||||
|
||||
template <class AllocPolicy2>
|
||||
bool operator==(const Sig<AllocPolicy2>& rhs) const {
|
||||
bool operator==(const Sig& rhs) const {
|
||||
if (ret() != rhs.ret())
|
||||
return false;
|
||||
if (args().length() != rhs.args().length())
|
||||
|
@ -240,39 +242,27 @@ class Sig
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class AllocPolicy2>
|
||||
bool operator!=(const Sig<AllocPolicy2>& rhs) const {
|
||||
bool operator!=(const Sig& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
class MallocSig : public Sig<SystemAllocPolicy>
|
||||
{
|
||||
typedef Sig<SystemAllocPolicy> BaseSig;
|
||||
// A "declared" signature is a Sig object that is created and owned by the
|
||||
// ModuleGenerator. These signature objects are read-only and have the same
|
||||
// lifetime as the ModuleGenerator. This type is useful since some uses of Sig
|
||||
// need this extended lifetime and want to statically distinguish from the
|
||||
// common stack-allocated Sig objects that get passed around.
|
||||
|
||||
public:
|
||||
MallocSig() = default;
|
||||
MallocSig(MallocSig&& rhs) : BaseSig(Move(rhs)) {}
|
||||
MallocSig(ArgVector&& args, ExprType ret) : BaseSig(Move(args), ret) {}
|
||||
struct DeclaredSig : Sig
|
||||
{
|
||||
DeclaredSig() = default;
|
||||
DeclaredSig(DeclaredSig&& rhs) : Sig(Move(rhs)) {}
|
||||
explicit DeclaredSig(Sig&& sig) : Sig(Move(sig)) {}
|
||||
void operator=(Sig&& rhs) { Sig& base = *this; base = Move(rhs); }
|
||||
};
|
||||
|
||||
class LifoSig : public Sig<LifoAllocPolicy<Fallible>>
|
||||
{
|
||||
typedef Sig<LifoAllocPolicy<Fallible>> BaseSig;
|
||||
LifoSig(ArgVector&& args, ExprType ret) : BaseSig(Move(args), ret) {}
|
||||
|
||||
public:
|
||||
static LifoSig* new_(LifoAlloc& lifo, const MallocSig& src) {
|
||||
void* mem = lifo.alloc(sizeof(LifoSig));
|
||||
if (!mem)
|
||||
return nullptr;
|
||||
ArgVector args(lifo);
|
||||
if (!args.appendAll(src.args()))
|
||||
return nullptr;
|
||||
return new (mem) LifoSig(Move(args), src.ret());
|
||||
}
|
||||
};
|
||||
typedef Vector<DeclaredSig, 0, SystemAllocPolicy> DeclaredSigVector;
|
||||
typedef Vector<const DeclaredSig*, 0, SystemAllocPolicy> DeclaredSigPtrVector;
|
||||
|
||||
// The (,Profiling,Func)Offsets classes are used to record the offsets of
|
||||
// different key points in a CodeRange during compilation.
|
||||
|
|
|
@ -270,7 +270,7 @@ EvalKernel(JSContext* cx, const CallArgs& args, EvalType evalType, AbstractFrame
|
|||
|
||||
EvalScriptGuard esg(cx);
|
||||
|
||||
if (evalType == DIRECT_EVAL && caller.isNonEvalFunctionFrame())
|
||||
if (evalType == DIRECT_EVAL && caller.isFunctionFrame())
|
||||
esg.lookupInEvalCache(linearStr, callerScript, pc);
|
||||
|
||||
if (!esg.foundScript()) {
|
||||
|
|
|
@ -1607,7 +1607,7 @@ ShellObjectMetadataCallback(JSContext* cx, JSObject*)
|
|||
RootedId id(cx);
|
||||
RootedValue callee(cx);
|
||||
for (NonBuiltinScriptFrameIter iter(cx); !iter.done(); ++iter) {
|
||||
if (iter.isNonEvalFunctionFrame() && iter.compartment() == cx->compartment()) {
|
||||
if (iter.isFunctionFrame() && iter.compartment() == cx->compartment()) {
|
||||
id = INT_TO_JSID(stackIndex);
|
||||
RootedObject callee(cx, iter.callee(cx));
|
||||
if (!JS_DefinePropertyById(cx, stack, id, callee, 0,
|
||||
|
|
|
@ -1221,6 +1221,9 @@ for (var i = 1; i < 10; ++i) {
|
|||
asmLink(asmCompile('glob', 'ffi', c), this, ffi)();
|
||||
}
|
||||
|
||||
// Bug 1240524
|
||||
assertAsmTypeFail(USE_ASM + B32 + 'var x = b4(0, 0, 0, 0); frd(x);');
|
||||
|
||||
// Passing boolean results to extern functions.
|
||||
// Verify that these functions are typed correctly.
|
||||
function isone(x) { return (x===1)|0 }
|
||||
|
|
|
@ -263,7 +263,7 @@ jit::EnsureHasScopeObjects(JSContext* cx, AbstractFramePtr fp)
|
|||
// Ion does not compile eval scripts.
|
||||
MOZ_ASSERT(!fp.isEvalFrame());
|
||||
|
||||
if (fp.isNonEvalFunctionFrame() &&
|
||||
if (fp.isFunctionFrame() &&
|
||||
fp.callee()->needsCallObject() &&
|
||||
!fp.hasCallObj())
|
||||
{
|
||||
|
|
|
@ -1723,7 +1723,7 @@ CopyFromRematerializedFrame(JSContext* cx, JitActivation* act, uint8_t* fp, size
|
|||
|
||||
frame->setScopeChain(rematFrame->scopeChain());
|
||||
|
||||
if (frame->isNonEvalFunctionFrame())
|
||||
if (frame->isFunctionFrame())
|
||||
frame->thisArgument() = rematFrame->thisArgument();
|
||||
|
||||
for (unsigned i = 0; i < frame->numActualArgs(); i++)
|
||||
|
|
|
@ -357,13 +357,7 @@ BaselineCompiler::emitPrologue()
|
|||
// is passed in R1, so we have to be careful not to clobber it.
|
||||
|
||||
// Initialize BaselineFrame::flags.
|
||||
uint32_t flags = 0;
|
||||
if (script->isForEval())
|
||||
flags |= BaselineFrame::EVAL;
|
||||
masm.store32(Imm32(flags), frame.addressOfFlags());
|
||||
|
||||
if (script->isForEval())
|
||||
masm.storePtr(ImmGCPtr(script), frame.addressOfEvalScript());
|
||||
masm.store32(Imm32(0), frame.addressOfFlags());
|
||||
|
||||
// Handle scope chain pre-initialization (in case GC gets run
|
||||
// during stack check). For global and eval scripts, the scope
|
||||
|
|
|
@ -33,7 +33,7 @@ BaselineFrame::trace(JSTracer* trc, JitFrameIterator& frameIterator)
|
|||
replaceCalleeToken(MarkCalleeToken(trc, calleeToken()));
|
||||
|
||||
// Mark |this|, actual and formal args.
|
||||
if (isNonEvalFunctionFrame()) {
|
||||
if (isFunctionFrame()) {
|
||||
TraceRoot(trc, &thisArgument(), "baseline-this");
|
||||
|
||||
unsigned numArgs = js::Max(numActualArgs(), numFormalArgs());
|
||||
|
@ -48,11 +48,8 @@ BaselineFrame::trace(JSTracer* trc, JitFrameIterator& frameIterator)
|
|||
if (hasReturnValue())
|
||||
TraceRoot(trc, returnValue().address(), "baseline-rval");
|
||||
|
||||
if (isEvalFrame()) {
|
||||
TraceRoot(trc, &evalScript_, "baseline-evalscript");
|
||||
if (script()->isDirectEvalInFunction())
|
||||
TraceRoot(trc, evalNewTargetAddress(), "baseline-evalNewTarget");
|
||||
}
|
||||
if (isEvalFrame() && script()->isDirectEvalInFunction())
|
||||
TraceRoot(trc, evalNewTargetAddress(), "baseline-evalNewTarget");
|
||||
|
||||
if (hasArgsObj())
|
||||
TraceRoot(trc, &argsObj_, "baseline-args-obj");
|
||||
|
@ -126,7 +123,7 @@ BaselineFrame::initStrictEvalScopeObjects(JSContext* cx)
|
|||
bool
|
||||
BaselineFrame::initFunctionScopeObjects(JSContext* cx)
|
||||
{
|
||||
MOZ_ASSERT(isNonEvalFunctionFrame());
|
||||
MOZ_ASSERT(isFunctionFrame());
|
||||
MOZ_ASSERT(callee()->needsCallObject());
|
||||
|
||||
CallObject* callobj = CallObject::createForFunction(cx, this);
|
||||
|
@ -148,11 +145,6 @@ BaselineFrame::initForOsr(InterpreterFrame* fp, uint32_t numStackValues)
|
|||
if (fp->hasCallObjUnchecked())
|
||||
flags_ |= BaselineFrame::HAS_CALL_OBJ;
|
||||
|
||||
if (fp->isEvalFrame()) {
|
||||
flags_ |= BaselineFrame::EVAL;
|
||||
evalScript_ = fp->script();
|
||||
}
|
||||
|
||||
if (fp->script()->needsArgsObj() && fp->hasArgsObj()) {
|
||||
flags_ |= BaselineFrame::HAS_ARGS_OBJ;
|
||||
argsObj_ = &fp->argsObj();
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче