Merge m-c to inbound, a=merge

This commit is contained in:
Wes Kocher 2015-11-11 17:12:26 -08:00
commit e985043b20
65 changed files with 1716 additions and 538 deletions

View File

@ -15,10 +15,10 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54"> <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <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="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/> <project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>

View File

@ -15,10 +15,10 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54"> <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <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="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/> <project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>

View File

@ -19,10 +19,10 @@
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/> <project name="gaia.git" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/> <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cb4604d5a578efd027277059ce3e0f6e3af59bd1"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c72c9278ddc2f442d193474993d36e7f2cfb08c4"/> <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c72c9278ddc2f442d193474993d36e7f2cfb08c4"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/> <project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>

View File

@ -17,8 +17,8 @@
</project> </project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/> <project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f009c98ba697582c857c5788e5cdf0640e287ae6"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f009c98ba697582c857c5788e5cdf0640e287ae6"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>

View File

@ -15,9 +15,9 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54"> <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/> <project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
@ -127,12 +127,12 @@
<default remote="caf" revision="refs/tags/android-4.4.2_r1" sync-j="4"/> <default remote="caf" revision="refs/tags/android-4.4.2_r1" sync-j="4"/>
<!-- Emulator specific things --> <!-- Emulator specific things -->
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="72ffdf71c68a96309212eb13d63560d66db14c9e"/> <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="72ffdf71c68a96309212eb13d63560d66db14c9e"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="8d4018ebd33ac3f1a043b2d54bc578028656a659"/> <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="58800ecb50e4e41cfb0a36cb43c82b73fb3612e5"/>
<project name="platform_bionic" path="bionic" remote="b2g" revision="3e85c4683c121530c1c3a48c696a569bf5f587e2"/> <project name="platform_bionic" path="bionic" remote="b2g" revision="3e85c4683c121530c1c3a48c696a569bf5f587e2"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="5a50f96a1d7c788817abb7c57acbb75172c1f48d"/> <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="5a50f96a1d7c788817abb7c57acbb75172c1f48d"/>
<project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="f37bd545063039e30a92f2550ae78c0e6e4e2d08"/> <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="f37bd545063039e30a92f2550ae78c0e6e4e2d08"/>
<project name="platform_external_wpa_supplicant_8" path="external/wpa_supplicant_8" remote="b2g" revision="0c6a6547cd1fd302fa2b0f6e375654df36bf0ec4"/> <project name="platform_external_wpa_supplicant_8" path="external/wpa_supplicant_8" remote="b2g" revision="0c6a6547cd1fd302fa2b0f6e375654df36bf0ec4"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="aa763fa9180a222547824ae6b6064e4851c15a86"/> <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="29cbaa03a380ab69d47c476dd433059f7680837c"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5f4b68c799927b6e078f987b12722c3a6ccd4a45"/> <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5f4b68c799927b6e078f987b12722c3a6ccd4a45"/>
<project name="platform/development" path="development" revision="5968ff4e13e0d696ad8d972281fc27ae5a12829b"/> <project name="platform/development" path="development" revision="5968ff4e13e0d696ad8d972281fc27ae5a12829b"/>
<project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="6a1bb59af65b6485b1090522f66fac95c3f9e22c"/> <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="6a1bb59af65b6485b1090522f66fac95c3f9e22c"/>

View File

@ -15,9 +15,9 @@
<project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf"> <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/> <project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>

View File

@ -19,10 +19,10 @@
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/> <project name="gaia.git" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/> <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cb4604d5a578efd027277059ce3e0f6e3af59bd1"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c72c9278ddc2f442d193474993d36e7f2cfb08c4"/> <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c72c9278ddc2f442d193474993d36e7f2cfb08c4"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/> <project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>

View File

@ -15,10 +15,10 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54"> <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <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="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/> <project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>

View File

@ -1,9 +1,9 @@
{ {
"git": { "git": {
"git_revision": "22f8023b112dfae83531b0a075ab9eb9a5444dfa", "git_revision": "fdb66f75963fa9255f707af87f405d54892e5e7d",
"remote": "https://git.mozilla.org/releases/gaia.git", "remote": "https://git.mozilla.org/releases/gaia.git",
"branch": "" "branch": ""
}, },
"revision": "fbb1b70ce9fdf934ff09885898d9ce5fa9f87b57", "revision": "3b39cf1a2b4cc636b84a437475db0efa9bb96c00",
"repo_path": "integration/gaia-central" "repo_path": "integration/gaia-central"
} }

View File

@ -15,10 +15,10 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54"> <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <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="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/> <project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>

View File

@ -18,8 +18,8 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <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="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/> <project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f009c98ba697582c857c5788e5cdf0640e287ae6"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f009c98ba697582c857c5788e5cdf0640e287ae6"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>

View File

@ -15,10 +15,10 @@
<project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf"> <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <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="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/> <project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>

View File

@ -2836,8 +2836,7 @@ var BrowserOnClick = {
.getService(Ci.nsIWeakCryptoOverride); .getService(Ci.nsIWeakCryptoOverride);
weakCryptoOverride.addWeakCryptoOverride( weakCryptoOverride.addWeakCryptoOverride(
msg.data.location.hostname, msg.data.location.hostname,
PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser), PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser));
true /* temporary */);
break; break;
} }
}, },
@ -7140,6 +7139,7 @@ var gIdentityHandler = {
if (shouldHidePopup) { if (shouldHidePopup) {
this._identityPopup.hidePopup(); this._identityPopup.hidePopup();
} }
this.showWeakCryptoInfoBar();
// NOTE: We do NOT update the identity popup (the control center) when // NOTE: We do NOT update the identity popup (the control center) when
// we receive a new security state on the existing page (i.e. from a // we receive a new security state on the existing page (i.e. from a
@ -7288,6 +7288,55 @@ var gIdentityHandler = {
this._identityIconLabel.parentNode.collapsed = icon_label ? false : true; this._identityIconLabel.parentNode.collapsed = icon_label ? false : true;
}, },
/**
* Show the weak crypto notification bar.
*/
showWeakCryptoInfoBar() {
if (!this._uriHasHost || !this._isBroken || !this._sslStatus.cipherName ||
this._sslStatus.cipherName.indexOf("_RC4_") < 0) {
return;
}
let notificationBox = gBrowser.getNotificationBox();
let notification = notificationBox.getNotificationWithValue("weak-crypto");
if (notification) {
return;
}
let brandBundle = document.getElementById("bundle_brand");
let brandShortName = brandBundle.getString("brandShortName");
let message = gNavigatorBundle.getFormattedString("weakCryptoOverriding.message",
[brandShortName]);
let host = this._uri.host;
let port = 443;
try {
if (this._uri.port > 0) {
port = this._uri.port;
}
} catch (e) {}
let buttons = [{
label: gNavigatorBundle.getString("revokeOverride.label"),
accessKey: gNavigatorBundle.getString("revokeOverride.accesskey"),
callback: function (aNotification, aButton) {
try {
let weakCryptoOverride = Cc["@mozilla.org/security/weakcryptooverride;1"]
.getService(Ci.nsIWeakCryptoOverride);
weakCryptoOverride.removeWeakCryptoOverride(host, port,
PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser));
BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
} catch (e) {
Cu.reportError(e);
}
}
}];
const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
notificationBox.appendNotification(message, "weak-crypto", null,
priority, buttons);
},
/** /**
* Set up the title and content messages for the identity message popup, * Set up the title and content messages for the identity message popup,
* based on the specified mode, and the details of the SSL cert, where * based on the specified mode, and the details of the SSL cert, where

View File

@ -2,8 +2,7 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */ * http://creativecommons.org/publicdomain/zero/1.0/ */
// Load directly from the browser-chrome support files of login tests. // Load directly from the browser-chrome support files of login tests.
const testUrlPath = const TEST_URL_PATH = "/browser/toolkit/components/passwordmgr/test/browser/";
"://example.com/browser/toolkit/components/passwordmgr/test/browser/";
/** /**
* Waits for the given number of occurrences of InsecureLoginFormsStateChange * Waits for the given number of occurrences of InsecureLoginFormsStateChange
@ -22,8 +21,13 @@ add_task(function* test_simple() {
"set": [["security.insecure_password.ui.enabled", true]], "set": [["security.insecure_password.ui.enabled", true]],
}, resolve)); }, resolve));
for (let scheme of ["http", "https"]) { for (let [origin, expectWarning] of [
let tab = gBrowser.addTab(scheme + testUrlPath + "form_basic.html"); ["http://example.com", true],
["http://127.0.0.1", false],
["https://example.com", false],
]) {
let testUrlPath = origin + TEST_URL_PATH;
let tab = gBrowser.addTab(testUrlPath + "form_basic.html");
let browser = tab.linkedBrowser; let browser = tab.linkedBrowser;
yield Promise.all([ yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab), BrowserTestUtils.switchTab(gBrowser, tab),
@ -36,7 +40,7 @@ add_task(function* test_simple() {
gIdentityHandler._identityBox.click(); gIdentityHandler._identityBox.click();
document.getElementById("identity-popup-security-expander").click(); document.getElementById("identity-popup-security-expander").click();
if (scheme == "http") { if (expectWarning) {
let identityBoxImage = gBrowser.ownerGlobal let identityBoxImage = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("page-proxy-favicon"), "") .getComputedStyle(document.getElementById("page-proxy-favicon"), "")
.getPropertyValue("list-style-image"); .getPropertyValue("list-style-image");
@ -64,8 +68,8 @@ add_task(function* test_simple() {
// the scheme is HTTPS. // the scheme is HTTPS.
is(Array.every(document.querySelectorAll("[when-loginforms=insecure]"), is(Array.every(document.querySelectorAll("[when-loginforms=insecure]"),
element => !is_hidden(element)), element => !is_hidden(element)),
scheme == "http", expectWarning,
"The relevant messages should visible or hidden."); "The relevant messages should be visible or hidden.");
gIdentityHandler._identityPopup.hidden = true; gIdentityHandler._identityPopup.hidden = true;
gBrowser.removeTab(tab); gBrowser.removeTab(tab);
@ -82,6 +86,7 @@ add_task(function* test_mixedcontent() {
}, resolve)); }, resolve));
// Load the page with the subframe in a new tab. // Load the page with the subframe in a new tab.
let testUrlPath = "://example.com" + TEST_URL_PATH;
let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html"); let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
let browser = tab.linkedBrowser; let browser = tab.linkedBrowser;
yield Promise.all([ yield Promise.all([

View File

@ -391,8 +391,7 @@ html[dir="rtl"] .room-entry-context-actions > .dropdown-menu {
vertical-align: middle; vertical-align: middle;
} }
.room-entry-context-item > img { .room-entry-context-item > a > img {
height: 16px;
width: 16px; width: 16px;
} }

View File

@ -798,6 +798,11 @@ muteTab.accesskey = M
unmuteTab.label = Unmute Tab unmuteTab.label = Unmute Tab
unmuteTab.accesskey = M unmuteTab.accesskey = M
# LOCALIZATION NOTE (weakCryptoOverriding.message): %S is brandShortName
weakCryptoOverriding.message = %S recommends that you don't enter your password, credit card and other personal information on this website.
revokeOverride.label = Don't Trust This Website
revokeOverride.accesskey = D
# LOCALIZATION NOTE (tabgroups.deprecationwarning.description): # LOCALIZATION NOTE (tabgroups.deprecationwarning.description):
# %S is brandShortName # %S is brandShortName
tabgroups.deprecationwarning.description = Heads up! Tab Groups will be removed from %S soon. tabgroups.deprecationwarning.description = Heads up! Tab Groups will be removed from %S soon.

View File

@ -2,9 +2,8 @@
http://creativecommons.org/publicdomain/zero/1.0/ */ http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict"; "use strict";
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; // shared-head.js handles imports, constants, and utility functions
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
// Disable logging for faster test runs. Set this pref to true if you want to // Disable logging for faster test runs. Set this pref to true if you want to
// debug a test in your try runs. Both the debugger server and frontend will // debug a test in your try runs. Both the debugger server and frontend will
@ -12,31 +11,20 @@ var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log"); var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
Services.prefs.setBoolPref("devtools.debugger.log", false); Services.prefs.setBoolPref("devtools.debugger.log", false);
var { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
var { Promise: promise } = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {});
var { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {}); var { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
var { DebuggerServer } = require("devtools/server/main"); var { DebuggerServer } = require("devtools/server/main");
var { DebuggerClient, ObjectClient } = require("devtools/shared/client/main"); var { DebuggerClient, ObjectClient } = require("devtools/shared/client/main");
var { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {}); var { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
var EventEmitter = require("devtools/shared/event-emitter"); var EventEmitter = require("devtools/shared/event-emitter");
const { promiseInvoke } = require("devtools/shared/async-utils"); const { promiseInvoke } = require("devtools/shared/async-utils");
var { TargetFactory } = require("devtools/client/framework/target");
var { Toolbox } = require("devtools/client/framework/toolbox") var { Toolbox } = require("devtools/client/framework/toolbox")
// Override promise with deprecated-sync-thenables
promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
const EXAMPLE_URL = "http://example.com/browser/devtools/client/debugger/test/mochitest/"; const EXAMPLE_URL = "http://example.com/browser/devtools/client/debugger/test/mochitest/";
const FRAME_SCRIPT_URL = getRootDirectory(gTestPath) + "code_frame-script.js"; const FRAME_SCRIPT_URL = getRootDirectory(gTestPath) + "code_frame-script.js";
DevToolsUtils.testing = true;
SimpleTest.registerCleanupFunction(() => {
DevToolsUtils.testing = false;
});
// All tests are asynchronous.
waitForExplicitFinish();
registerCleanupFunction(function* () { registerCleanupFunction(function* () {
info("finish() was called, cleaning up..."); info("finish() was called, cleaning up...");
Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging); Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
@ -84,7 +72,9 @@ function getChromeWindow(aWindow) {
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
} }
function addTab(aUrl, aWindow) { // Override addTab/removeTab as defined by shared-head, since these have
// an extra window parameter and add a frame script
this.addTab = function addTab(aUrl, aWindow) {
info("Adding tab: " + aUrl); info("Adding tab: " + aUrl);
let deferred = promise.defer(); let deferred = promise.defer();
@ -107,7 +97,7 @@ function addTab(aUrl, aWindow) {
return deferred.promise; return deferred.promise;
} }
function removeTab(aTab, aWindow) { this.removeTab = function removeTab(aTab, aWindow) {
info("Removing tab."); info("Removing tab.");
let deferred = promise.defer(); let deferred = promise.defer();
@ -1215,30 +1205,3 @@ function getSplitConsole(toolbox, win) {
}); });
}); });
} }
// This can be removed once debugger uses shared-head.js (bug 1181838)
function synthesizeKeyFromKeyTag(key) {
is(key && key.tagName, "key", "Successfully retrieved the <key> node");
let modifiersAttr = key.getAttribute("modifiers");
let name = null;
if (key.getAttribute("keycode"))
name = key.getAttribute("keycode");
else if (key.getAttribute("key"))
name = key.getAttribute("key");
isnot(name, null, "Successfully retrieved keycode/key");
let modifiers = {
shiftKey: !!modifiersAttr.match("shift"),
ctrlKey: !!modifiersAttr.match("control"),
altKey: !!modifiersAttr.match("alt"),
metaKey: !!modifiersAttr.match("meta"),
accelKey: !!modifiersAttr.match("accel")
};
info("Synthesizing key " + name + " " + JSON.stringify(modifiers));
EventUtils.synthesizeKey(name, modifiers);
}

View File

@ -162,6 +162,12 @@ Selection.prototype = {
setNodeFront: function(value, reason="unknown") { setNodeFront: function(value, reason="unknown") {
this.reason = reason; this.reason = reason;
// If a singleTextChild text node is being set, then set it's parent instead.
let parentNode = value && value.parentNode();
if (value && parentNode && parentNode.singleTextChild === value) {
value = parentNode;
}
// We used to return here if the node had not changed but we now need to // We used to return here if the node had not changed but we now need to
// set the node even if it is already set otherwise it is not possible to // set the node even if it is already set otherwise it is not possible to
// e.g. highlight the same node twice. // e.g. highlight the same node twice.

View File

@ -20,7 +20,7 @@ const {require} = scopedCuImport("resource://devtools/shared/Loader.jsm");
const {TargetFactory} = require("devtools/client/framework/target"); const {TargetFactory} = require("devtools/client/framework/target");
const DevToolsUtils = require("devtools/shared/DevToolsUtils"); const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const promise = require("promise"); let promise = require("promise");
const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/")); const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
const CHROME_URL_ROOT = TEST_DIR + "/"; const CHROME_URL_ROOT = TEST_DIR + "/";

View File

@ -16,7 +16,7 @@ var {HostType} = require("devtools/client/framework/toolbox").Toolbox;
loader.lazyGetter(this, "MarkupView", () => require("devtools/client/markupview/markup-view").MarkupView); loader.lazyGetter(this, "MarkupView", () => require("devtools/client/markupview/markup-view").MarkupView);
loader.lazyGetter(this, "HTMLBreadcrumbs", () => require("devtools/client/inspector/breadcrumbs").HTMLBreadcrumbs); loader.lazyGetter(this, "HTMLBreadcrumbs", () => require("devtools/client/inspector/breadcrumbs").HTMLBreadcrumbs);
loader.lazyGetter(this, "ToolSidebar", () => require("devtools/client/framework/sidebar").ToolSidebar); loader.lazyGetter(this, "ToolSidebar", () => require("devtools/client/framework/sidebar").ToolSidebar);
loader.lazyGetter(this, "SelectorSearch", () => require("devtools/client/inspector/selector-search").SelectorSearch); loader.lazyGetter(this, "InspectorSearch", () => require("devtools/client/inspector/inspector-search").InspectorSearch);
loader.lazyGetter(this, "strings", () => { loader.lazyGetter(this, "strings", () => {
return Services.strings.createBundle("chrome://devtools/locale/inspector.properties"); return Services.strings.createBundle("chrome://devtools/locale/inspector.properties");
@ -78,7 +78,20 @@ function InspectorPanel(iframeWindow, toolbox) {
this.panelWin.inspector = this; this.panelWin.inspector = this;
this.nodeMenuTriggerInfo = null; this.nodeMenuTriggerInfo = null;
this._onBeforeNavigate = this._onBeforeNavigate.bind(this); this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
this.onNewRoot = this.onNewRoot.bind(this);
this._setupNodeMenu = this._setupNodeMenu.bind(this);
this._resetNodeMenu = this._resetNodeMenu.bind(this);
this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this);
this.onNewSelection = this.onNewSelection.bind(this);
this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
this.onDetached = this.onDetached.bind(this);
this.onToolboxHostChanged = this.onToolboxHostChanged.bind(this);
this.scheduleLayoutChange = this.scheduleLayoutChange.bind(this);
this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
this._target.on("will-navigate", this._onBeforeNavigate); this._target.on("will-navigate", this._onBeforeNavigate);
EventEmitter.decorate(this); EventEmitter.decorate(this);
@ -139,31 +152,23 @@ InspectorPanel.prototype = {
_deferredOpen: function(defaultSelection) { _deferredOpen: function(defaultSelection) {
let deferred = promise.defer(); let deferred = promise.defer();
this.onNewRoot = this.onNewRoot.bind(this);
this.walker.on("new-root", this.onNewRoot); this.walker.on("new-root", this.onNewRoot);
this.nodemenu = this.panelDoc.getElementById("inspector-node-popup"); this.nodemenu = this.panelDoc.getElementById("inspector-node-popup");
this.lastNodemenuItem = this.nodemenu.lastChild; this.lastNodemenuItem = this.nodemenu.lastChild;
this._setupNodeMenu = this._setupNodeMenu.bind(this);
this._resetNodeMenu = this._resetNodeMenu.bind(this);
this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true); this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true); this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
this.onNewSelection = this.onNewSelection.bind(this);
this.selection.on("new-node-front", this.onNewSelection); this.selection.on("new-node-front", this.onNewSelection);
this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
this.selection.on("before-new-node-front", this.onBeforeNewSelection); this.selection.on("before-new-node-front", this.onBeforeNewSelection);
this.onDetached = this.onDetached.bind(this);
this.selection.on("detached-front", this.onDetached); this.selection.on("detached-front", this.onDetached);
this.breadcrumbs = new HTMLBreadcrumbs(this); this.breadcrumbs = new HTMLBreadcrumbs(this);
this.onToolboxHostChanged = this.onToolboxHostChanged.bind(this);
this._toolbox.on("host-changed", this.onToolboxHostChanged); this._toolbox.on("host-changed", this.onToolboxHostChanged);
if (this.target.isLocalTab) { if (this.target.isLocalTab) {
this.browser = this.target.tab.linkedBrowser; this.browser = this.target.tab.linkedBrowser;
this.scheduleLayoutChange = this.scheduleLayoutChange.bind(this);
this.browser.addEventListener("resize", this.scheduleLayoutChange, true); this.browser.addEventListener("resize", this.scheduleLayoutChange, true);
// Show a warning when the debugger is paused. // Show a warning when the debugger is paused.
@ -309,13 +314,31 @@ InspectorPanel.prototype = {
* Hooks the searchbar to show result and auto completion suggestions. * Hooks the searchbar to show result and auto completion suggestions.
*/ */
setupSearchBox: function() { setupSearchBox: function() {
// Initiate the selectors search object.
if (this.searchSuggestions) {
this.searchSuggestions.destroy();
this.searchSuggestions = null;
}
this.searchBox = this.panelDoc.getElementById("inspector-searchbox"); this.searchBox = this.panelDoc.getElementById("inspector-searchbox");
this.searchSuggestions = new SelectorSearch(this, this.searchBox); this.searchResultsLabel = this.panelDoc.getElementById("inspector-searchlabel");
this.search = new InspectorSearch(this, this.searchBox);
this.search.on("search-cleared", this._updateSearchResultsLabel);
this.search.on("search-result", this._updateSearchResultsLabel);
},
get searchSuggestions() {
return this.search.autocompleter;
},
_updateSearchResultsLabel: function(event, result) {
let str = "";
if (event !== "search-cleared") {
if (result) {
str = strings.formatStringFromName(
"inspector.searchResultsCount2",
[result.resultsIndex + 1, result.resultsLength], 2);
} else {
str = strings.GetStringFromName("inspector.searchResultsNone");
}
}
this.searchResultsLabel.textContent = str;
}, },
/** /**
@ -369,7 +392,6 @@ InspectorPanel.prototype = {
*/ */
setupSidebarToggle: function() { setupSidebarToggle: function() {
this._paneToggleButton = this.panelDoc.getElementById("inspector-pane-toggle"); this._paneToggleButton = this.panelDoc.getElementById("inspector-pane-toggle");
this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
this._paneToggleButton.addEventListener("mousedown", this._paneToggleButton.addEventListener("mousedown",
this.onPaneToggleButtonClicked); this.onPaneToggleButtonClicked);
this.updatePaneToggleButton(); this.updatePaneToggleButton();
@ -399,7 +421,6 @@ InspectorPanel.prototype = {
return; return;
} }
this.markup.expandNode(this.selection.nodeFront); this.markup.expandNode(this.selection.nodeFront);
this.setupSearchBox();
this.emit("new-root"); this.emit("new-root");
}); });
}; };
@ -590,8 +611,6 @@ InspectorPanel.prototype = {
this._paneToggleButton.removeEventListener("mousedown", this._paneToggleButton.removeEventListener("mousedown",
this.onPaneToggleButtonClicked); this.onPaneToggleButtonClicked);
this._paneToggleButton = null; this._paneToggleButton = null;
this.searchSuggestions.destroy();
this.searchBox = null;
this.selection.off("new-node-front", this.onNewSelection); this.selection.off("new-node-front", this.onNewSelection);
this.selection.off("before-new-node", this.onBeforeNewSelection); this.selection.off("before-new-node", this.onBeforeNewSelection);
this.selection.off("before-new-node-front", this.onBeforeNewSelection); this.selection.off("before-new-node-front", this.onBeforeNewSelection);
@ -602,10 +621,12 @@ InspectorPanel.prototype = {
this.panelDoc = null; this.panelDoc = null;
this.panelWin = null; this.panelWin = null;
this.breadcrumbs = null; this.breadcrumbs = null;
this.searchSuggestions = null;
this.lastNodemenuItem = null; this.lastNodemenuItem = null;
this.nodemenu = null; this.nodemenu = null;
this._toolbox = null; this._toolbox = null;
this.search.destroy();
this.search = null;
this.searchBox = null;
this._panelDestroyer = promise.all([ this._panelDestroyer = promise.all([
sidebarDestroyer, sidebarDestroyer,
@ -914,8 +935,7 @@ InspectorPanel.prototype = {
this._markupFrame.setAttribute("context", "inspector-node-popup"); this._markupFrame.setAttribute("context", "inspector-node-popup");
// This is needed to enable tooltips inside the iframe document. // This is needed to enable tooltips inside the iframe document.
this._boundMarkupFrameLoad = this._onMarkupFrameLoad.bind(this); this._markupFrame.addEventListener("load", this._onMarkupFrameLoad, true);
this._markupFrame.addEventListener("load", this._boundMarkupFrameLoad, true);
this._markupBox.setAttribute("collapsed", true); this._markupBox.setAttribute("collapsed", true);
this._markupBox.appendChild(this._markupFrame); this._markupBox.appendChild(this._markupFrame);
@ -924,8 +944,7 @@ InspectorPanel.prototype = {
}, },
_onMarkupFrameLoad: function() { _onMarkupFrameLoad: function() {
this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true); this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
delete this._boundMarkupFrameLoad;
this._markupFrame.contentWindow.focus(); this._markupFrame.contentWindow.focus();
@ -940,9 +959,8 @@ InspectorPanel.prototype = {
_destroyMarkup: function() { _destroyMarkup: function() {
let destroyPromise; let destroyPromise;
if (this._boundMarkupFrameLoad) { if (this._markupFrame) {
this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true); this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
this._boundMarkupFrameLoad = null;
} }
if (this.markup) { if (this.markup) {

View File

@ -4,7 +4,7 @@
"use strict"; "use strict";
const { Cu } = require("chrome"); const {Cu, Ci} = require("chrome");
const promise = require("promise"); const promise = require("promise");
loader.lazyGetter(this, "EventEmitter", () => require("devtools/shared/event-emitter")); loader.lazyGetter(this, "EventEmitter", () => require("devtools/shared/event-emitter"));
@ -13,6 +13,108 @@ loader.lazyGetter(this, "AutocompletePopup", () => require("devtools/client/shar
// Maximum number of selector suggestions shown in the panel. // Maximum number of selector suggestions shown in the panel.
const MAX_SUGGESTIONS = 15; const MAX_SUGGESTIONS = 15;
/**
* Converts any input field into a document search box.
*
* @param {InspectorPanel} inspector The InspectorPanel whose `walker` attribute
* should be used for document traversal.
* @param {DOMNode} input The input element to which the panel will be attached
* and from where search input will be taken.
*
* Emits the following events:
* - search-cleared: when the search box is emptied
* - search-result: when a search is made and a result is selected
*/
function InspectorSearch(inspector, input) {
this.inspector = inspector;
this.searchBox = input;
this._lastSearched = null;
this._onKeyDown = this._onKeyDown.bind(this);
this._onCommand = this._onCommand.bind(this);
this.searchBox.addEventListener("keydown", this._onKeyDown, true);
this.searchBox.addEventListener("command", this._onCommand, true);
// For testing, we need to be able to wait for the most recent node request
// to finish. Tests can watch this promise for that.
this._lastQuery = promise.resolve(null);
this.autocompleter = new SelectorAutocompleter(inspector, input);
EventEmitter.decorate(this);
}
exports.InspectorSearch = InspectorSearch;
InspectorSearch.prototype = {
get walker() {
return this.inspector.walker;
},
destroy: function() {
this.searchBox.removeEventListener("keydown", this._onKeyDown, true);
this.searchBox.removeEventListener("command", this._onCommand, true);
this.searchBox = null;
this.autocompleter.destroy();
},
_onSearch: function(reverse = false) {
this.doFullTextSearch(this.searchBox.value, reverse)
.catch(e => console.error(e));
},
doFullTextSearch: Task.async(function*(query, reverse) {
let lastSearched = this._lastSearched;
this._lastSearched = query;
if (query.length === 0) {
this.searchBox.classList.remove("devtools-no-search-result");
if (!lastSearched || lastSearched.length > 0) {
this.emit("search-cleared");
}
return;
}
let res = yield this.walker.search(query, { reverse });
// Value has changed since we started this request, we're done.
if (query != this.searchBox.value) {
return;
}
if (res) {
this.inspector.selection.setNodeFront(res.node, "inspectorsearch");
this.searchBox.classList.remove("devtools-no-search-result");
res.query = query;
this.emit("search-result", res);
} else {
this.searchBox.classList.add("devtools-no-search-result");
this.emit("search-result");
}
}),
_onCommand: function() {
if (this.searchBox.value.length === 0) {
this._onSearch();
}
},
_onKeyDown: function(event) {
if (this.searchBox.value.length === 0) {
this.searchBox.removeAttribute("filled");
} else {
this.searchBox.setAttribute("filled", true);
}
if (event.keyCode === event.DOM_VK_RETURN) {
this._onSearch();
} if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_G && event.metaKey) {
this._onSearch(event.shiftKey);
event.preventDefault();
}
}
};
/** /**
* Converts any input box on a page to a CSS selector search and suggestion box. * Converts any input box on a page to a CSS selector search and suggestion box.
* *
@ -21,29 +123,19 @@ const MAX_SUGGESTIONS = 15;
* search or not. * search or not.
* *
* @constructor * @constructor
* @param InspectorPanel aInspector * @param InspectorPanel inspector
* The InspectorPanel whose `walker` attribute should be used for * The InspectorPanel whose `walker` attribute should be used for
* document traversal. * document traversal.
* @param nsiInputElement aInputNode * @param nsiInputElement inputNode
* The input element to which the panel will be attached and from where * The input element to which the panel will be attached and from where
* search input will be taken. * search input will be taken.
*/ */
function SelectorSearch(aInspector, aInputNode) { function SelectorAutocompleter(inspector, inputNode) {
this.inspector = aInspector; this.inspector = inspector;
this.searchBox = aInputNode; this.searchBox = inputNode;
this.panelDoc = this.searchBox.ownerDocument; this.panelDoc = this.searchBox.ownerDocument;
// initialize variables. this.showSuggestions = this.showSuggestions.bind(this);
this._lastSearched = null;
this._lastValidSearch = "";
this._lastToLastValidSearch = null;
this._searchResults = null;
this._searchSuggestions = {};
this._searchIndex = 0;
// bind!
this._showPopup = this._showPopup.bind(this);
this._onHTMLSearch = this._onHTMLSearch.bind(this);
this._onSearchKeypress = this._onSearchKeypress.bind(this); this._onSearchKeypress = this._onSearchKeypress.bind(this);
this._onListBoxKeypress = this._onListBoxKeypress.bind(this); this._onListBoxKeypress = this._onListBoxKeypress.bind(this);
this._onMarkupMutation = this._onMarkupMutation.bind(this); this._onMarkupMutation = this._onMarkupMutation.bind(this);
@ -61,8 +153,7 @@ function SelectorSearch(aInspector, aInputNode) {
}; };
this.searchPopup = new AutocompletePopup(this.panelDoc, options); this.searchPopup = new AutocompletePopup(this.panelDoc, options);
// event listeners. this.searchBox.addEventListener("input", this.showSuggestions, true);
this.searchBox.addEventListener("command", this._onHTMLSearch, true);
this.searchBox.addEventListener("keypress", this._onSearchKeypress, true); this.searchBox.addEventListener("keypress", this._onSearchKeypress, true);
this.inspector.on("markupmutation", this._onMarkupMutation); this.inspector.on("markupmutation", this._onMarkupMutation);
@ -72,9 +163,9 @@ function SelectorSearch(aInspector, aInputNode) {
EventEmitter.decorate(this); EventEmitter.decorate(this);
} }
exports.SelectorSearch = SelectorSearch; exports.SelectorAutocompleter = SelectorAutocompleter;
SelectorSearch.prototype = { SelectorAutocompleter.prototype = {
get walker() { get walker() {
return this.inspector.walker; return this.inspector.walker;
}, },
@ -169,142 +260,33 @@ SelectorSearch.prototype = {
* Removes event listeners and cleans up references. * Removes event listeners and cleans up references.
*/ */
destroy: function() { destroy: function() {
// event listeners. this.searchBox.removeEventListener("input", this.showSuggestions, true);
this.searchBox.removeEventListener("command", this._onHTMLSearch, true);
this.searchBox.removeEventListener("keypress", this._onSearchKeypress, true); this.searchBox.removeEventListener("keypress", this._onSearchKeypress, true);
this.inspector.off("markupmutation", this._onMarkupMutation); this.inspector.off("markupmutation", this._onMarkupMutation);
this.searchPopup.destroy(); this.searchPopup.destroy();
this.searchPopup = null; this.searchPopup = null;
this.searchBox = null; this.searchBox = null;
this.panelDoc = null; this.panelDoc = null;
this._searchResults = null;
this._searchSuggestions = null;
},
_selectResult: function(index) {
return this._searchResults.item(index).then(node => {
this.inspector.selection.setNodeFront(node, "selectorsearch");
});
},
_queryNodes: Task.async(function*(query) {
if (typeof this.hasMultiFrameSearch === "undefined") {
let target = this.inspector.toolbox.target;
this.hasMultiFrameSearch = yield target.actorHasMethod("domwalker",
"multiFrameQuerySelectorAll");
}
if (this.hasMultiFrameSearch) {
return yield this.walker.multiFrameQuerySelectorAll(query);
} else {
return yield this.walker.querySelectorAll(this.walker.rootNode, query);
}
}),
/**
* The command callback for the input box. This function is automatically
* invoked as the user is typing if the input box type is search.
*/
_onHTMLSearch: function() {
let query = this.searchBox.value;
if (query == this._lastSearched) {
this.emit("processing-done");
return;
}
this._lastSearched = query;
this._searchResults = [];
this._searchIndex = 0;
if (query.length == 0) {
this._lastValidSearch = "";
this.searchBox.removeAttribute("filled");
this.searchBox.classList.remove("devtools-no-search-result");
if (this.searchPopup.isOpen) {
this.searchPopup.hidePopup();
}
this.emit("processing-done");
return;
}
this.searchBox.setAttribute("filled", true);
let queryList = null;
this._lastQuery = this._queryNodes(query).then(list => {
return list;
}, (err) => {
// Failures are ok here, just use a null item list;
return null;
}).then(queryList => {
// Value has changed since we started this request, we're done.
if (query != this.searchBox.value) {
if (queryList) {
queryList.release();
}
return promise.reject(null);
}
this._searchResults = queryList || [];
if (this._searchResults && this._searchResults.length > 0) {
this._lastValidSearch = query;
// Even though the selector matched atleast one node, there is still
// possibility of suggestions.
if (query.match(/[\s>+]$/)) {
// If the query has a space or '>' at the end, create a selector to match
// the children of the selector inside the search box by adding a '*'.
this._lastValidSearch += "*";
}
else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
// If the query is a partial descendant selector which does not matches
// any node, remove the last incomplete part and add a '*' to match
// everything. For ex, convert 'foo > b' to 'foo > *' .
let lastPart = query.match(/[\s>+][\.#a-zA-Z][^>\s+]*$/)[0];
this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
}
if (!query.slice(-1).match(/[\.#\s>+]/)) {
// Hide the popup if we have some matching nodes and the query is not
// ending with [.# >] which means that the selector is not at the
// beginning of a new class, tag or id.
if (this.searchPopup.isOpen) {
this.searchPopup.hidePopup();
}
this.searchBox.classList.remove("devtools-no-search-result");
return this._selectResult(0);
}
return this._selectResult(0).then(() => {
this.searchBox.classList.remove("devtools-no-search-result");
}).then(() => this.showSuggestions());
}
if (query.match(/[\s>+]$/)) {
this._lastValidSearch = query + "*";
}
else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
let lastPart = query.match(/[\s+>][\.#a-zA-Z][^>\s+]*$/)[0];
this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
}
this.searchBox.classList.add("devtools-no-search-result");
return this.showSuggestions();
}).then(() => this.emit("processing-done"), Cu.reportError);
}, },
/** /**
* Handles keypresses inside the input box. * Handles keypresses inside the input box.
*/ */
_onSearchKeypress: function(aEvent) { _onSearchKeypress: function(event) {
let query = this.searchBox.value; let query = this.searchBox.value;
switch(aEvent.keyCode) { switch(event.keyCode) {
case aEvent.DOM_VK_RETURN: case event.DOM_VK_RETURN:
if (query == this._lastSearched && this._searchResults) { case event.DOM_VK_TAB:
this._searchIndex = (this._searchIndex + 1) % this._searchResults.length; if (this.searchPopup.isOpen &&
} this.searchPopup.getItemAtIndex(this.searchPopup.itemCount - 1)
else { .preLabel == query) {
this._onHTMLSearch(); this.searchPopup.selectedIndex = this.searchPopup.itemCount - 1;
return; this.searchBox.value = this.searchPopup.selectedItem.label;
this.hidePopup();
} }
break; break;
case aEvent.DOM_VK_UP: case event.DOM_VK_UP:
if (this.searchPopup.isOpen && this.searchPopup.itemCount > 0) { if (this.searchPopup.isOpen && this.searchPopup.itemCount > 0) {
this.searchPopup.focus(); this.searchPopup.focus();
if (this.searchPopup.selectedIndex == this.searchPopup.itemCount - 1) { if (this.searchPopup.selectedIndex == this.searchPopup.itemCount - 1) {
@ -316,76 +298,45 @@ SelectorSearch.prototype = {
} }
this.searchBox.value = this.searchPopup.selectedItem.label; this.searchBox.value = this.searchPopup.selectedItem.label;
} }
else if (--this._searchIndex < 0) {
this._searchIndex = this._searchResults.length - 1;
}
break; break;
case aEvent.DOM_VK_DOWN: case event.DOM_VK_DOWN:
if (this.searchPopup.isOpen && this.searchPopup.itemCount > 0) { if (this.searchPopup.isOpen && this.searchPopup.itemCount > 0) {
this.searchPopup.focus(); this.searchPopup.focus();
this.searchPopup.selectedIndex = 0; this.searchPopup.selectedIndex = 0;
this.searchBox.value = this.searchPopup.selectedItem.label; this.searchBox.value = this.searchPopup.selectedItem.label;
} }
this._searchIndex = (this._searchIndex + 1) % this._searchResults.length;
break; break;
case aEvent.DOM_VK_TAB:
if (this.searchPopup.isOpen &&
this.searchPopup.getItemAtIndex(this.searchPopup.itemCount - 1)
.preLabel == query) {
this.searchPopup.selectedIndex = this.searchPopup.itemCount - 1;
this.searchBox.value = this.searchPopup.selectedItem.label;
this._onHTMLSearch();
}
break;
case aEvent.DOM_VK_BACK_SPACE:
case aEvent.DOM_VK_DELETE:
// need to throw away the lastValidSearch.
this._lastToLastValidSearch = null;
// This gets the most complete selector from the query. For ex.
// '.foo.ba' returns '.foo' , '#foo > .bar.baz' returns '#foo > .bar'
// '.foo +bar' returns '.foo +' and likewise.
this._lastValidSearch = (query.match(/(.*)[\.#][^\.# ]{0,}$/) ||
query.match(/(.*[\s>+])[a-zA-Z][^\.# ]{0,}$/) ||
["",""])[1];
return;
default: default:
return; return;
} }
aEvent.preventDefault(); event.preventDefault();
aEvent.stopPropagation(); event.stopPropagation();
if (this._searchResults && this._searchResults.length > 0) { this.emit("processing-done");
this._lastQuery = this._selectResult(this._searchIndex).then(() => this.emit("processing-done"));
}
else {
this.emit("processing-done");
}
}, },
/** /**
* Handles keypress and mouse click on the suggestions richlistbox. * Handles keypress and mouse click on the suggestions richlistbox.
*/ */
_onListBoxKeypress: function(aEvent) { _onListBoxKeypress: function(event) {
switch(aEvent.keyCode || aEvent.button) { switch(event.keyCode || event.button) {
case aEvent.DOM_VK_RETURN: case event.DOM_VK_RETURN:
case aEvent.DOM_VK_TAB: case event.DOM_VK_TAB:
case 0: // left mouse button case 0: // left mouse button
aEvent.stopPropagation(); event.stopPropagation();
aEvent.preventDefault(); event.preventDefault();
this.searchBox.value = this.searchPopup.selectedItem.label; this.searchBox.value = this.searchPopup.selectedItem.label;
this.searchBox.focus(); this.searchBox.focus();
this._onHTMLSearch(); this.hidePopup();
break; break;
case aEvent.DOM_VK_UP: case event.DOM_VK_UP:
if (this.searchPopup.selectedIndex == 0) { if (this.searchPopup.selectedIndex == 0) {
this.searchPopup.selectedIndex = -1; this.searchPopup.selectedIndex = -1;
aEvent.stopPropagation(); event.stopPropagation();
aEvent.preventDefault(); event.preventDefault();
this.searchBox.focus(); this.searchBox.focus();
} }
else { else {
@ -394,11 +345,11 @@ SelectorSearch.prototype = {
} }
break; break;
case aEvent.DOM_VK_DOWN: case event.DOM_VK_DOWN:
if (this.searchPopup.selectedIndex == this.searchPopup.itemCount - 1) { if (this.searchPopup.selectedIndex == this.searchPopup.itemCount - 1) {
this.searchPopup.selectedIndex = -1; this.searchPopup.selectedIndex = -1;
aEvent.stopPropagation(); event.stopPropagation();
aEvent.preventDefault(); event.preventDefault();
this.searchBox.focus(); this.searchBox.focus();
} }
else { else {
@ -407,20 +358,15 @@ SelectorSearch.prototype = {
} }
break; break;
case aEvent.DOM_VK_BACK_SPACE: case event.DOM_VK_BACK_SPACE:
aEvent.stopPropagation(); event.stopPropagation();
aEvent.preventDefault(); event.preventDefault();
this.searchBox.focus(); this.searchBox.focus();
if (this.searchBox.selectionStart > 0) { if (this.searchBox.selectionStart > 0) {
this.searchBox.value = this.searchBox.value =
this.searchBox.value.substring(0, this.searchBox.selectionStart - 1); this.searchBox.value.substring(0, this.searchBox.selectionStart - 1);
} }
this._lastToLastValidSearch = null; this.hidePopup();
let query = this.searchBox.value;
this._lastValidSearch = (query.match(/(.*)[\.#][^\.# ]{0,}$/) ||
query.match(/(.*[\s>+])[a-zA-Z][^\.# ]{0,}$/) ||
["",""])[1];
this._onHTMLSearch();
break; break;
} }
this.emit("processing-done"); this.emit("processing-done");
@ -438,12 +384,12 @@ SelectorSearch.prototype = {
/** /**
* Populates the suggestions list and show the suggestion popup. * Populates the suggestions list and show the suggestion popup.
*/ */
_showPopup: function(aList, aFirstPart, aState) { _showPopup: function(list, firstPart, aState) {
let total = 0; let total = 0;
let query = this.searchBox.value; let query = this.searchBox.value;
let items = []; let items = [];
for (let [value, count, state] of aList) { for (let [value, /*count*/, state] of list) {
// for cases like 'div ' or 'div >' or 'div+' // for cases like 'div ' or 'div >' or 'div+'
if (query.match(/[\s>+]$/)) { if (query.match(/[\s>+]$/)) {
value = query + value; value = query + value;
@ -461,8 +407,7 @@ SelectorSearch.prototype = {
let item = { let item = {
preLabel: query, preLabel: query,
label: value, label: value
count: count
}; };
// In case of tagNames, change the case to small // In case of tagNames, change the case to small
@ -489,6 +434,16 @@ SelectorSearch.prototype = {
this.searchPopup.openPopup(this.searchBox); this.searchPopup.openPopup(this.searchBox);
} }
else { else {
this.hidePopup();
}
},
/**
* Hide the suggestion popup if necessary.
*/
hidePopup: function() {
if (this.searchPopup.isOpen) {
this.searchPopup.hidePopup(); this.searchPopup.hidePopup();
} }
}, },
@ -502,18 +457,18 @@ SelectorSearch.prototype = {
let state = this.state; let state = this.state;
let firstPart = ""; let firstPart = "";
if (state == this.States.TAG) { if (state === this.States.TAG) {
// gets the tag that is being completed. For ex. 'div.foo > s' returns 's', // gets the tag that is being completed. For ex. 'div.foo > s' returns 's',
// 'di' returns 'di' and likewise. // 'di' returns 'di' and likewise.
firstPart = (query.match(/[\s>+]?([a-zA-Z]*)$/) || ["", query])[1]; firstPart = (query.match(/[\s>+]?([a-zA-Z]*)$/) || ["", query])[1];
query = query.slice(0, query.length - firstPart.length); query = query.slice(0, query.length - firstPart.length);
} }
else if (state == this.States.CLASS) { else if (state === this.States.CLASS) {
// gets the class that is being completed. For ex. '.foo.b' returns 'b' // gets the class that is being completed. For ex. '.foo.b' returns 'b'
firstPart = query.match(/\.([^\.]*)$/)[1]; firstPart = query.match(/\.([^\.]*)$/)[1];
query = query.slice(0, query.length - firstPart.length - 1); query = query.slice(0, query.length - firstPart.length - 1);
} }
else if (state == this.States.ID) { else if (state === this.States.ID) {
// gets the id that is being completed. For ex. '.foo#b' returns 'b' // gets the id that is being completed. For ex. '.foo#b' returns 'b'
firstPart = query.match(/#([^#]*)$/)[1]; firstPart = query.match(/#([^#]*)$/)[1];
query = query.slice(0, query.length - firstPart.length - 1); query = query.slice(0, query.length - firstPart.length - 1);
@ -524,23 +479,31 @@ SelectorSearch.prototype = {
query += "*"; query += "*";
} }
this._currentSuggesting = query; this._lastQuery = this.walker.getSuggestionsForQuery(query, firstPart, state).then(result => {
return this.walker.getSuggestionsForQuery(query, firstPart, state).then(result => { this.emit("processing-done");
if (this._currentSuggesting != result.query) { if (result.query !== query) {
// This means that this response is for a previous request and the user // This means that this response is for a previous request and the user
// as since typed something extra leading to a new request. // as since typed something extra leading to a new request.
return; return;
} }
this._lastToLastValidSearch = this._lastValidSearch;
if (state == this.States.CLASS) { if (state === this.States.CLASS) {
firstPart = "." + firstPart; firstPart = "." + firstPart;
} } else if (state === this.States.ID) {
else if (state == this.States.ID) {
firstPart = "#" + firstPart; firstPart = "#" + firstPart;
} }
// If there is a single tag match and it's what the user typed, then
// don't need to show a popup.
if (result.suggestions.length === 1 &&
result.suggestions[0][0] === firstPart) {
result.suggestions = [];
}
this._showPopup(result.suggestions, firstPart, state); this._showPopup(result.suggestions, firstPart, state);
}); });
return this._lastQuery;
} }
}; };

View File

@ -154,11 +154,12 @@
class="breadcrumbs-widget-container" class="breadcrumbs-widget-container"
flex="1" orient="horizontal" flex="1" orient="horizontal"
clicktoscroll="true"/> clicktoscroll="true"/>
<box id="inspector-searchlabel" />
<textbox id="inspector-searchbox" <textbox id="inspector-searchbox"
type="search" type="search"
timeout="50" timeout="50"
class="devtools-searchinput" class="devtools-searchinput"
placeholder="&inspectorSearchHTML.label2;"/> placeholder="&inspectorSearchHTML.label3;"/>
<toolbarbutton id="inspector-pane-toggle" <toolbarbutton id="inspector-pane-toggle"
class="devtools-toolbarbutton" class="devtools-toolbarbutton"
tabindex="0" /> tabindex="0" />

View File

@ -6,7 +6,7 @@ DevToolsModules(
'breadcrumbs.js', 'breadcrumbs.js',
'inspector-commands.js', 'inspector-commands.js',
'inspector-panel.js', 'inspector-panel.js',
'selector-search.js' 'inspector-search.js'
) )
BROWSER_CHROME_MANIFESTS += ['test/browser.ini'] BROWSER_CHROME_MANIFESTS += ['test/browser.ini']

View File

@ -8,11 +8,6 @@
const TEST_URL = TEST_URL_ROOT + "doc_inspector_search.html"; const TEST_URL = TEST_URL_ROOT + "doc_inspector_search.html";
// Indexes of the keys in the KEY_STATES array that should listen to "keypress"
// event instead of "command". These are keys that don't change the content of
// the search field and thus don't trigger command event.
const LISTEN_KEYPRESS = [3,4,8,18,19,20,21,22];
// The various states of the inspector: [key, id, isValid] // The various states of the inspector: [key, id, isValid]
// [ // [
// what key to press, // what key to press,
@ -20,35 +15,45 @@ const LISTEN_KEYPRESS = [3,4,8,18,19,20,21,22];
// is the searched text valid selector // is the searched text valid selector
// ] // ]
const KEY_STATES = [ const KEY_STATES = [
["d", "b1", false], ["#", "b1", true], // #
["i", "b1", false], ["d", "b1", true], // #d
["v", "d1", true], ["1", "b1", true], // #d1
["VK_DOWN", "d2", true], // keypress ["VK_RETURN", "d1", true], // #d1
["VK_RETURN", "d1", true], //keypress ["VK_BACK_SPACE", "d1", true], // #d
[".", "d1", false], ["2", "d1", true], // #d2
["c", "d1", false], ["VK_RETURN", "d2", true], // #d2
["1", "d2", true], ["2", "d2", true], // #d22
["VK_DOWN", "d2", true], // keypress ["VK_RETURN", "d2", false], // #d22
["VK_BACK_SPACE", "d2", false], ["VK_BACK_SPACE", "d2", false], // #d2
["VK_BACK_SPACE", "d2", false], ["VK_RETURN", "d2", true], // #d2
["VK_BACK_SPACE", "d1", true], ["VK_BACK_SPACE", "d2", true], // #d
["VK_BACK_SPACE", "d1", false], ["1", "d2", true], // #d1
["VK_BACK_SPACE", "d1", false], ["VK_RETURN", "d1", true], // #d1
["VK_BACK_SPACE", "d1", true], ["VK_BACK_SPACE", "d1", true], // #d
[".", "d1", false], ["VK_BACK_SPACE", "d1", true], // #
["c", "d1", false], ["VK_BACK_SPACE", "d1", true], //
["1", "d2", true], ["d", "d1", true], // d
["VK_DOWN", "s2", true], // keypress ["i", "d1", true], // di
["VK_DOWN", "p1", true], // kepress ["v", "d1", true], // div
["VK_UP", "s2", true], // keypress [".", "d1", true], // div.
["VK_UP", "d2", true], // keypress ["c", "d1", true], // div.c
["VK_UP", "p1", true], ["VK_UP", "d1", true], // div.c1
["VK_BACK_SPACE", "p1", false], ["VK_TAB", "d1", true], // div.c1
["2", "p3", true], ["VK_RETURN", "d2", true], // div.c1
["VK_BACK_SPACE", "p3", false], ["VK_BACK_SPACE", "d2", true], // div.c
["VK_BACK_SPACE", "p3", false], ["VK_BACK_SPACE", "d2", true], // div.
["VK_BACK_SPACE", "p3", true], ["VK_BACK_SPACE", "d2", true], // div
["r", "p3", false], ["VK_BACK_SPACE", "d2", true], // di
["VK_BACK_SPACE", "d2", true], // d
["VK_BACK_SPACE", "d2", true], //
[".", "d2", true], // .
["c", "d2", true], // .c
["1", "d2", true], // .c1
["VK_RETURN", "d2", true], // .c1
["VK_RETURN", "s2", true], // .c1
["VK_RETURN", "p1", true], // .c1
["P", "p1", true], // .c1P
["VK_RETURN", "p1", false], // .c1P
]; ];
add_task(function* () { add_task(function* () {
@ -60,14 +65,18 @@ add_task(function* () {
let index = 0; let index = 0;
for (let [ key, id, isValid ] of KEY_STATES) { for (let [ key, id, isValid ] of KEY_STATES) {
let event = (LISTEN_KEYPRESS.indexOf(index) !== -1) ? "keypress" : "command"; info(index + ": Pressing key " + key + " to get id " + id + ".");
let eventHandled = once(searchBox, event, true); let done = inspector.searchSuggestions.once("processing-done");
info(index + ": Pressing key " + key + " to get id " + id);
EventUtils.synthesizeKey(key, {}, inspector.panelWin); EventUtils.synthesizeKey(key, {}, inspector.panelWin);
yield eventHandled; yield done;
info("Got processing-done event");
info("Got " + event + " event. Waiting for search query to complete"); if (key === "VK_RETURN") {
info ("Waiting for " + (isValid ? "NO " : "") + "results");
yield inspector.search.once("search-result");
}
info("Waiting for search query to complete");
yield inspector.searchSuggestions._lastQuery; yield inspector.searchSuggestions._lastQuery;
info(inspector.selection.nodeFront.id + " is selected with text " + info(inspector.selection.nodeFront.id + " is selected with text " +

View File

@ -16,14 +16,14 @@ const TEST_DATA = [
{ {
key: "d", key: "d",
suggestions: [ suggestions: [
{label: "div", count: 4}, {label: "div"},
{label: "#d1", count: 1}, {label: "#d1"},
{label: "#d2", count: 1} {label: "#d2"}
] ]
}, },
{ {
key: "i", key: "i",
suggestions: [{label: "div", count: 4}] suggestions: [{label: "div"}]
}, },
{ {
key: "v", key: "v",
@ -32,22 +32,22 @@ const TEST_DATA = [
{ {
key: " ", key: " ",
suggestions: [ suggestions: [
{label: "div div", count: 2}, {label: "div div"},
{label: "div span", count: 2} {label: "div span"}
] ]
}, },
{ {
key: ">", key: ">",
suggestions: [ suggestions: [
{label: "div >div", count: 2}, {label: "div >div"},
{label: "div >span", count: 2} {label: "div >span"}
] ]
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
suggestions: [ suggestions: [
{label: "div div", count: 2 }, {label: "div div"},
{label: "div span", count: 2} {label: "div span"}
] ]
}, },
{ {
@ -57,8 +57,8 @@ const TEST_DATA = [
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
suggestions: [ suggestions: [
{label: "div div", count: 2 }, {label: "div div"},
{label: "div span", count: 2} {label: "div span"}
] ]
}, },
{ {
@ -67,14 +67,14 @@ const TEST_DATA = [
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
suggestions: [{label: "div", count: 4}] suggestions: [{label: "div"}]
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
suggestions: [ suggestions: [
{label: "div", count: 4}, {label: "div"},
{label: "#d1", count: 1}, {label: "#d1"},
{label: "#d2", count: 1} {label: "#d2"}
] ]
}, },
{ {
@ -83,7 +83,12 @@ const TEST_DATA = [
}, },
{ {
key: "p", key: "p",
suggestions: [] suggestions: [
{label: "p"},
{label: "#p1"},
{label: "#p2"},
{label: "#p3"},
]
}, },
{ {
key: " ", key: " ",
@ -153,14 +158,12 @@ add_task(function* () {
for (let i = 0; i < suggestions.length; i++) { for (let i = 0; i < suggestions.length; i++) {
is(actualSuggestions[i].label, suggestions[i].label, is(actualSuggestions[i].label, suggestions[i].label,
"The suggestion at " + i + "th index is correct."); "The suggestion at " + i + "th index is correct.");
is(actualSuggestions[i].count, suggestions[i].count || 1,
"The count for suggestion at " + i + "th index is correct.");
} }
} }
}); });
function formatSuggestions(suggestions) { function formatSuggestions(suggestions) {
return "[" + suggestions return "[" + suggestions
.map(s => "'" + s.label + "' (" + (s.count || 1) + ")") .map(s => "'" + s.label + "'")
.join(", ") + "]"; .join(", ") + "]";
} }

View File

@ -16,14 +16,14 @@ var TEST_DATA = [
{ {
key: "d", key: "d",
suggestions: [ suggestions: [
{label: "div", count: 2}, {label: "div"},
{label: "#d1", count: 1}, {label: "#d1"},
{label: "#d2", count: 1} {label: "#d2"}
] ]
}, },
{ {
key: "i", key: "i",
suggestions: [{label: "div", count: 2}] suggestions: [{label: "div"}]
}, },
{ {
key: "v", key: "v",
@ -50,14 +50,14 @@ var TEST_DATA = [
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
suggestions: [{label: "div", count: 2}] suggestions: [{label: "div"}]
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
suggestions: [ suggestions: [
{label: "div", count: 2}, {label: "div"},
{label: "#d1", count: 1}, {label: "#d1"},
{label: "#d2", count: 1} {label: "#d2"}
] ]
}, },
{ {
@ -67,14 +67,14 @@ var TEST_DATA = [
{ {
key: ".", key: ".",
suggestions: [ suggestions: [
{label: ".c1", count: 3}, {label: ".c1"},
{label: ".c2"} {label: ".c2"}
] ]
}, },
{ {
key: "c", key: "c",
suggestions: [ suggestions: [
{label: ".c1", count: 3}, {label: ".c1"},
{label: ".c2"} {label: ".c2"}
] ]
}, },
@ -85,7 +85,7 @@ var TEST_DATA = [
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
suggestions: [ suggestions: [
{label: ".c1", count: 3}, {label: ".c1"},
{label: ".c2"} {label: ".c2"}
] ]
}, },
@ -108,14 +108,14 @@ var TEST_DATA = [
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
suggestions: [ suggestions: [
{label: ".c1", count: 3}, {label: ".c1"},
{label: ".c2"} {label: ".c2"}
] ]
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
suggestions: [ suggestions: [
{label: ".c1", count: 3}, {label: ".c1"},
{label: ".c2"} {label: ".c2"}
] ]
}, },
@ -189,14 +189,12 @@ add_task(function* () {
for (let i = 0; i < suggestions.length; i++) { for (let i = 0; i < suggestions.length; i++) {
is(actualSuggestions[i].label, suggestions[i].label, is(actualSuggestions[i].label, suggestions[i].label,
"The suggestion at " + i + "th index is correct."); "The suggestion at " + i + "th index is correct.");
is(actualSuggestions[i].count, suggestions[i].count || 1,
"The count for suggestion at " + i + "th index is correct.");
} }
} }
}); });
function formatSuggestions(suggestions) { function formatSuggestions(suggestions) {
return "[" + suggestions return "[" + suggestions
.map(s => "'" + s.label + "' (" + (s.count || 1) + ")") .map(s => "'" + s.label + "'")
.join(", ") + "]"; .join(", ") + "]";
} }

View File

@ -19,14 +19,14 @@ var TEST_DATA = [
{ {
key: "d", key: "d",
suggestions: [ suggestions: [
{label: "div", count: 5}, {label: "div"},
{label: "#d1", count: 2}, {label: "#d1"},
{label: "#d2", count: 2} {label: "#d2"}
] ]
}, },
{ {
key: "i", key: "i",
suggestions: [{label: "div", count: 5}] suggestions: [{label: "div"}]
}, },
{ {
key: "v", key: "v",
@ -34,14 +34,14 @@ var TEST_DATA = [
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
suggestions: [{label: "div", count: 5}] suggestions: [{label: "div"}]
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
suggestions: [ suggestions: [
{label: "div", count: 5}, {label: "div"},
{label: "#d1", count: 2}, {label: "#d1"},
{label: "#d2", count: 2} {label: "#d2"}
] ]
}, },
{ {
@ -51,8 +51,8 @@ var TEST_DATA = [
{ {
key: ".", key: ".",
suggestions: [ suggestions: [
{label: ".c1", count: 7}, {label: ".c1"},
{label: ".c2", count: 3} {label: ".c2"}
] ]
}, },
{ {
@ -62,14 +62,14 @@ var TEST_DATA = [
{ {
key: "#", key: "#",
suggestions: [ suggestions: [
{label: "#b1", count: 2}, {label: "#b1"},
{label: "#d1", count: 2}, {label: "#d1"},
{label: "#d2", count: 2}, {label: "#d2"},
{label: "#p1", count: 2}, {label: "#p1"},
{label: "#p2", count: 2}, {label: "#p2"},
{label: "#p3", count: 2}, {label: "#p3"},
{label: "#s1", count: 2}, {label: "#s1"},
{label: "#s2", count: 2} {label: "#s2"}
] ]
}, },
]; ];
@ -100,14 +100,12 @@ add_task(function* () {
for (let i = 0; i < suggestions.length; i++) { for (let i = 0; i < suggestions.length; i++) {
is(actualSuggestions[i].label, suggestions[i].label, is(actualSuggestions[i].label, suggestions[i].label,
"The suggestion at " + i + "th index is correct."); "The suggestion at " + i + "th index is correct.");
is(actualSuggestions[i].count, suggestions[i].count || 1,
"The count for suggestion at " + i + "th index is correct.");
} }
} }
}); });
function formatSuggestions(suggestions) { function formatSuggestions(suggestions) {
return "[" + suggestions return "[" + suggestions
.map(s => "'" + s.label + "' (" + (s.count || 1) + ")") .map(s => "'" + s.label + "'")
.join(", ") + "]"; .join(", ") + "]";
} }

View File

@ -11,11 +11,14 @@ const TEST_URL = "data:text/html;charset=utf-8," +
"<iframe id=\"iframe-1\" src=\"" + "<iframe id=\"iframe-1\" src=\"" +
TEST_URL_ROOT + IFRAME_SRC + "\"></iframe>" + TEST_URL_ROOT + IFRAME_SRC + "\"></iframe>" +
"<iframe id=\"iframe-2\" src=\"" + "<iframe id=\"iframe-2\" src=\"" +
TEST_URL_ROOT + IFRAME_SRC + "\"></iframe>"; TEST_URL_ROOT + IFRAME_SRC + "\"></iframe>" +
"<iframe id='iframe-3' src='data:text/html," +
"<button id=\"b1\">Nested button</button>" +
"<iframe id=\"iframe-4\" src=" + TEST_URL_ROOT + IFRAME_SRC + "></iframe>'>" +
"</iframe>";
add_task(function* () { add_task(function* () {
let {inspector} = yield openInspectorForURL(TEST_URL); let {inspector} = yield openInspectorForURL(TEST_URL);
let {walker} = inspector;
let searchBox = inspector.searchBox; let searchBox = inspector.searchBox;
let popup = inspector.searchSuggestions.searchPopup; let popup = inspector.searchSuggestions.searchPopup;
@ -24,41 +27,65 @@ add_task(function* () {
yield focusSearchBoxUsingShortcut(inspector.panelWin); yield focusSearchBoxUsingShortcut(inspector.panelWin);
info("Enter # to search for all ids"); info("Enter # to search for all ids");
let command = once(searchBox, "command"); let processingDone = once(inspector.searchSuggestions, "processing-done");
EventUtils.synthesizeKey("#", {}, inspector.panelWin); EventUtils.synthesizeKey("#", {}, inspector.panelWin);
yield command; yield processingDone;
info("Wait for search query to complete"); info("Wait for search query to complete");
yield inspector.searchSuggestions._lastQuery; yield inspector.searchSuggestions._lastQuery;
info("Press tab to fill the search input with the first suggestion and " + info("Press tab to fill the search input with the first suggestion");
"expect a new selection"); processingDone = once(inspector.searchSuggestions, "processing-done");
let onSelect = inspector.once("inspector-updated");
EventUtils.synthesizeKey("VK_TAB", {}, inspector.panelWin); EventUtils.synthesizeKey("VK_TAB", {}, inspector.panelWin);
yield processingDone;
info("Press enter and expect a new selection");
let onSelect = inspector.once("inspector-updated");
EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
yield onSelect; yield onSelect;
let node = inspector.selection.nodeFront; yield checkCorrectButton(inspector, "#iframe-1");
ok(node.id, "b1", "The selected node is #b1");
ok(node.tagName.toLowerCase(), "button",
"The selected node is <button>");
let selectedNodeDoc = yield walker.document(node);
let iframe1 = yield walker.querySelector(walker.rootNode, "#iframe-1");
let iframe1Doc = (yield walker.children(iframe1)).nodes[0];
is(selectedNodeDoc, iframe1Doc, "The selected node is in iframe 1");
info("Press enter to cycle through multiple nodes matching this suggestion"); info("Press enter to cycle through multiple nodes matching this suggestion");
onSelect = inspector.once("inspector-updated"); onSelect = inspector.once("inspector-updated");
EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin); EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
yield onSelect; yield onSelect;
node = inspector.selection.nodeFront; yield checkCorrectButton(inspector, "#iframe-2");
ok(node.id, "b1", "The selected node is #b1 again");
ok(node.tagName.toLowerCase(), "button",
"The selected node is <button> again");
selectedNodeDoc = yield walker.document(node); info("Press enter to cycle through multiple nodes matching this suggestion");
let iframe2 = yield walker.querySelector(walker.rootNode, "#iframe-2"); onSelect = inspector.once("inspector-updated");
let iframe2Doc = (yield walker.children(iframe2)).nodes[0]; EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
is(selectedNodeDoc, iframe2Doc, "The selected node is in iframe 2"); yield onSelect;
yield checkCorrectButton(inspector, "#iframe-3");
info("Press enter to cycle through multiple nodes matching this suggestion");
onSelect = inspector.once("inspector-updated");
EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
yield onSelect;
yield checkCorrectButton(inspector, "#iframe-4");
info("Press enter to cycle through multiple nodes matching this suggestion");
onSelect = inspector.once("inspector-updated");
EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
yield onSelect;
yield checkCorrectButton(inspector, "#iframe-1");
});
let checkCorrectButton = Task.async(function*(inspector, frameSelector) {
let {walker} = inspector;
let node = inspector.selection.nodeFront;
ok(node.id, "b1", "The selected node is #b1");
ok(node.tagName.toLowerCase(), "button",
"The selected node is <button>");
let selectedNodeDoc = yield walker.document(node);
let iframe = yield walker.multiFrameQuerySelectorAll(frameSelector);
iframe = yield iframe.item(0);
let iframeDoc = (yield walker.children(iframe)).nodes[0];
is(selectedNodeDoc, iframeDoc, "The selected node is in " + frameSelector);
}); });

View File

@ -14,8 +14,9 @@ add_task(function* () {
info("Searching for test node #d1"); info("Searching for test node #d1");
yield focusSearchBoxUsingShortcut(inspector.panelWin); yield focusSearchBoxUsingShortcut(inspector.panelWin);
yield synthesizeKeys(["#", "d", "1"], inspector); yield synthesizeKeys(["#", "d", "1", "VK_RETURN"], inspector);
yield inspector.search.once("search-result");
assertHasResult(inspector, true); assertHasResult(inspector, true);
info("Removing node #d1"); info("Removing node #d1");
@ -25,12 +26,15 @@ add_task(function* () {
info("Pressing return button to search again for node #d1."); info("Pressing return button to search again for node #d1.");
yield synthesizeKeys("VK_RETURN", inspector); yield synthesizeKeys("VK_RETURN", inspector);
yield inspector.search.once("search-result");
assertHasResult(inspector, false); assertHasResult(inspector, false);
info("Emptying the field and searching for a node that doesn't exist: #d3"); info("Emptying the field and searching for a node that doesn't exist: #d3");
let keys = ["VK_BACK_SPACE", "VK_BACK_SPACE", "VK_BACK_SPACE", "#", "d", "3"]; let keys = ["VK_BACK_SPACE", "VK_BACK_SPACE", "VK_BACK_SPACE", "#", "d", "3",
"VK_RETURN"];
yield synthesizeKeys(keys, inspector); yield synthesizeKeys(keys, inspector);
yield inspector.search.once("search-result");
assertHasResult(inspector, false); assertHasResult(inspector, false);
info("Create the #d3 node in the page"); info("Create the #d3 node in the page");
@ -41,6 +45,7 @@ add_task(function* () {
info("Pressing return button to search again for node #d3."); info("Pressing return button to search again for node #d3.");
yield synthesizeKeys("VK_RETURN", inspector); yield synthesizeKeys("VK_RETURN", inspector);
yield inspector.search.once("search-result");
assertHasResult(inspector, true); assertHasResult(inspector, true);
// Catch-all event for remaining server requests when searching for the new // Catch-all event for remaining server requests when searching for the new

View File

@ -13,15 +13,15 @@ const TEST_URL = TEST_URL_ROOT + "doc_inspector_search-reserved.html";
const TEST_DATA = [ const TEST_DATA = [
{ {
key: "#", key: "#",
suggestions: [{label: "#d1\\.d2", count: 1}] suggestions: [{label: "#d1\\.d2"}]
}, },
{ {
key: "d", key: "d",
suggestions: [{label: "#d1\\.d2", count: 1}] suggestions: [{label: "#d1\\.d2"}]
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
suggestions: [{label: "#d1\\.d2", count: 1}] suggestions: [{label: "#d1\\.d2"}]
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
@ -29,15 +29,15 @@ const TEST_DATA = [
}, },
{ {
key: ".", key: ".",
suggestions: [{label: ".c1\\.c2", count: 1}] suggestions: [{label: ".c1\\.c2"}]
}, },
{ {
key: "c", key: "c",
suggestions: [{label: ".c1\\.c2", count: 1}] suggestions: [{label: ".c1\\.c2"}]
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
suggestions: [{label: ".c1\\.c2", count: 1}] suggestions: [{label: ".c1\\.c2"}]
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
@ -45,8 +45,8 @@ const TEST_DATA = [
}, },
{ {
key: "d", key: "d",
suggestions: [{label: "div", count: 2}, suggestions: [{label: "div"},
{label: "#d1\\.d2", count: 1}] {label: "#d1\\.d2"}]
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
@ -54,7 +54,7 @@ const TEST_DATA = [
}, },
{ {
key:"c", key:"c",
suggestions: [{label: ".c1\\.c2", count: 1}] suggestions: [{label: ".c1\\.c2"}]
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
@ -62,15 +62,15 @@ const TEST_DATA = [
}, },
{ {
key: "b", key: "b",
suggestions: [{label: "body", count: 1}] suggestions: [{label: "body"}]
}, },
{ {
key: "o", key: "o",
suggestions: [{label: "body", count: 1}] suggestions: [{label: "body"}]
}, },
{ {
key: "d", key: "d",
suggestions: [{label: "body", count: 1}] suggestions: [{label: "body"}]
}, },
{ {
key: "y", key: "y",
@ -78,20 +78,20 @@ const TEST_DATA = [
}, },
{ {
key: " ", key: " ",
suggestions: [{label: "body div", count: 2}] suggestions: [{label: "body div"}]
}, },
{ {
key: ".", key: ".",
suggestions: [{label: "body .c1\\.c2", count: 1}] suggestions: [{label: "body .c1\\.c2"}]
}, },
{ {
key: "VK_BACK_SPACE", key: "VK_BACK_SPACE",
suggestions: [{label: "body div", count: 2}] suggestions: [{label: "body div"}]
}, },
{ {
key: "#", key: "#",
suggestions: [{label: "body #", count: 1}, suggestions: [{label: "body #"},
{label: "body #d1\\.d2", count: 1}] {label: "body #d1\\.d2"}]
} }
]; ];
@ -121,14 +121,12 @@ add_task(function* () {
for (let i = 0; i < suggestions.length; i++) { for (let i = 0; i < suggestions.length; i++) {
is(suggestions[i].label, actualSuggestions[i].label, is(suggestions[i].label, actualSuggestions[i].label,
"The suggestion at " + i + "th index is correct."); "The suggestion at " + i + "th index is correct.");
is(suggestions[i].count || 1, actualSuggestions[i].count,
"The count for suggestion at " + i + "th index is correct.");
} }
} }
}); });
function formatSuggestions(suggestions) { function formatSuggestions(suggestions) {
return "[" + suggestions return "[" + suggestions
.map(s => "'" + s.label + "' (" + s.count || 1 + ")") .map(s => "'" + s.label + "'")
.join(", ") + "]"; .join(", ") + "]";
} }

View File

@ -113,9 +113,8 @@
<!ENTITY inspectorSearchHTML.label2 "Search with CSS Selectors"> <!ENTITY inspectorSearchHTML.label2 "Search with CSS Selectors">
<!ENTITY inspectorSearchHTML.key "F"> <!ENTITY inspectorSearchHTML.key "F">
<!-- LOCALIZATION NOTE (inspectorSearchHTML.label3): This is the label that will <!-- LOCALIZATION NOTE (inspectorSearchHTML.label3): This is the label that is
be shown as the placeholder in the future, once the inspector search box shown as the placeholder for the markup view search in the inspector. -->
supports the full text HTML search in Bug 835896. -->
<!ENTITY inspectorSearchHTML.label3 "Search HTML"> <!ENTITY inspectorSearchHTML.label3 "Search HTML">
<!-- LOCALIZATION NOTE (inspectorCopyImageDataUri.label): This is the label <!-- LOCALIZATION NOTE (inspectorCopyImageDataUri.label): This is the label

View File

@ -40,7 +40,7 @@ const Toolbar = module.exports = createClass({
dom.div({ className: "devtools-toolbar" }, dom.div({ className: "devtools-toolbar" },
dom.button({ dom.button({
id: "take-snapshot", id: "take-snapshot",
className: `take-snapshot devtools-button`, className: "take-snapshot devtools-button",
onClick: onTakeSnapshotClick, onClick: onTakeSnapshotClick,
title: L10N.getStr("take-snapshot") title: L10N.getStr("take-snapshot")
}), }),
@ -50,7 +50,7 @@ const Toolbar = module.exports = createClass({
L10N.getStr("toolbar.breakdownBy"), L10N.getStr("toolbar.breakdownBy"),
dom.select({ dom.select({
id: "select-breakdown", id: "select-breakdown",
className: `select-breakdown`, className: "select-breakdown",
onChange: e => onBreakdownChange(e.target.value), onChange: e => onBreakdownChange(e.target.value),
}, ...breakdowns.map(({ name, displayName }) => dom.option({ key: name, value: name }, displayName))) }, ...breakdowns.map(({ name, displayName }) => dom.option({ key: name, value: name }, displayName)))
), ),
@ -81,6 +81,7 @@ const Toolbar = module.exports = createClass({
dom.input({ dom.input({
id: "filter", id: "filter",
type: "search", type: "search",
className: "devtools-searchinput",
placeholder: L10N.getStr("filter.placeholder"), placeholder: L10N.getStr("filter.placeholder"),
onChange: event => setFilterString(event.target.value), onChange: event => setFilterString(event.target.value),
value: !!filterString ? filterString : undefined, value: !!filterString ? filterString : undefined,

View File

@ -59,4 +59,14 @@ add_task(function *() {
widget.setCssValue("contrast(5%) whatever invert('xxx')"); widget.setCssValue("contrast(5%) whatever invert('xxx')");
is(widget.getCssValue(), "contrast(5%) invert(0%)", is(widget.getCssValue(), "contrast(5%) invert(0%)",
"setCssValue should handle multiple errors"); "setCssValue should handle multiple errors");
info("Test parsing of 'unset'");
widget.setCssValue("unset");
is(widget.getCssValue(), "unset", "setCssValue should handle 'unset'");
info("Test parsing of 'initial'");
widget.setCssValue("initial");
is(widget.getCssValue(), "initial", "setCssValue should handle 'initial'");
info("Test parsing of 'inherit'");
widget.setCssValue("inherit");
is(widget.getCssValue(), "inherit", "setCssValue should handle 'inherit'");
}); });

View File

@ -98,6 +98,9 @@ const filterList = [
} }
]; ];
// Valid values that shouldn't be parsed for filters.
const SPECIAL_VALUES = new Set(["none", "unset", "initial", "inherit"]);
/** /**
* A CSS Filter editor widget used to add/remove/modify * A CSS Filter editor widget used to add/remove/modify
* filters. * filters.
@ -557,7 +560,7 @@ CSSFilterEditorWidget.prototype = {
let name = this.addPresetInput.value; let name = this.addPresetInput.value;
let value = this.getCssValue(); let value = this.getCssValue();
if (!name || !value || value === "none") { if (!name || !value || SPECIAL_VALUES.has(value)) {
this.emit("preset-save-error"); this.emit("preset-save-error");
return; return;
} }
@ -706,7 +709,8 @@ CSSFilterEditorWidget.prototype = {
this.filters = []; this.filters = [];
if (cssValue === "none") { if (SPECIAL_VALUES.has(cssValue)) {
this._specialValue = cssValue;
this.emit("updated", this.getCssValue()); this.emit("updated", this.getCssValue());
this.render(); this.render();
return; return;
@ -825,7 +829,7 @@ CSSFilterEditorWidget.prototype = {
getCssValue: function() { getCssValue: function() {
return this.filters.map((filter, i) => { return this.filters.map((filter, i) => {
return `${filter.name}(${this.getValueAt(i)})`; return `${filter.name}(${this.getValueAt(i)})`;
}).join(" ") || "none"; }).join(" ") || this._specialValue || "none";
}, },
/** /**
@ -906,7 +910,7 @@ function tokenizeFilterValue(css) {
let filters = []; let filters = [];
let depth = 0; let depth = 0;
if (css === "none") { if (SPECIAL_VALUES.has(css)) {
return filters; return filters;
} }

View File

@ -15,6 +15,10 @@
%endif %endif
#inspector-searchlabel {
overflow: hidden;
}
#inspector-searchbox { #inspector-searchbox {
transition-property: max-width, -moz-padding-end, -moz-padding-start; transition-property: max-width, -moz-padding-end, -moz-padding-start;
transition-duration: 250ms; transition-duration: 250ms;

View File

@ -100,6 +100,11 @@ html, body, #app, #memory-tool {
flex: 1; flex: 1;
} }
#filter {
align-self: stretch;
margin: 2px;
}
/** /**
* TODO bug 1213100 * TODO bug 1213100
* Once we figure out how to store invertable buttons (pseudo element like in * Once we figure out how to store invertable buttons (pseudo element like in

View File

@ -61,6 +61,7 @@ const object = require("sdk/util/object");
const events = require("sdk/event/core"); const events = require("sdk/event/core");
const {Unknown} = require("sdk/platform/xpcom"); const {Unknown} = require("sdk/platform/xpcom");
const {Class} = require("sdk/core/heritage"); const {Class} = require("sdk/core/heritage");
const {WalkerSearch} = require("devtools/server/actors/utils/walker-search");
const {PageStyleActor, getFontPreviewData} = require("devtools/server/actors/styles"); const {PageStyleActor, getFontPreviewData} = require("devtools/server/actors/styles");
const { const {
HighlighterActor, HighlighterActor,
@ -1122,6 +1123,13 @@ types.addDictType("disconnectedNodeArray", {
types.addDictType("dommutation", {}); types.addDictType("dommutation", {});
types.addDictType("searchresult", {
list: "domnodelist",
// Right now there is isn't anything required for metadata,
// but it's json so it can be extended with extra data.
metadata: "array:json"
});
/** /**
* Server side of a node list as returned by querySelectorAll() * Server side of a node list as returned by querySelectorAll()
*/ */
@ -1299,6 +1307,8 @@ var WalkerActor = protocol.ActorClass({
this._activePseudoClassLocks = new Set(); this._activePseudoClassLocks = new Set();
this.showAllAnonymousContent = options.showAllAnonymousContent; this.showAllAnonymousContent = options.showAllAnonymousContent;
this.walkerSearch = new WalkerSearch(this);
// Nodes which have been removed from the client's known // Nodes which have been removed from the client's known
// ownership tree are considered "orphaned", and stored in // ownership tree are considered "orphaned", and stored in
// this set. // this set.
@ -1334,7 +1344,13 @@ var WalkerActor = protocol.ActorClass({
// FF42+ Inspector starts managing the Walker, while the inspector also // FF42+ Inspector starts managing the Walker, while the inspector also
// starts cleaning itself up automatically on client disconnection. // starts cleaning itself up automatically on client disconnection.
// So that there is no need to manually release the walker anymore. // So that there is no need to manually release the walker anymore.
autoReleased: true autoReleased: true,
// XXX: It seems silly that we need to tell the front which capabilities
// its actor has in this way when the target can use actorHasMethod. If
// this was ported to the protocol (Bug 1157048) we could call that inside
// of custom front methods and not need to do traits for this.
multiFrameQuerySelectorAll: true,
textSearch: true,
} }
} }
}, },
@ -1376,6 +1392,7 @@ var WalkerActor = protocol.ActorClass({
this.onFrameLoad = null; this.onFrameLoad = null;
this.onFrameUnload = null; this.onFrameUnload = null;
this.walkerSearch.destroy();
this.reflowObserver.off("reflows", this._onReflows); this.reflowObserver.off("reflows", this._onReflows);
this.reflowObserver = null; this.reflowObserver = null;
this._onReflows = null; this._onReflows = null;
@ -2048,6 +2065,33 @@ var WalkerActor = protocol.ActorClass({
} }
}), }),
/**
* Search the document for a given string.
* Results will be searched with the walker-search module (searches through
* tag names, attribute names and values, and text contents).
*
* @returns {searchresult}
* - {NodeList} list
* - {Array<Object>} metadata. Extra information with indices that
* match up with node list.
*/
search: method(function(query) {
let results = this.walkerSearch.search(query);
let nodeList = new NodeListActor(this, results.map(r => r.node));
return {
list: nodeList,
metadata: []
}
}, {
request: {
query: Arg(0),
},
response: {
list: RetVal("searchresult"),
}
}),
/** /**
* Returns a list of matching results for CSS selector autocompletion. * Returns a list of matching results for CSS selector autocompletion.
* *
@ -2838,6 +2882,11 @@ var WalkerActor = protocol.ActorClass({
* See https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#MutationRecord * See https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#MutationRecord
*/ */
onMutations: function(mutations) { onMutations: function(mutations) {
// Notify any observers that want *all* mutations (even on nodes that aren't
// referenced). This is not sent over the protocol so can only be used by
// scripts running in the server process.
events.emit(this, "any-mutation");
for (let change of mutations) { for (let change of mutations) {
let targetActor = this._refMap.get(change.target); let targetActor = this._refMap.get(change.target);
if (!targetActor) { if (!targetActor) {
@ -3315,6 +3364,75 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
impl: "_getNodeFromActor" impl: "_getNodeFromActor"
}), }),
/*
* Incrementally search the document for a given string.
* For modern servers, results will be searched with using the WalkerActor
* `search` function (includes tag names, attributes, and text contents).
* Only 1 result is sent back, and calling the method again with the same
* query will send the next result. When there are no more results to be sent
* back, null is sent.
* @param {String} query
* @param {Object} options
* - "reverse": search backwards
* - "selectorOnly": treat input as a selector string (don't search text
* tags, attributes, etc)
*/
search: protocol.custom(Task.async(function*(query, options = { }) {
let nodeList;
let searchType;
let searchData = this.searchData = this.searchData || { };
let selectorOnly = !!options.selectorOnly;
// Backwards compat. Use selector only search if the new
// search functionality isn't implemented, or if the caller (tests)
// want it.
if (selectorOnly || !this.traits.textSearch) {
searchType = "selector";
if (this.traits.multiFrameQuerySelectorAll) {
nodeList = yield this.multiFrameQuerySelectorAll(query);
} else {
nodeList = yield this.querySelectorAll(this.rootNode, query);
}
} else {
searchType = "search";
let result = yield this._search(query, options);
nodeList = result.list;
}
// If this is a new search, start at the beginning.
if (searchData.query !== query ||
searchData.selectorOnly !== selectorOnly) {
searchData.selectorOnly = selectorOnly;
searchData.query = query;
searchData.index = -1;
}
if (!nodeList.length) {
return null;
}
// Move search result cursor and cycle if necessary.
searchData.index = options.reverse ? searchData.index - 1 :
searchData.index + 1;
if (searchData.index >= nodeList.length) {
searchData.index = 0;
}
if (searchData.index < 0) {
searchData.index = nodeList.length - 1;
}
// Send back the single node, along with any relevant search data
let node = yield nodeList.item(searchData.index);
return {
type: searchType,
node: node,
resultsLength: nodeList.length,
resultsIndex: searchData.index,
};
}), {
impl: "_search"
}),
_releaseFront: function(node, force) { _releaseFront: function(node, force) {
if (node.retained && !force) { if (node.retained && !force) {
node.reparent(null); node.reparent(null);
@ -3875,10 +3993,25 @@ DocumentWalker.prototype = {
return this.walker.parentNode(); return this.walker.parentNode();
}, },
nextNode: function() {
let node = this.walker.currentNode;
if (!node) {
return null;
}
let nextNode = this.walker.nextNode();
while (nextNode && this.filter(nextNode) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {
nextNode = this.walker.nextNode();
}
return nextNode;
},
firstChild: function() { firstChild: function() {
let node = this.walker.currentNode; let node = this.walker.currentNode;
if (!node) if (!node) {
return null; return null;
}
let firstChild = this.walker.firstChild(); let firstChild = this.walker.firstChild();
while (firstChild && this.filter(firstChild) === Ci.nsIDOMNodeFilter.FILTER_SKIP) { while (firstChild && this.filter(firstChild) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {
@ -3890,8 +4023,9 @@ DocumentWalker.prototype = {
lastChild: function() { lastChild: function() {
let node = this.walker.currentNode; let node = this.walker.currentNode;
if (!node) if (!node) {
return null; return null;
}
let lastChild = this.walker.lastChild(); let lastChild = this.walker.lastChild();
while (lastChild && this.filter(lastChild) === Ci.nsIDOMNodeFilter.FILTER_SKIP) { while (lastChild && this.filter(lastChild) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {

View File

@ -2084,6 +2084,13 @@ function getRuleText(initialText, line, column) {
if (startOffset === undefined) { if (startOffset === undefined) {
return {offset: 0, text: ""}; return {offset: 0, text: ""};
} }
// If the input didn't have any tokens between the braces (e.g.,
// "div {}"), then the endOffset won't have been set yet; so account
// for that here.
if (endOffset === undefined) {
endOffset = startOffset;
}
// Note that this approach will preserve comments, despite the fact // Note that this approach will preserve comments, despite the fact
// that cssTokenizer skips them. // that cssTokenizer skips them.
return {offset: textOffset + startOffset, return {offset: textOffset + startOffset,

View File

@ -12,5 +12,6 @@ DevToolsModules(
'map-uri-to-addon-id.js', 'map-uri-to-addon-id.js',
'ScriptStore.js', 'ScriptStore.js',
'stack.js', 'stack.js',
'TabSources.js' 'TabSources.js',
'walker-search.js'
) )

View File

@ -0,0 +1,278 @@
/* 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/. */
"use strict";
/**
* The walker-search module provides a simple API to index and search strings
* and elements inside a given document.
* It indexes tag names, attribute names and values, and text contents.
* It provides a simple search function that returns a list of nodes that
* matched.
*/
const {Ci, Cu} = require("chrome");
/**
* The WalkerIndex class indexes the document (and all subdocs) from
* a given walker.
*
* It is only indexed the first time the data is accessed and will be
* re-indexed if a mutation happens between requests.
*
* @param {Walker} walker The walker to be indexed
*/
function WalkerIndex(walker) {
this.walker = walker;
this.clearIndex = this.clearIndex.bind(this);
// Kill the index when mutations occur, the next data get will re-index.
this.walker.on("any-mutation", this.clearIndex);
}
WalkerIndex.prototype = {
/**
* Destroy this instance, releasing all data and references
*/
destroy: function() {
this.walker.off("any-mutation", this.clearIndex);
},
clearIndex: function() {
if (!this.currentlyIndexing) {
this._data = null;
}
},
get doc() {
return this.walker.rootDoc;
},
/**
* Get the indexed data
* This getter also indexes if it hasn't been done yet or if the state is
* dirty
*
* @returns Map<String, Array<{type:String, node:DOMNode}>>
* A Map keyed on the searchable value, containing an array with
* objects containing the 'type' (one of ALL_RESULTS_TYPES), and
* the DOM Node.
*/
get data() {
if (!this._data) {
this._data = new Map();
this.index();
}
return this._data;
},
_addToIndex: function(type, node, value) {
// Add an entry for this value if there isn't one
let entry = this._data.get(value);
if (!entry) {
this._data.set(value, []);
}
// Add the type/node to the list
this._data.get(value).push({
type: type,
node: node
});
},
index: function() {
// Handle case where iterating nextNode() with the deepTreeWalker triggers
// a mutation (Bug 1222558)
this.currentlyIndexing = true;
let documentWalker = this.walker.getDocumentWalker(this.doc);
while (documentWalker.nextNode()) {
let node = documentWalker.currentNode;
if (node.nodeType === 1) {
// For each element node, we get the tagname and all attributes names
// and values
let localName = node.localName;
if (localName === "_moz_generated_content_before") {
this._addToIndex("tag", node, "::before");
this._addToIndex("text", node, node.textContent.trim());
} else if (localName === "_moz_generated_content_after") {
this._addToIndex("tag", node, "::after");
this._addToIndex("text", node, node.textContent.trim());
} else {
this._addToIndex("tag", node, node.localName);
}
for (let {name, value} of node.attributes) {
this._addToIndex("attributeName", node, name);
this._addToIndex("attributeValue", node, value);
}
} else if (node.textContent && node.textContent.trim().length) {
// For comments and text nodes, we get the text
this._addToIndex("text", node, node.textContent.trim());
}
}
this.currentlyIndexing = false;
}
};
exports.WalkerIndex = WalkerIndex;
/**
* The WalkerSearch class provides a way to search an indexed document as well
* as find elements that match a given css selector.
*
* Usage example:
* let s = new WalkerSearch(doc);
* let res = s.search("lang", index);
* for (let {matched, results} of res) {
* for (let {node, type} of results) {
* console.log("The query matched a node's " + type);
* console.log("Node that matched", node);
* }
* }
* s.destroy();
*
* @param {Walker} the walker to be searched
*/
function WalkerSearch(walker) {
this.walker = walker;
this.index = new WalkerIndex(this.walker);
}
WalkerSearch.prototype = {
destroy: function() {
this.index.destroy();
this.walker = null;
},
_addResult: function(node, type, results) {
if (!results.has(node)) {
results.set(node, []);
}
let matches = results.get(node);
// Do not add if the exact same result is already in the list
let isKnown = false;
for (let match of matches) {
if (match.type === type) {
isKnown = true;
break;
}
}
if (!isKnown) {
matches.push({type});
}
},
_searchIndex: function(query, options, results) {
for (let [matched, res] of this.index.data) {
if (!options.searchMethod(query, matched)) {
continue;
}
// Add any relevant results (skipping non-requested options).
res.filter(entry => {
return options.types.indexOf(entry.type) !== -1;
}).forEach(({node, type}) => {
this._addResult(node, type, results);
});
}
},
_searchSelectors: function(query, options, results) {
// If the query is just one "word", no need to search because _searchIndex
// will lead the same results since it has access to tagnames anyway
let isSelector = query && query.match(/[ >~.#\[\]]/);
if (options.types.indexOf("selector") === -1 || !isSelector) {
return;
}
let nodes = this.walker._multiFrameQuerySelectorAll(query);
for (let node of nodes) {
this._addResult(node, "selector", results);
}
},
/**
* Search the document
* @param {String} query What to search for
* @param {Object} options The following options are accepted:
* - searchMethod {String} one of WalkerSearch.SEARCH_METHOD_*
* defaults to WalkerSearch.SEARCH_METHOD_CONTAINS (does not apply to
* selector search type)
* - types {Array} a list of things to search for (tag, text, attributes, etc)
* defaults to WalkerSearch.ALL_RESULTS_TYPES
* @return {Array} An array is returned with each item being an object like:
* {
* node: <the dom node that matched>,
* type: <the type of match: one of WalkerSearch.ALL_RESULTS_TYPES>
* }
*/
search: function(query, options={}) {
options.searchMethod = options.searchMethod || WalkerSearch.SEARCH_METHOD_CONTAINS;
options.types = options.types || WalkerSearch.ALL_RESULTS_TYPES;
// Empty strings will return no results, as will non-string input
if (typeof query !== "string") {
query = "";
}
// Store results in a map indexed by nodes to avoid duplicate results
let results = new Map();
// Search through the indexed data
this._searchIndex(query, options, results);
// Search with querySelectorAll
this._searchSelectors(query, options, results);
// Concatenate all results into an Array to return
let resultList = [];
for (let [node, matches] of results) {
for (let {type} of matches) {
resultList.push({
node: node,
type: type,
});
// For now, just do one result per node since the frontend
// doesn't have a way to highlight each result individually
// yet.
break;
}
}
let documents = this.walker.tabActor.windows.map(win=>win.document);
// Sort the resulting nodes by order of appearance in the DOM
resultList.sort((a,b) => {
// Disconnected nodes won't get good results from compareDocumentPosition
// so check the order of their document instead.
if (a.node.ownerDocument != b.node.ownerDocument) {
let indA = documents.indexOf(a.node.ownerDocument);
let indB = documents.indexOf(b.node.ownerDocument);
return indA - indB;
}
// If the same document, then sort on DOCUMENT_POSITION_FOLLOWING (4)
// which means B is after A.
return a.node.compareDocumentPosition(b.node) & 4 ? -1 : 1;
});
return resultList;
}
};
WalkerSearch.SEARCH_METHOD_CONTAINS = (query, candidate) => {
return query && candidate.toLowerCase().indexOf(query.toLowerCase()) !== -1;
};
WalkerSearch.ALL_RESULTS_TYPES = ["tag", "text", "attributeName",
"attributeValue", "selector"];
exports.WalkerSearch = WalkerSearch;

View File

@ -10,6 +10,7 @@ support-files =
inspector_getImageData.html inspector_getImageData.html
inspector-delay-image-response.sjs inspector-delay-image-response.sjs
inspector-helpers.js inspector-helpers.js
inspector-search-data.html
inspector-styles-data.css inspector-styles-data.css
inspector-styles-data.html inspector-styles-data.html
inspector-traversal-data.html inspector-traversal-data.html
@ -77,6 +78,8 @@ skip-if = buildapp == 'mulet'
[test_inspector-remove.html] [test_inspector-remove.html]
[test_inspector-resolve-url.html] [test_inspector-resolve-url.html]
[test_inspector-retain.html] [test_inspector-retain.html]
[test_inspector-search.html]
[test_inspector-search-front.html]
[test_inspector-scroll-into-view.html] [test_inspector-scroll-into-view.html]
[test_inspector-traversal.html] [test_inspector-traversal.html]
[test_makeGlobalObjectReference.html] [test_makeGlobalObjectReference.html]

View File

@ -0,0 +1,52 @@
<html>
<head>
<meta charset="UTF-8">
<title>Inspector Search Test Data</title>
<style>
#pseudo {
display: block;
margin: 0;
}
#pseudo:before {
content: "before element";
}
#pseudo:after {
content: "after element";
}
</style>
<script type="text/javascript">
window.onload = function() {
window.opener.postMessage('ready', '*');
};
</script>
</head>
</body>
<!-- A comment
spread across multiple lines -->
<img width="100" height="100" src="large-image.jpg" />
<h1 id="pseudo">Heading 1</h1>
<p>A p tag with the text 'h1' inside of it.
<strong>A strong h1 result</strong>
</p>
<div id="arrows" northwest="↖" northeast="↗" southeast="↘" southwest="↙">
Unicode arrows
</div>
<h2>Heading 2</h2>
<h2>Heading 2</h2>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h3>Heading 3</h3>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h4>Heading 4</h4>
<h4>Heading 4</h4>
<div class="💩" id="💩" 💩="💩"></div>
</body>
</html>

View File

@ -0,0 +1,220 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=835896
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 835896</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
window.onload = function() {
const Cu = Components.utils;
Cu.import("resource://gre/modules/devtools/Loader.jsm");
const {Promise: promise} =
Cu.import("resource://gre/modules/Promise.jsm", {});
const {InspectorFront} =
devtools.require("devtools/server/actors/inspector");
const {console} =
Cu.import("resource://gre/modules/devtools/shared/Console.jsm", {});
SimpleTest.waitForExplicitFinish();
let walkerFront = null;
let inspectee = null;
let inspector = null;
// WalkerFront specific tests. These aren't to excercise search
// edge cases so much as to test the state the Front maintains between
// searches.
// See also test_inspector-search.html
addAsyncTest(function* setup() {
info ("Setting up inspector and walker actors.");
let url = document.getElementById("inspectorContent").href;
yield new Promise(resolve => {
attachURL(url, function(err, client, tab, doc) {
inspectee = doc;
inspector = InspectorFront(client, tab);
resolve();
});
});
walkerFront = yield inspector.getWalker();
ok(walkerFront, "getWalker() should return an actor.");
runNextTest();
});
addAsyncTest(function* testWalkerFrontDefaults() {
info ("Testing search API using WalkerFront.");
let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
let fronts = yield nodes.items();
let frontResult = yield walkerFront.search("");
ok(!frontResult, "Null result on front when searching for ''");
let results = yield walkerFront.search("h2");
isDeeply(results, {
node: fronts[0],
type: "search",
resultsIndex: 0,
resultsLength: 3
}, "Default options work");
results = yield walkerFront.search("h2", { });
isDeeply(results, {
node: fronts[1],
type: "search",
resultsIndex: 1,
resultsLength: 3
}, "Search works with empty options");
// Clear search data to remove result state on the front
yield walkerFront.search("");
runNextTest();
});
addAsyncTest(function* testMultipleSearches() {
info ("Testing search API using WalkerFront (reverse=false)");
let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
let fronts = yield nodes.items();
let results = yield walkerFront.search("h2");
isDeeply(results, {
node: fronts[0],
type: "search",
resultsIndex: 0,
resultsLength: 3
}, "Search works with multiple results (reverse=false)");
results = yield walkerFront.search("h2");
isDeeply(results, {
node: fronts[1],
type: "search",
resultsIndex: 1,
resultsLength: 3
}, "Search works with multiple results (reverse=false)");
results = yield walkerFront.search("h2");
isDeeply(results, {
node: fronts[2],
type: "search",
resultsIndex: 2,
resultsLength: 3
}, "Search works with multiple results (reverse=false)");
results = yield walkerFront.search("h2");
isDeeply(results, {
node: fronts[0],
type: "search",
resultsIndex: 0,
resultsLength: 3
}, "Search works with multiple results (reverse=false)");
// Clear search data to remove result state on the front
yield walkerFront.search("");
runNextTest();
});
addAsyncTest(function* testMultipleSearchesReverse() {
info ("Testing search API using WalkerFront (reverse=true)");
let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
let fronts = yield nodes.items();
let results = yield walkerFront.search("h2", {reverse: true});
isDeeply(results, {
node: fronts[2],
type: "search",
resultsIndex: 2,
resultsLength: 3
}, "Search works with multiple results (reverse=true)");
results = yield walkerFront.search("h2", {reverse: true});
isDeeply(results, {
node: fronts[1],
type: "search",
resultsIndex: 1,
resultsLength: 3
}, "Search works with multiple results (reverse=true)");
results = yield walkerFront.search("h2", {reverse: true});
isDeeply(results, {
node: fronts[0],
type: "search",
resultsIndex: 0,
resultsLength: 3
}, "Search works with multiple results (reverse=true)");
results = yield walkerFront.search("h2", {reverse: true});
isDeeply(results, {
node: fronts[2],
type: "search",
resultsIndex: 2,
resultsLength: 3
}, "Search works with multiple results (reverse=true)");
results = yield walkerFront.search("h2", {reverse: false});
isDeeply(results, {
node: fronts[0],
type: "search",
resultsIndex: 0,
resultsLength: 3
}, "Search works with multiple results (reverse=false)");
// Clear search data to remove result state on the front
yield walkerFront.search("");
runNextTest();
});
addAsyncTest(function* testBackwardsCompat() {
info ("Simulating a server that doesn't have the new search functionality.");
walkerFront.traits.textSearch = false;
let front = yield walkerFront.querySelector(walkerFront.rootNode, "h1");
let results = yield walkerFront.search("h1");
isDeeply(results, {
node: front,
type: "selector",
resultsIndex: 0,
resultsLength: 1
}, "Only querySelectorAll results being returned");
// Clear search data to remove result state on the front
yield walkerFront.search("");
// Reset the normal textSearch behavior
walkerFront.traits.textSearch = true;
results = yield walkerFront.search("h1");
isDeeply(results, {
node: front,
type: "search",
resultsIndex: 0,
resultsLength: 3
}, "Other results being included");
// Clear search data to remove result state on the front
yield walkerFront.search("");
runNextTest();
});
runNextTest();
};
</script>
</head>
<body>
<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -0,0 +1,300 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=835896
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 835896</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
window.onload = function() {
const Cu = Components.utils;
Cu.import("resource://gre/modules/devtools/Loader.jsm");
const {Promise: promise} =
Cu.import("resource://gre/modules/Promise.jsm", {});
const {InspectorFront} =
devtools.require("devtools/server/actors/inspector");
const {WalkerSearch, WalkerIndex} =
devtools.require("devtools/server/actors/utils/walker-search");
const {console} =
Cu.import("resource://gre/modules/devtools/shared/Console.jsm", {});
SimpleTest.waitForExplicitFinish();
let walkerActor = null;
let walkerSearch = null;
let inspectee = null;
let inspector = null;
// WalkerSearch specific tests. This is to make sure search results are
// coming back as expected.
// See also test_inspector-search-front.html.
addAsyncTest(function* setup() {
info ("Setting up inspector and walker actors.");
let url = document.getElementById("inspectorContent").href;
yield new Promise(resolve => {
attachURL(url, function(err, client, tab, doc) {
inspectee = doc;
inspector = InspectorFront(client, tab);
resolve();
});
});
let walkerFront = yield inspector.getWalker();
ok(walkerFront, "getWalker() should return an actor.");
let serverConnection = walkerFront.conn._transport._serverConnection;
walkerActor = serverConnection.getActor(walkerFront.actorID);
ok(walkerActor,
"Got a reference to the walker actor (" + walkerFront.actorID + ")");
walkerSearch = walkerActor.walkerSearch;
runNextTest();
});
addAsyncTest(function* testIndexExists() {
info ("Testing basic index APIs exist.");
let index = new WalkerIndex(walkerActor);
ok(index.data.size > 0, "public index is filled after getting");
index.clearIndex();
ok(!index._data, "private index is empty after clearing");
ok(index.data.size > 0, "public index is filled after getting");
index.destroy();
runNextTest();
});
addAsyncTest(function* testSearchExists() {
info ("Testing basic search APIs exist.");
ok(walkerSearch, "walker search exists on the WalkerActor");
ok(walkerSearch.search, "walker search has `search` method");
ok(walkerSearch.index, "walker search has `index` property");
is(walkerSearch.walker, walkerActor, "referencing the correct WalkerActor");
let search = new WalkerSearch(walkerActor);
ok(search, "a new search instance can be created");
ok(search.search, "new search instance has `search` method");
ok(search.index, "new search instance has `index` property");
isnot(search, walkerSearch, "new search instance differs from the WalkerActor's");
search.destroy();
runNextTest();
});
addAsyncTest(function* testEmptySearch() {
info ("Testing search with an empty query.");
results = walkerSearch.search("");
is(results.length, 0, "No results when searching for ''");
results = walkerSearch.search(null);
is(results.length, 0, "No results when searching for null");
results = walkerSearch.search(undefined);
is(results.length, 0, "No results when searching for undefined");
results = walkerSearch.search(10);
is(results.length, 0, "No results when searching for 10");
runNextTest();
});
addAsyncTest(function* testBasicSearchData() {
let testData = [
{
desc: "Search for tag with one result.",
search: "body",
expected: [
{node: inspectee.body, type: "tag"}
]
},
{
desc: "Search for tag with multiple results",
search: "h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "tag"},
{node: inspectee.querySelectorAll("h2")[1], type: "tag"},
{node: inspectee.querySelectorAll("h2")[2], type: "tag"},
]
},
{
desc: "Search for selector with multiple results",
search: "body > h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "selector"},
{node: inspectee.querySelectorAll("h2")[1], type: "selector"},
{node: inspectee.querySelectorAll("h2")[2], type: "selector"},
]
},
{
desc: "Search for selector with multiple results",
search: ":root h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "selector"},
{node: inspectee.querySelectorAll("h2")[1], type: "selector"},
{node: inspectee.querySelectorAll("h2")[2], type: "selector"},
]
},
{
desc: "Search for selector with multiple results",
search: "* h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "selector"},
{node: inspectee.querySelectorAll("h2")[1], type: "selector"},
{node: inspectee.querySelectorAll("h2")[2], type: "selector"},
]
},
{
desc: "Search with multiple matches in a single tag expecting a single result",
search: "💩",
expected: [
{node: inspectee.getElementById("💩"), type: "attributeName"}
]
},
{
desc: "Search that has tag and text results",
search: "h1",
expected: [
{node: inspectee.querySelector("h1"), type: "tag"},
{node: inspectee.querySelector("h1 + p").childNodes[0], type: "text"},
{node: inspectee.querySelector("h1 + p > strong").childNodes[0], type: "text"},
]
},
]
for (let {desc, search, expected} of testData) {
info("Running test: " + desc);
let results = walkerSearch.search(search);
isDeeply(results, expected,
"Search returns correct results with '" + search + "'");
}
runNextTest();
});
addAsyncTest(function* testPseudoElements() {
info ("Testing ::before and ::after element matching");
let beforeElt = new _documentWalker(inspectee.querySelector("#pseudo"),
inspectee.defaultView).firstChild();
let afterElt = new _documentWalker(inspectee.querySelector("#pseudo"),
inspectee.defaultView).lastChild();
let styleText = inspectee.querySelector("style").childNodes[0];
// ::before
let results = walkerSearch.search("::before");
isDeeply(results, [ {node: beforeElt, type: "tag"} ],
"Tag search works for pseudo element");
results = walkerSearch.search("_moz_generated_content_before");
is(results.length, 0, "No results for anon tag name");
results = walkerSearch.search("before element");
isDeeply(results, [
{node: styleText, type: "text"},
{node: beforeElt, type: "text"}
], "Text search works for pseudo element");
// ::after
results = walkerSearch.search("::after");
isDeeply(results, [ {node: afterElt, type: "tag"} ],
"Tag search works for pseudo element");
results = walkerSearch.search("_moz_generated_content_after");
is(results.length, 0, "No results for anon tag name");
results = walkerSearch.search("after element");
isDeeply(results, [
{node: styleText, type: "text"},
{node: afterElt, type: "text"}
], "Text search works for pseudo element");
runNextTest();
});
addAsyncTest(function* testSearchMutationChangeResults() {
info ("Testing search before and after a mutation.");
let expected = [
{node: inspectee.querySelectorAll("h3")[0], type: "tag"},
{node: inspectee.querySelectorAll("h3")[1], type: "tag"},
{node: inspectee.querySelectorAll("h3")[2], type: "tag"},
];
let results = walkerSearch.search("h3");
isDeeply(results, expected, "Search works with tag results");
yield mutateDocumentAndWaitForMutation(() => {
expected[0].node.remove();
});
results = walkerSearch.search("h3");
isDeeply(results, [
expected[1],
expected[2]
], "Results are updated after removal");
yield new Promise(resolve => {
info("Waiting for a mutation to happen");
let observer = new inspectee.defaultView.MutationObserver(() => {
resolve();
});
observer.observe(inspectee, {attributes: true, subtree: true});
inspectee.body.setAttribute("h3", "true");
});
results = walkerSearch.search("h3");
isDeeply(results, [
{node: inspectee.body, type: "attributeName"},
expected[1],
expected[2]
], "Results are updated after addition");
yield new Promise(resolve => {
info("Waiting for a mutation to happen");
let observer = new inspectee.defaultView.MutationObserver(() => {
resolve();
});
observer.observe(inspectee, {attributes: true, childList: true, subtree: true});
inspectee.body.removeAttribute("h3");
expected[1].node.remove();
expected[2].node.remove();
});
results = walkerSearch.search("h3");
is(results.length, 0, "Results are updated after removal");
runNextTest();
});
runNextTest();
function mutateDocumentAndWaitForMutation(mutationFn) {
return new Promise(resolve => {
info("Listening to markup mutation on the inspectee");
let observer = new inspectee.defaultView.MutationObserver(resolve);
observer.observe(inspectee, {childList: true, subtree: true});
mutationFn();
});
}
};
</script>
</head>
<body>
<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -100,6 +100,13 @@ const TEST_DATA = [
column: 4, column: 4,
expected: {offset: 7, text: "border:1px solid red;content: '}';color:red;"} expected: {offset: 7, text: "border:1px solid red;content: '}';color:red;"}
}, },
{
desc: "Rule contains no tokens",
input: "div{}",
line: 1,
column: 1,
expected: {offset: 4, text: ""}
},
]; ];
function run_test() { function run_test() {

View File

@ -75,45 +75,31 @@ Concrete<DeserializedNode>::size(mozilla::MallocSizeOf mallocSizeof) const
class DeserializedEdgeRange : public EdgeRange class DeserializedEdgeRange : public EdgeRange
{ {
EdgeVector edges; DeserializedNode* node;
size_t i; Edge currentEdge;
size_t i;
void settle() { void settle() {
front_ = i < edges.length() ? &edges[i] : nullptr; if (i >= node->edges.length()) {
front_ = nullptr;
return;
}
auto& edge = node->edges[i];
auto referent = node->getEdgeReferent(edge);
currentEdge = mozilla::Move(Edge(edge.name ? NS_strdup(edge.name) : nullptr,
referent));
front_ = &currentEdge;
} }
public: public:
explicit DeserializedEdgeRange() explicit DeserializedEdgeRange(DeserializedNode& node)
: edges() : node(&node)
, i(0) , i(0)
{ {
settle(); settle();
} }
bool init(DeserializedNode& node)
{
if (!edges.reserve(node.edges.length()))
return false;
for (DeserializedEdge* edgep = node.edges.begin();
edgep != node.edges.end();
edgep++)
{
char16_t* name = nullptr;
if (edgep->name) {
name = NS_strdup(edgep->name);
if (!name)
return false;
}
auto referent = node.getEdgeReferent(*edgep);
edges.infallibleAppend(mozilla::Move(Edge(name, referent)));
}
settle();
return true;
}
void popFront() override void popFront() override
{ {
i++; i++;
@ -138,9 +124,9 @@ UniquePtr<EdgeRange>
Concrete<DeserializedNode>::edges(JSRuntime* rt, bool) const Concrete<DeserializedNode>::edges(JSRuntime* rt, bool) const
{ {
UniquePtr<DeserializedEdgeRange, JS::DeletePolicy<DeserializedEdgeRange>> range( UniquePtr<DeserializedEdgeRange, JS::DeletePolicy<DeserializedEdgeRange>> range(
js_new<DeserializedEdgeRange>()); js_new<DeserializedEdgeRange>(get()));
if (!range || !range->init(get())) if (!range)
return nullptr; return nullptr;
return UniquePtr<EdgeRange>(range.release()); return UniquePtr<EdgeRange>(range.release());

View File

@ -68,9 +68,7 @@ DEF_TEST(DeserializedNodeUbiNodes, {
10)); 10));
DeserializedEdge edge1(referent1->id); DeserializedEdge edge1(referent1->id);
mocked.addEdge(Move(edge1)); mocked.addEdge(Move(edge1));
EXPECT_CALL(mocked, EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent1->id)))
getEdgeReferent(Field(&DeserializedEdge::referent,
referent1->id)))
.Times(1) .Times(1)
.WillOnce(Return(JS::ubi::Node(referent1.get()))); .WillOnce(Return(JS::ubi::Node(referent1.get())));
@ -79,9 +77,7 @@ DEF_TEST(DeserializedNodeUbiNodes, {
20)); 20));
DeserializedEdge edge2(referent2->id); DeserializedEdge edge2(referent2->id);
mocked.addEdge(Move(edge2)); mocked.addEdge(Move(edge2));
EXPECT_CALL(mocked, EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent2->id)))
getEdgeReferent(Field(&DeserializedEdge::referent,
referent2->id)))
.Times(1) .Times(1)
.WillOnce(Return(JS::ubi::Node(referent2.get()))); .WillOnce(Return(JS::ubi::Node(referent2.get())));
@ -90,11 +86,15 @@ DEF_TEST(DeserializedNodeUbiNodes, {
30)); 30));
DeserializedEdge edge3(referent3->id); DeserializedEdge edge3(referent3->id);
mocked.addEdge(Move(edge3)); mocked.addEdge(Move(edge3));
EXPECT_CALL(mocked, EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent3->id)))
getEdgeReferent(Field(&DeserializedEdge::referent,
referent3->id)))
.Times(1) .Times(1)
.WillOnce(Return(JS::ubi::Node(referent3.get()))); .WillOnce(Return(JS::ubi::Node(referent3.get())));
ubi.edges(rt); auto range = ubi.edges(rt);
ASSERT_TRUE(!!range);
for ( ; !range->empty(); range->popFront()) {
// Nothing to do here. This loop ensures that we get each edge referent
// that we expect above.
}
}); });

View File

@ -278,6 +278,12 @@ MATCHER(UniqueIsNull, "") {
return arg.get() == nullptr; return arg.get() == nullptr;
} }
// Matches an edge whose referent is the node with the given id.
MATCHER_P(EdgeTo, id, "") {
return Matcher<const DeserializedEdge&>(Field(&DeserializedEdge::referent, id))
.MatchAndExplain(arg, result_listener);
}
} // namespace testing } // namespace testing

View File

@ -46,7 +46,7 @@ interface nsIContentSecurityManager : nsISupports
* https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy * https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
* *
* This method should only be used when the context of the URI isn't available * This method should only be used when the context of the URI isn't available
* since isSecureContext is preffered as it handles parent contexts. * since isSecureContext is preferred as it handles parent contexts.
* *
* This method returns false instead of throwing upon errors. * This method returns false instead of throwing upon errors.
*/ */

View File

@ -1015,7 +1015,7 @@ TelephonyService.prototype = {
aCallback.notifyDialMMI(mmiServiceCode); aCallback.notifyDialMMI(mmiServiceCode);
if (mmiServiceCode !== RIL.MMI_KS_SC_IMEI && !this._isRadioOn(aClientId)) { if (mmiServiceCode !== MMI_KS_SC_IMEI && !this._isRadioOn(aClientId)) {
aCallback.notifyDialMMIError(DIAL_ERROR_RADIO_NOT_AVAILABLE); aCallback.notifyDialMMIError(DIAL_ERROR_RADIO_NOT_AVAILABLE);
return; return;
} }

View File

@ -19,8 +19,12 @@ function testGettingIMEI() {
} }
// Start test // Start test
startTest(function() { startTestWithPermissions(['mobileconnection'], function() {
testGettingIMEI() Promise.resolve()
.then(() => gSetRadioEnabledAll(false))
.then(() => testGettingIMEI())
.then(() => gSetRadioEnabledAll(true))
.then(() => testGettingIMEI())
.catch(error => ok(false, "Promise reject: " + error)) .catch(error => ok(false, "Promise reject: " + error))
.then(finish); .then(finish);
}); });

View File

@ -3191,7 +3191,11 @@ BytecodeEmitter::emitSwitch(ParseNode* pn)
/* Emit code for evaluating cases and jumping to case statements. */ /* Emit code for evaluating cases and jumping to case statements. */
for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) { for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) {
ParseNode* caseValue = caseNode->pn_left; ParseNode* caseValue = caseNode->pn_left;
if (caseValue && !emitTree(caseValue)) // If the expression is a literal, suppress line number
// emission so that debugging works more naturally.
if (caseValue &&
!emitTree(caseValue, caseValue->isLiteral() ? SUPPRESS_LINENOTE :
EMIT_LINENOTE))
return false; return false;
if (!beforeCases) { if (!beforeCases) {
/* prevCaseOffset is the previous JSOP_CASE's bytecode offset. */ /* prevCaseOffset is the previous JSOP_CASE's bytecode offset. */
@ -7554,7 +7558,7 @@ BytecodeEmitter::emitClass(ParseNode* pn)
} }
bool bool
BytecodeEmitter::emitTree(ParseNode* pn) BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote)
{ {
JS_CHECK_RECURSION(cx, return false); JS_CHECK_RECURSION(cx, return false);
@ -7567,7 +7571,7 @@ BytecodeEmitter::emitTree(ParseNode* pn)
/* Emit notes to tell the current bytecode's source line number. /* Emit notes to tell the current bytecode's source line number.
However, a couple trees require special treatment; see the However, a couple trees require special treatment; see the
relevant emitter functions for details. */ relevant emitter functions for details. */
if (pn->getKind() != PNK_WHILE && pn->getKind() != PNK_FOR && if (emitLineNote == EMIT_LINENOTE && pn->getKind() != PNK_WHILE && pn->getKind() != PNK_FOR &&
!updateLineNumberNotes(pn->pn_pos.begin)) !updateLineNumberNotes(pn->pn_pos.begin))
return false; return false;

View File

@ -336,8 +336,14 @@ struct BytecodeEmitter
void setJumpOffsetAt(ptrdiff_t off); void setJumpOffsetAt(ptrdiff_t off);
// Control whether emitTree emits a line number note.
enum EmitLineNumberNote {
EMIT_LINENOTE,
SUPPRESS_LINENOTE
};
// Emit code for the tree rooted at pn. // Emit code for the tree rooted at pn.
bool emitTree(ParseNode* pn); bool emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote = EMIT_LINENOTE);
// Emit function code for the tree rooted at body. // Emit function code for the tree rooted at body.
bool emitFunctionScript(ParseNode* body); bool emitFunctionScript(ParseNode* body);

View File

@ -0,0 +1,46 @@
// Test how stepping interacts with switch statements.
var g = newGlobal();
g.eval('function bob() { return "bob"; }');
// Stepping into a sparse switch should not stop on literal cases.
evaluate(`function lit(x) { // 1
debugger; // 2
switch(x) { // 3
case "nope": // 4
break; // 5
case "bob": // 6
break; // 7
} // 8
}`, {lineNumber: 1, global: g});
// Stepping into a sparse switch should stop on non-literal cases.
evaluate(`function nonlit(x) { // 1
debugger; // 2
switch(x) { // 3
case bob(): // 4
break; // 5
} // 6
}`, {lineNumber: 1, global: g});
var dbg = Debugger(g);
var badStep = false;
function test(s, okLine) {
dbg.onDebuggerStatement = function(frame) {
frame.onStep = function() {
let thisLine = this.script.getOffsetLocation(this.offset).lineNumber;
// The stop at line 3 is the switch.
if (thisLine > 3) {
assertEq(thisLine, okLine)
frame.onStep = undefined;
}
};
};
g.eval(s);
}
test("lit('bob');", 7);
test("nonlit('bob');", 4);

View File

@ -735,6 +735,7 @@ just addresses the organization to follow, e.g. "This site is run by " -->
<!ENTITY restriction_disallow_master_password_title2 "Disable master password"> <!ENTITY restriction_disallow_master_password_title2 "Disable master password">
<!ENTITY restriction_disallow_guest_browsing_title2 "Disable Guest Browsing"> <!ENTITY restriction_disallow_guest_browsing_title2 "Disable Guest Browsing">
<!ENTITY restriction_disallow_advanced_settings_title "Disable Advanced Settings"> <!ENTITY restriction_disallow_advanced_settings_title "Disable Advanced Settings">
<!ENTITY restriction_disallow_camera_microphone_title "Block camera and microphone">
<!-- Default Bookmarks titles--> <!-- Default Bookmarks titles-->
<!-- LOCALIZATION NOTE (bookmarks_about_browser): link title for about:fennec --> <!-- LOCALIZATION NOTE (bookmarks_about_browser): link title for about:fennec -->

View File

@ -28,7 +28,8 @@ public class RestrictedProfileConfiguration implements RestrictionConfiguration
Restriction.DISALLOW_MASTER_PASSWORD, Restriction.DISALLOW_MASTER_PASSWORD,
Restriction.DISALLOW_GUEST_BROWSING, Restriction.DISALLOW_GUEST_BROWSING,
Restriction.DISALLOW_DEFAULT_THEME, Restriction.DISALLOW_DEFAULT_THEME,
Restriction.DISALLOW_ADVANCED_SETTINGS Restriction.DISALLOW_ADVANCED_SETTINGS,
Restriction.DISALLOW_CAMERA_MICROPHONE
); );
private Context context; private Context context;

View File

@ -52,7 +52,9 @@ public enum Restriction {
DISALLOW_DEFAULT_THEME(17, "no_default_theme", 0), DISALLOW_DEFAULT_THEME(17, "no_default_theme", 0),
DISALLOW_ADVANCED_SETTINGS(18, "no_advanced_settings", R.string.restriction_disallow_advanced_settings_title); DISALLOW_ADVANCED_SETTINGS(18, "no_advanced_settings", R.string.restriction_disallow_advanced_settings_title),
DISALLOW_CAMERA_MICROPHONE(19, "no_camera_microphone", R.string.restriction_disallow_camera_microphone_title);
public final int id; public final int id;
public final String name; public final String name;

View File

@ -579,6 +579,7 @@
<string name="restriction_disallow_master_password_title">&restriction_disallow_master_password_title2;</string> <string name="restriction_disallow_master_password_title">&restriction_disallow_master_password_title2;</string>
<string name="restriction_disallow_guest_browsing_title">&restriction_disallow_guest_browsing_title2;</string> <string name="restriction_disallow_guest_browsing_title">&restriction_disallow_guest_browsing_title2;</string>
<string name="restriction_disallow_advanced_settings_title">&restriction_disallow_advanced_settings_title;</string> <string name="restriction_disallow_advanced_settings_title">&restriction_disallow_advanced_settings_title;</string>
<string name="restriction_disallow_camera_microphone_title">&restriction_disallow_camera_microphone_title;</string>
<!-- Miscellaneous --> <!-- Miscellaneous -->
<string name="ellipsis">&ellipsis;</string> <string name="ellipsis">&ellipsis;</string>

View File

@ -6,6 +6,7 @@
this.EXPORTED_SYMBOLS = ["WebrtcUI"]; this.EXPORTED_SYMBOLS = ["WebrtcUI"];
XPCOMUtils.defineLazyModuleGetter(this, "Notifications", "resource://gre/modules/Notifications.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Notifications", "resource://gre/modules/Notifications.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "ParentalControls", "@mozilla.org/parental-controls-service;1", "nsIParentalControlsService");
var WebrtcUI = { var WebrtcUI = {
_notificationId: null, _notificationId: null,
@ -108,6 +109,12 @@ var WebrtcUI = {
contentWindow.navigator.mozGetUserMediaDevices( contentWindow.navigator.mozGetUserMediaDevices(
constraints, constraints,
function (devices) { function (devices) {
if (!ParentalControls.isAllowed(ParentalControls.CAMERA_MICROPHONE)) {
Services.obs.notifyObservers(null, "getUserMedia:response:deny", aSubject.callID);
WebrtcUI.showBlockMessage(devices);
return;
}
WebrtcUI.prompt(contentWindow, aSubject.callID, constraints.audio, WebrtcUI.prompt(contentWindow, aSubject.callID, constraints.audio,
constraints.video, devices); constraints.video, devices);
}, },
@ -187,6 +194,31 @@ var WebrtcUI = {
} }
}, },
showBlockMessage: function(aDevices) {
let microphone = false;
let camera = false;
for (let device of aDevices) {
device = device.QueryInterface(Ci.nsIMediaDevice);
if (device.type == "audio") {
microphone = true;
} else if (device.type == "video") {
camera = true;
}
}
let message;
if (microphone && !camera) {
message = Strings.browser.GetStringFromName("getUserMedia.blockedMicrophoneAccess");
} else if (camera && !microphone) {
message = Strings.browser.GetStringFromName("getUserMedia.blockedCameraAccess");
} else {
message = Strings.browser.GetStringFromName("getUserMedia.blockedCameraAndMicrophoneAccess");
}
NativeWindow.doorhanger.show(message, "webrtc-blocked", [], BrowserApp.selectedTab.id, {});
},
prompt: function prompt(aContentWindow, aCallID, aAudioRequested, prompt: function prompt(aContentWindow, aCallID, aAudioRequested,
aVideoRequested, aDevices) { aVideoRequested, aDevices) {
let audioDevices = []; let audioDevices = [];

View File

@ -397,6 +397,9 @@ getUserMedia.audioDevice.prompt = Microphone to use
getUserMedia.sharingCamera.message2 = Camera is on getUserMedia.sharingCamera.message2 = Camera is on
getUserMedia.sharingMicrophone.message2 = Microphone is on getUserMedia.sharingMicrophone.message2 = Microphone is on
getUserMedia.sharingCameraAndMicrophone.message2 = Camera and microphone are on getUserMedia.sharingCameraAndMicrophone.message2 = Camera and microphone are on
getUserMedia.blockedCameraAccess = Camera has been blocked.
getUserMedia.blockedMicrophoneAccess = Microphone has been blocked.
getUserMedia.blockedCameraAndMicrophoneAccess = Camera and microphone have been blocked.
# LOCALIZATION NOTE (readerMode.toolbarTip): # LOCALIZATION NOTE (readerMode.toolbarTip):
# Tip shown to users the first time we hide the reader mode toolbar. # Tip shown to users the first time we hide the reader mode toolbar.

View File

@ -8,6 +8,7 @@
#include "MainThreadUtils.h" #include "MainThreadUtils.h"
#include "SharedSSLState.h" #include "SharedSSLState.h"
#include "nss.h"
using namespace mozilla; using namespace mozilla;
using namespace mozilla::psm; using namespace mozilla::psm;
@ -58,5 +59,9 @@ WeakCryptoOverride::RemoveWeakCryptoOverride(const nsACString& aHostName,
const nsPromiseFlatCString& host = PromiseFlatCString(aHostName); const nsPromiseFlatCString& host = PromiseFlatCString(aHostName);
sharedState->IOLayerHelpers().removeInsecureFallbackSite(host, aPort); sharedState->IOLayerHelpers().removeInsecureFallbackSite(host, aPort);
// Some servers will fail with SSL_ERROR_ILLEGAL_PARAMETER_ALERT
// unless the session cache is cleared.
SSL_ClearSessionCache();
return NS_OK; return NS_OK;
} }

View File

@ -11,7 +11,7 @@ interface nsIFile;
interface nsIInterfaceRequestor; interface nsIInterfaceRequestor;
interface nsIArray; interface nsIArray;
[scriptable, uuid(8c4962aa-e0e0-482e-b1db-7846cb78b3d6)] [scriptable, uuid(ca8eb4f8-89bc-4479-91c9-1f381e045ed7)]
interface nsIParentalControlsService : nsISupports interface nsIParentalControlsService : nsISupports
{ {
/** /**
@ -35,6 +35,7 @@ interface nsIParentalControlsService : nsISupports
const short GUEST_BROWSING = 16; // Disallow usage of guest browsing const short GUEST_BROWSING = 16; // Disallow usage of guest browsing
const short DEFAULT_THEME = 17; // Use default theme or a special parental controls theme const short DEFAULT_THEME = 17; // Use default theme or a special parental controls theme
const short ADVANCED_SETTINGS = 18; // Advanced settings const short ADVANCED_SETTINGS = 18; // Advanced settings
const short CAMERA_MICROPHONE = 19; // Camera and microphone (WebRTC)
/** /**
* @returns true if the current user account has parental controls * @returns true if the current user account has parental controls

View File

@ -22,6 +22,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper", XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm"); "resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager",
"@mozilla.org/contentsecuritymanager;1",
"nsIContentSecurityManager");
XPCOMUtils.defineLazyGetter(this, "log", () => { XPCOMUtils.defineLazyGetter(this, "log", () => {
let logger = LoginHelper.createLogger("LoginManagerContent"); let logger = LoginHelper.createLogger("LoginManagerContent");
return logger.log.bind(logger); return logger.log.bind(logger);
@ -1130,7 +1134,9 @@ var LoginManagerContent = {
let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil); let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
let ph = Ci.nsIProtocolHandler; let ph = Ci.nsIProtocolHandler;
if (netutil.URIChainHasFlags(uri, ph.URI_IS_LOCAL_RESOURCE) || // Is the connection to localhost? Consider localhost safe for passwords.
if (gContentSecurityManager.isURIPotentiallyTrustworthy(uri) ||
netutil.URIChainHasFlags(uri, ph.URI_IS_LOCAL_RESOURCE) ||
netutil.URIChainHasFlags(uri, ph.URI_DOES_NOT_RETURN_DATA) || netutil.URIChainHasFlags(uri, ph.URI_DOES_NOT_RETURN_DATA) ||
netutil.URIChainHasFlags(uri, ph.URI_INHERITS_SECURITY_CONTEXT) || netutil.URIChainHasFlags(uri, ph.URI_INHERITS_SECURITY_CONTEXT) ||
netutil.URIChainHasFlags(uri, ph.URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT)) { netutil.URIChainHasFlags(uri, ph.URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT)) {

View File

@ -764,7 +764,6 @@ var Impl = {
// task to complete, but TelemetryStorage blocks on it during shutdown. // task to complete, but TelemetryStorage blocks on it during shutdown.
TelemetryStorage.runCleanPingArchiveTask(); TelemetryStorage.runCleanPingArchiveTask();
Telemetry.asyncFetchTelemetryData(function () {});
this._delayedInitTaskDeferred.resolve(); this._delayedInitTaskDeferred.resolve();
} catch (e) { } catch (e) {
this._delayedInitTaskDeferred.reject(e); this._delayedInitTaskDeferred.reject(e);

View File

@ -174,6 +174,9 @@ var ClientIDImpl = {
// Not yet loaded, return the cached client id if we have one. // Not yet loaded, return the cached client id if we have one.
let id = Preferences.get(PREF_CACHED_CLIENTID, null); let id = Preferences.get(PREF_CACHED_CLIENTID, null);
if (id === null) {
return null;
}
if (!isValidClientID(id)) { if (!isValidClientID(id)) {
this._log.error("getCachedClientID - invalid client id in preferences, resetting", id); this._log.error("getCachedClientID - invalid client id in preferences, resetting", id);
Preferences.reset(PREF_CACHED_CLIENTID); Preferences.reset(PREF_CACHED_CLIENTID);