--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1109,13 +1109,15 @@ pref("dom.requestSync.enabled", true);
// Only enable for kit kat and above devices
// kit kat == 19, L = 21, 20 is kit-kat for wearables
// 15 is for the ICS emulators which will fallback to software vsync
#if ANDROID_VERSION == 19 || ANDROID_VERSION == 21 || ANDROID_VERSION == 15
pref("gfx.vsync.hw-vsync.enabled", true);
pref("gfx.vsync.compositor", true);
pref("gfx.touch.resample", true);
+pref("gfx.vsync.refreshdriver", true);
#else
pref("gfx.vsync.hw-vsync.enabled", false);
pref("gfx.vsync.compositor", false);
pref("gfx.touch.resample", false);
+pref("gfx.vsync.refreshdriver", false);
#endif
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
<!--original fetch url was git://codeaurora.org/-->
<remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
<!--original fetch url was https://git.mozilla.org/releases-->
<remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
<!-- B2G specific things. -->
<project name="platform_build" path="build" remote="b2g" revision="cdaa0a4ac28c781709df8c318ed079e9e475503a">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
- <project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+ <project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
<!--original fetch url was git://github.com/apitrace/-->
<remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
<default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
<!-- Gonk specific things and forks -->
<project name="platform_build" path="build" remote="b2g" revision="6295c4eb38de793159368aa7f745ef3faf7208aa">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
- <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+ <project name="gaia.git" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eb1795a9002eb142ac58c8d68f8f4ba094af07ca"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="2c31ac3a31a340b40ecd9c291df9b9613d3afa72"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
<!--original fetch url was https://git.mozilla.org/releases-->
<remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
<!-- B2G specific things. -->
<project name="platform_build" path="build" remote="b2g" revision="ac778ae59be38aea284a04c89640b1a11c26a5de">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
- <project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+ <project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
<project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
<!--original fetch url was git://codeaurora.org/-->
<remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
<!--original fetch url was https://git.mozilla.org/releases-->
<remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
<!-- B2G specific things. -->
<project name="platform_build" path="build" remote="b2g" revision="cdaa0a4ac28c781709df8c318ed079e9e475503a">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
- <project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+ <project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
<!--original fetch url was git://github.com/apitrace/-->
<remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
<default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
<!-- Gonk specific things and forks -->
<project name="platform_build" path="build" remote="b2g" revision="6295c4eb38de793159368aa7f745ef3faf7208aa">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
- <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+ <project name="gaia.git" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eb1795a9002eb142ac58c8d68f8f4ba094af07ca"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="2c31ac3a31a340b40ecd9c291df9b9613d3afa72"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
<!--original fetch url was git://codeaurora.org/-->
<remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
<!--original fetch url was https://git.mozilla.org/releases-->
<remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
<!-- B2G specific things. -->
<project name="platform_build" path="build" remote="b2g" revision="cdaa0a4ac28c781709df8c318ed079e9e475503a">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
- <project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+ <project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
<!--original fetch url was https://git.mozilla.org/releases-->
<remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
<!-- B2G specific things. -->
<project name="platform_build" path="build" remote="b2g" revision="ac778ae59be38aea284a04c89640b1a11c26a5de">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
- <project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+ <project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
<project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
{
"git": {
- "git_revision": "f0b93e0668ef9565bd6f050b15b4f794d59feb65",
+ "git_revision": "ae02fbdeae77b2002cebe33c61aedeee4b9439fd",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
- "revision": "d170342f8e1837435749392fc2bded3b9fee01d4",
+ "revision": "62d026a98ea42f2b93de000e8d0d4f1254f86730",
"repo_path": "integration/gaia-central"
}
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
<!--original fetch url was https://git.mozilla.org/releases-->
<remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
<!-- B2G specific things. -->
<project name="platform_build" path="build" remote="b2g" revision="ac778ae59be38aea284a04c89640b1a11c26a5de">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
- <project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+ <project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
<project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
<!--original fetch url was git://codeaurora.org/-->
<remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
<!--original fetch url was https://git.mozilla.org/releases-->
<remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
<!-- B2G specific things. -->
<project name="platform_build" path="build" remote="b2g" revision="7f2ee9f4cb926684883fc2a2e407045fd9db2199">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
- <project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+ <project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
<?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1423263875000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1423697587000">
<emItems>
<emItem blockID="i58" id="webmaster@buzzzzvideos.info">
<versionRange minVersion="0" maxVersion="*">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i71" id="youtube@2youtube.com">
@@ -216,16 +216,36 @@
</prefs>
</emItem>
<emItem blockID="i93" id="{68b8676b-99a5-46d1-b390-22411d8bcd61}">
<versionRange minVersion="0" maxVersion="*">
</versionRange>
<prefs>
</prefs>
</emItem>
+ <emItem blockID="i858" id="fftoolbar2014@etech.com">
+ <versionRange minVersion="0" maxVersion="*" severity="1">
+ </versionRange>
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ </emItem>
+ <emItem blockID="i854" id="/^(7tG@zEb\.net|ru@gfK0J\.edu)$/">
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ </versionRange>
+ <prefs>
+ </prefs>
+ </emItem>
+ <emItem blockID="i850" id="P2@D.edu">
+ <versionRange minVersion="0" maxVersion="*" severity="1">
+ </versionRange>
+ <prefs>
+ </prefs>
+ </emItem>
<emItem blockID="i554" id="lightningnewtab@gmail.com">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i348" id="{13c9f1f9-2322-4d5c-81df-6d4bf8476ba4}">
<versionRange minVersion="0" maxVersion="*" severity="1">
@@ -264,16 +284,22 @@
</prefs>
</emItem>
<emItem blockID="i620" id="{21EAF666-26B3-4A3C-ABD0-CA2F5A326744}">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
<prefs>
</prefs>
</emItem>
+ <emItem blockID="i856" id="/^({94d62e35-4b43-494c-bf52-ba5935df36ef}|firefox@advanceelite\.com|{bb7b7a60-f574-47c2-8a0b-4c56f2da9802})$/">
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ </versionRange>
+ <prefs>
+ </prefs>
+ </emItem>
<emItem blockID="i482" id="brasilescapeeight@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i17" id="{3252b9ae-c69a-4eaf-9502-dc9c1f6c009e}">
<versionRange minVersion="2.2" maxVersion="2.2">
@@ -448,16 +474,22 @@
</prefs>
</emItem>
<emItem blockID="i744" id="{84a93d51-b7a9-431e-8ff8-d60e5d7f5df1}">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
<prefs>
</prefs>
</emItem>
+ <emItem blockID="i848" id="bcVX5@nQm9l.org">
+ <versionRange minVersion="0" maxVersion="*" severity="1">
+ </versionRange>
+ <prefs>
+ </prefs>
+ </emItem>
<emItem blockID="i431" id="chinaescapeone@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i806" id="{d9284e50-81fc-11da-a72b-0800200c9a66}">
<versionRange minVersion="0" maxVersion="7.7.34" severity="1">
@@ -1540,16 +1572,22 @@
</prefs>
</emItem>
<emItem blockID="i726" id="{d87d56b2-1379-49f4-b081-af2850c79d8e}">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
<prefs>
</prefs>
</emItem>
+ <emItem blockID="i852" id="6lIy@T.edu">
+ <versionRange minVersion="0" maxVersion="*" severity="1">
+ </versionRange>
+ <prefs>
+ </prefs>
+ </emItem>
<emItem blockID="i505" id="extacylife@a.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i86" id="{45147e67-4020-47e2-8f7a-55464fb535aa}">
<versionRange minVersion="0" maxVersion="*">
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -410,8 +410,15 @@
accesskey="&bidiSwitchPageDirectionItem.accesskey;"
oncommand="gContextMenu.switchPageDirection();"/>
<menuseparator id="inspect-separator" hidden="true"/>
<menuitem id="context-inspect"
hidden="true"
label="&inspectContextMenu.label;"
accesskey="&inspectContextMenu.accesskey;"
oncommand="gContextMenu.inspectNode();"/>
+ <menuseparator id="context-media-eme-separator" hidden="true"/>
+ <menuitem id="context-media-eme-learnmore"
+ class="menuitem-iconic"
+ hidden="true"
+ label="&emeLearnMoreContextMenu.label;"
+ accesskey="&emeLearnMoreContextMenu.accesskey;"
+ onclick="gContextMenu.drmLearnMore(event);"/>
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -473,16 +473,18 @@ nsContextMenu.prototype = {
this.showItem("context-media-unmute", onMedia && this.target.muted);
this.showItem("context-media-playbackrate", onMedia);
this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
this.showItem("context-media-hidecontrols", onMedia && this.target.controls);
this.showItem("context-video-fullscreen", this.onVideo && this.target.ownerDocument.mozFullScreenElement == null);
var statsShowing = this.onVideo && this.target.mozMediaStatisticsShowing;
this.showItem("context-video-showstats", this.onVideo && this.target.controls && !statsShowing);
this.showItem("context-video-hidestats", this.onVideo && this.target.controls && statsShowing);
+ this.showItem("context-media-eme-learnmore", this.onDRMMedia);
+ this.showItem("context-media-eme-separator", this.onDRMMedia);
// Disable them when there isn't a valid media source loaded.
if (onMedia) {
this.setItemAttr("context-media-playbackrate-050x", "checked", this.target.playbackRate == 0.5);
this.setItemAttr("context-media-playbackrate-100x", "checked", this.target.playbackRate == 1.0);
this.setItemAttr("context-media-playbackrate-150x", "checked", this.target.playbackRate == 1.5);
this.setItemAttr("context-media-playbackrate-200x", "checked", this.target.playbackRate == 2.0);
var hasError = this.target.error != null ||
@@ -494,17 +496,17 @@ nsContextMenu.prototype = {
this.setItemAttr("context-media-playbackrate", "disabled", hasError);
this.setItemAttr("context-media-playbackrate-050x", "disabled", hasError);
this.setItemAttr("context-media-playbackrate-100x", "disabled", hasError);
this.setItemAttr("context-media-playbackrate-150x", "disabled", hasError);
this.setItemAttr("context-media-playbackrate-200x", "disabled", hasError);
this.setItemAttr("context-media-showcontrols", "disabled", hasError);
this.setItemAttr("context-media-hidecontrols", "disabled", hasError);
if (this.onVideo) {
- let canSaveSnapshot = this.target.readyState >= this.target.HAVE_CURRENT_DATA;
+ let canSaveSnapshot = !this.onDRMMedia && this.target.readyState >= this.target.HAVE_CURRENT_DATA;
this.setItemAttr("context-video-saveimage", "disabled", !canSaveSnapshot);
this.setItemAttr("context-video-fullscreen", "disabled", hasError);
this.setItemAttr("context-video-showstats", "disabled", hasError);
this.setItemAttr("context-video-hidestats", "disabled", hasError);
}
}
this.showItem("context-media-sep-commands", onMedia);
},
@@ -557,16 +559,17 @@ nsContextMenu.prototype = {
// Initialize contextual info.
this.onImage = false;
this.onLoadedImage = false;
this.onCompletedImage = false;
this.imageDescURL = "";
this.onCanvas = false;
this.onVideo = false;
this.onAudio = false;
+ this.onDRMMedia = false;
this.onTextInput = false;
this.onNumeric = false;
this.onKeywordField = false;
this.mediaURL = "";
this.onLink = false;
this.onMailtoLink = false;
this.onSaveableLink = false;
this.link = null;
@@ -635,32 +638,38 @@ nsContextMenu.prototype = {
else if (this.target instanceof HTMLCanvasElement) {
this.onCanvas = true;
}
else if (this.target instanceof HTMLVideoElement) {
let mediaURL = this.target.currentSrc || this.target.src;
if (this.isMediaURLReusable(mediaURL)) {
this.mediaURL = mediaURL;
}
+ if (this.target.isEncrypted) {
+ this.onDRMMedia = true;
+ }
// Firefox always creates a HTMLVideoElement when loading an ogg file
// directly. If the media is actually audio, be smarter and provide a
// context menu with audio operations.
if (this.target.readyState >= this.target.HAVE_METADATA &&
(this.target.videoWidth == 0 || this.target.videoHeight == 0)) {
this.onAudio = true;
} else {
this.onVideo = true;
}
}
else if (this.target instanceof HTMLAudioElement) {
this.onAudio = true;
let mediaURL = this.target.currentSrc || this.target.src;
if (this.isMediaURLReusable(mediaURL)) {
this.mediaURL = mediaURL;
}
+ if (this.target.isEncrypted) {
+ this.onDRMMedia = true;
+ }
}
else if (editFlags & (SpellCheckHelper.INPUT | SpellCheckHelper.TEXTAREA)) {
this.onTextInput = (editFlags & SpellCheckHelper.TEXTINPUT) !== 0;
this.onNumeric = (editFlags & SpellCheckHelper.NUMERIC) !== 0;
this.onEditableArea = (editFlags & SpellCheckHelper.EDITABLE) !== 0;
if (this.onEditableArea) {
if (this.isRemote) {
InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
@@ -1693,16 +1702,27 @@ nsContextMenu.prototype = {
},
copyMediaLocation : function () {
var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
getService(Ci.nsIClipboardHelper);
clipboard.copyString(this.mediaURL, document);
},
+ drmLearnMore: function(aEvent) {
+ let drmInfoURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
+ let dest = whereToOpenLink(aEvent);
+ // Don't ever want this to open in the same tab as it'll unload the
+ // DRM'd video, which is going to be a bad idea in most cases.
+ if (dest == "current") {
+ dest = "tab";
+ }
+ openUILinkIn(drmInfoURL, dest);
+ },
+
get imageURL() {
if (this.onImage)
return this.mediaURL;
return "";
},
// Formats the 'Search <engine> for "<selection or link text>"' context menu.
formatSearchContextItem: function() {
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -56,16 +56,17 @@ support-files =
file_double_close_tab.html
file_favicon_change.html
file_favicon_change_not_in_document.html
file_fullscreen-window-open.html
get_user_media.html
head.js
healthreport_testRemoteCommands.html
moz.png
+ navigating_window_with_download.html
offlineQuotaNotification.cacheManifest
offlineQuotaNotification.html
page_style_sample.html
parsingTestHelpers.jsm
pinning_headers.sjs
pinning_reports.sjs
popup_blocker.html
print_postdata.sjs
@@ -77,16 +78,18 @@ support-files =
test_bug435035.html
test_bug462673.html
test_bug628179.html
test_bug839103.html
test_bug959531.html
test_process_flags_chrome.html
test_wyciwyg_copying.html
title_test.svg
+ unknownContentType_file.pif
+ unknownContentType_file.pif^headers^
video.ogg
web_video.html
web_video1.ogv
web_video1.ogv^headers^
zoom_test.html
test_no_mcb_on_http_site_img.html
test_no_mcb_on_http_site_img.css
test_no_mcb_on_http_site_font.html
@@ -382,16 +385,18 @@ skip-if = e10s
[browser_sanitize-timespans.js]
skip-if = buildapp == 'mulet'
[browser_sanitizeDialog.js]
skip-if = buildapp == 'mulet'
[browser_save_link-perwindowpb.js]
skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
[browser_save_private_link_perwindowpb.js]
skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
+[browser_save_link_when_window_navigates.js]
+skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
[browser_save_video.js]
skip-if = buildapp == 'mulet' || e10s # Bug 1100698 - test uses synthesizeMouse and then does a load of other stuff that breaks in e10s
[browser_save_video_frame.js]
[browser_scope.js]
[browser_searchSuggestionUI.js]
skip-if = e10s
support-files =
searchSuggestionUI.html
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_save_link_when_window_navigates.js
@@ -0,0 +1,173 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+const SAVE_PER_SITE_PREF = "browser.download.lastDir.savePerSite";
+const ALWAYS_DOWNLOAD_DIR_PREF = "browser.download.useDownloadDir";
+const UCT_URI = "chrome://mozapps/content/downloads/unknownContentType.xul";
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this);
+
+function createTemporarySaveDirectory() {
+ var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists()) {
+ info("create testsavedir!");
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
+ }
+ info("return from createTempSaveDir: " + saveDir.path);
+ return saveDir;
+}
+
+function triggerSave(aWindow, aCallback) {
+ info("started triggerSave, persite downloads: " + (Services.prefs.getBoolPref(SAVE_PER_SITE_PREF) ? "on" : "off"));
+ var fileName;
+ let testBrowser = aWindow.gBrowser.selectedBrowser;
+ let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/navigating_window_with_download.html";
+ windowObserver.setCallback(onUCTDialog);
+ testBrowser.loadURI(testURI);
+
+ // Create the folder the link will be saved into.
+ var destDir = createTemporarySaveDirectory();
+ var destFile = destDir.clone();
+
+ MockFilePicker.displayDirectory = destDir;
+ MockFilePicker.showCallback = function(fp) {
+ info("showCallback");
+ fileName = fp.defaultString;
+ info("fileName: " + fileName);
+ destFile.append (fileName);
+ MockFilePicker.returnFiles = [destFile];
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+ info("done showCallback");
+ };
+
+ mockTransferCallback = function(downloadSuccess) {
+ info("mockTransferCallback");
+ onTransferComplete(aWindow, downloadSuccess, destDir);
+ destDir.remove(true);
+ ok(!destDir.exists(), "Destination dir should be removed");
+ ok(!destFile.exists(), "Destination file should be removed");
+ mockTransferCallback = null;
+ info("done mockTransferCallback");
+ }
+
+ function onUCTDialog(dialog) {
+ function doLoad() {
+ content.document.querySelector('iframe').remove();
+ }
+ testBrowser.messageManager.loadFrameScript("data:,(" + doLoad.toString() + ")()", false);
+ executeSoon(continueDownloading);
+ }
+
+ function continueDownloading() {
+ let windows = Services.wm.getEnumerator("");
+ while (windows.hasMoreElements()) {
+ let win = windows.getNext();
+ if (win.location && win.location.href == UCT_URI) {
+ win.document.documentElement._fireButtonEvent("accept");
+ win.close();
+ return;
+ }
+ }
+ ok(false, "No Unknown Content Type dialog yet?");
+ }
+
+ function onTransferComplete(aWindow, downloadSuccess, destDir) {
+ ok(downloadSuccess, "Link should have been downloaded successfully");
+ aWindow.close();
+
+ executeSoon(aCallback);
+ }
+}
+
+
+let windowObserver = {
+ setCallback: function(aCallback) {
+ if (this._callback) {
+ ok(false, "Should only be dealing with one callback at a time.");
+ }
+ this._callback = aCallback;
+ },
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic != "domwindowopened") {
+ return;
+ }
+
+ let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
+
+ win.addEventListener("load", function onLoad(event) {
+ win.removeEventListener("load", onLoad, false);
+
+ if (win.location == UCT_URI) {
+ SimpleTest.executeSoon(function() {
+ if (windowObserver._callback) {
+ windowObserver._callback(win);
+ delete windowObserver._callback;
+ } else {
+ ok(false, "Unexpected UCT dialog!");
+ }
+ });
+ }
+ }, false);
+ }
+};
+
+Services.ww.registerNotification(windowObserver);
+
+function test() {
+ waitForExplicitFinish();
+
+ function testOnWindow(options, callback) {
+ info("testOnWindow(" + options + ")");
+ var win = OpenBrowserWindow(options);
+ info("got " + win);
+ whenDelayedStartupFinished(win, () => callback(win));
+ }
+
+ function whenDelayedStartupFinished(aWindow, aCallback) {
+ info("whenDelayedStartupFinished");
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ info("whenDelayedStartupFinished, got topic: " + aTopic + ", got subject: " + aSubject + ", waiting for " + aWindow);
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ executeSoon(aCallback);
+ info("whenDelayedStartupFinished found our window");
+ }
+ }, "browser-delayed-startup-finished", false);
+ }
+
+ mockTransferRegisterer.register();
+
+ registerCleanupFunction(function () {
+ info("Running the cleanup code");
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ Services.ww.unregisterNotification(windowObserver);
+ Services.prefs.clearUserPref(ALWAYS_DOWNLOAD_DIR_PREF);
+ Services.prefs.clearUserPref(SAVE_PER_SITE_PREF);
+ info("Finished running the cleanup code");
+ });
+
+ Services.prefs.setBoolPref(ALWAYS_DOWNLOAD_DIR_PREF, false);
+ testOnWindow(undefined, function(win) {
+ let windowGonePromise = promiseWindowWillBeClosed(win);
+ Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, true);
+ triggerSave(win, function() {
+ windowGonePromise.then(function() {
+ Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, false);
+ testOnWindow(undefined, function(win) {
+ triggerSave(win, finish);
+ });
+ });
+ });
+ });
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/navigating_window_with_download.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <head><title>This window will navigate while you're downloading something</title></head>
+ <body>
+ <iframe src="http://mochi.test:8888/browser/browser/base/content/test/general/unknownContentType_file.pif"></iframe>
+ </body>
+</html>
copy from toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif
copy to browser/base/content/test/general/unknownContentType_file.pif
copy from toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif^headers^
copy to browser/base/content/test/general/unknownContentType_file.pif^headers^
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -290,40 +290,64 @@ loop.standaloneRoomViews = (function(moz
* Specifically updates the local camera stream size and position, depending
* on the size and position of the remote video stream.
* This method gets called from `updateVideoContainer`, which is defined in
* the `MediaSetupMixin`.
*
* @param {Object} ratio Aspect ratio of the local camera stream
*/
updateLocalCameraPosition: function(ratio) {
+ // The local stream is a quarter of the remote stream.
+ var LOCAL_STREAM_SIZE = 0.25;
+ // The local stream overlaps the remote stream by a quarter of the local stream.
+ var LOCAL_STREAM_OVERLAP = 0.25;
+ // The minimum size of video height/width allowed by the sdk css.
+ var SDK_MIN_SIZE = 48;
+
var node = this._getElement(".local");
- var parent = node.offsetParent || this._getElement(".media");
- // The local camera view should be a sixth of the size of its offset parent
- // and positioned to overlap with the remote stream at a quarter of its width.
- var parentWidth = parent.offsetWidth;
- var targetWidth = parentWidth / 6;
+ var targetWidth;
node.style.right = "auto";
if (window.matchMedia && window.matchMedia("screen and (max-width:640px)").matches) {
+ // For reduced screen widths, we just go for a fixed size and no overlap.
targetWidth = 180;
+ node.style.width = (targetWidth * ratio.width) + "px";
+ node.style.height = (targetWidth * ratio.height) + "px";
node.style.left = "auto";
} else {
+ // The local camera view should be a quarter of the size of the remote stream
+ // and positioned to overlap with the remote stream at a quarter of its width.
+
// Now position the local camera view correctly with respect to the remote
// video stream.
var remoteVideoDimensions = this.getRemoteVideoDimensions();
+ targetWidth = remoteVideoDimensions.streamWidth * LOCAL_STREAM_SIZE;
+
+ var realWidth = targetWidth * ratio.width;
+ var realHeight = targetWidth * ratio.height;
+
+ // If we've hit the min size limits, then limit at the minimum.
+ if (realWidth < SDK_MIN_SIZE) {
+ realWidth = SDK_MIN_SIZE;
+ realHeight = realWidth / ratio.width * ratio.height;
+ }
+ if (realHeight < SDK_MIN_SIZE) {
+ realHeight = SDK_MIN_SIZE;
+ realWidth = realHeight / ratio.height * ratio.width;
+ }
+
var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX);
// The horizontal offset of the stream, and the width of the resulting
// pillarbox, is determined by the height exponent of the aspect ratio.
// Therefore we multiply the width of the local camera view by the height
// ratio.
- node.style.left = (offsetX - ((targetWidth * ratio.height) / 4)) + "px";
+ node.style.left = (offsetX - (realWidth * LOCAL_STREAM_OVERLAP)) + "px";
+ node.style.width = realWidth + "px";
+ node.style.height = realHeight + "px";
}
- node.style.width = (targetWidth * ratio.width) + "px";
- node.style.height = (targetWidth * ratio.height) + "px";
},
/**
* Checks if current room is active.
*
* @return {Boolean}
*/
_roomIsActive: function() {
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -290,40 +290,64 @@ loop.standaloneRoomViews = (function(moz
* Specifically updates the local camera stream size and position, depending
* on the size and position of the remote video stream.
* This method gets called from `updateVideoContainer`, which is defined in
* the `MediaSetupMixin`.
*
* @param {Object} ratio Aspect ratio of the local camera stream
*/
updateLocalCameraPosition: function(ratio) {
+ // The local stream is a quarter of the remote stream.
+ var LOCAL_STREAM_SIZE = 0.25;
+ // The local stream overlaps the remote stream by a quarter of the local stream.
+ var LOCAL_STREAM_OVERLAP = 0.25;
+ // The minimum size of video height/width allowed by the sdk css.
+ var SDK_MIN_SIZE = 48;
+
var node = this._getElement(".local");
- var parent = node.offsetParent || this._getElement(".media");
- // The local camera view should be a sixth of the size of its offset parent
- // and positioned to overlap with the remote stream at a quarter of its width.
- var parentWidth = parent.offsetWidth;
- var targetWidth = parentWidth / 6;
+ var targetWidth;
node.style.right = "auto";
if (window.matchMedia && window.matchMedia("screen and (max-width:640px)").matches) {
+ // For reduced screen widths, we just go for a fixed size and no overlap.
targetWidth = 180;
+ node.style.width = (targetWidth * ratio.width) + "px";
+ node.style.height = (targetWidth * ratio.height) + "px";
node.style.left = "auto";
} else {
+ // The local camera view should be a quarter of the size of the remote stream
+ // and positioned to overlap with the remote stream at a quarter of its width.
+
// Now position the local camera view correctly with respect to the remote
// video stream.
var remoteVideoDimensions = this.getRemoteVideoDimensions();
+ targetWidth = remoteVideoDimensions.streamWidth * LOCAL_STREAM_SIZE;
+
+ var realWidth = targetWidth * ratio.width;
+ var realHeight = targetWidth * ratio.height;
+
+ // If we've hit the min size limits, then limit at the minimum.
+ if (realWidth < SDK_MIN_SIZE) {
+ realWidth = SDK_MIN_SIZE;
+ realHeight = realWidth / ratio.width * ratio.height;
+ }
+ if (realHeight < SDK_MIN_SIZE) {
+ realHeight = SDK_MIN_SIZE;
+ realWidth = realHeight / ratio.height * ratio.width;
+ }
+
var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX);
// The horizontal offset of the stream, and the width of the resulting
// pillarbox, is determined by the height exponent of the aspect ratio.
// Therefore we multiply the width of the local camera view by the height
// ratio.
- node.style.left = (offsetX - ((targetWidth * ratio.height) / 4)) + "px";
+ node.style.left = (offsetX - (realWidth * LOCAL_STREAM_OVERLAP)) + "px";
+ node.style.width = realWidth + "px";
+ node.style.height = realHeight + "px";
}
- node.style.width = (targetWidth * ratio.width) + "px";
- node.style.height = (targetWidth * ratio.height) + "px";
},
/**
* Checks if current room is active.
*
* @return {Boolean}
*/
_roomIsActive: function() {
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -138,16 +138,118 @@ describe("loop.standaloneRoomViews", fun
sinon.assert.calledOnce(dispatch);
sinon.assert.calledWithExactly(dispatch, new sharedActions.SetMute({
type: "video",
enabled: true
}));
});
});
+ describe("Local Stream Size Position", function() {
+ var view, localElement;
+
+ beforeEach(function() {
+ sandbox.stub(window, "matchMedia").returns({
+ matches: false
+ });
+ view = mountTestComponent();
+ localElement = view._getElement(".local");
+ });
+
+ it("should be a quarter of the width of the main stream", function() {
+ sandbox.stub(view, "getRemoteVideoDimensions").returns({
+ streamWidth: 640,
+ offsetX: 0
+ });
+
+ view.updateLocalCameraPosition({
+ width: 1,
+ height: 0.75
+ });
+
+ expect(localElement.style.width).eql("160px");
+ expect(localElement.style.height).eql("120px");
+ });
+
+ it("should be a quarter of the width reduced for aspect ratio", function() {
+ sandbox.stub(view, "getRemoteVideoDimensions").returns({
+ streamWidth: 640,
+ offsetX: 0
+ });
+
+ view.updateLocalCameraPosition({
+ width: 0.75,
+ height: 1
+ });
+
+ expect(localElement.style.width).eql("120px");
+ expect(localElement.style.height).eql("160px");
+ });
+
+ it("should ensure the height is a minimum of 48px", function() {
+ sandbox.stub(view, "getRemoteVideoDimensions").returns({
+ streamWidth: 180,
+ offsetX: 0
+ });
+
+ view.updateLocalCameraPosition({
+ width: 1,
+ height: 0.75
+ });
+
+ expect(localElement.style.width).eql("64px");
+ expect(localElement.style.height).eql("48px");
+ });
+
+ it("should ensure the width is a minimum of 48px", function() {
+ sandbox.stub(view, "getRemoteVideoDimensions").returns({
+ streamWidth: 180,
+ offsetX: 0
+ });
+
+ view.updateLocalCameraPosition({
+ width: 0.75,
+ height: 1
+ });
+
+ expect(localElement.style.width).eql("48px");
+ expect(localElement.style.height).eql("64px");
+ });
+
+ it("should position the stream to overlap the main stream by a quarter", function() {
+ sandbox.stub(view, "getRemoteVideoDimensions").returns({
+ streamWidth: 640,
+ offsetX: 0
+ });
+
+ view.updateLocalCameraPosition({
+ width: 1,
+ height: 0.75
+ });
+
+ expect(localElement.style.width).eql("160px");
+ expect(localElement.style.left).eql("600px");
+ });
+
+ it("should position the stream to overlap the main stream by a quarter when the aspect ratio is vertical", function() {
+ sandbox.stub(view, "getRemoteVideoDimensions").returns({
+ streamWidth: 640,
+ offsetX: 0
+ });
+
+ view.updateLocalCameraPosition({
+ width: 0.75,
+ height: 1
+ });
+
+ expect(localElement.style.width).eql("120px");
+ expect(localElement.style.left).eql("610px");
+ });
+ });
+
describe("#render", function() {
var view;
beforeEach(function() {
view = mountTestComponent();
});
describe("Empty room message", function() {
--- a/browser/components/migration/content/aboutWelcomeBack.xhtml
+++ b/browser/components/migration/content/aboutWelcomeBack.xhtml
@@ -61,17 +61,19 @@
<treecol cycler="true" id="restore" type="checkbox" label="&restorepage.restoreHeader;"/>
<splitter class="tree-splitter"/>
<treecol primary="true" id="title" label="&restorepage.listHeader;" flex="1"/>
</treecols>
<treechildren flex="1"/>
</tree>
<hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="button-container">
- <button class="primary" label="&welcomeback2.restoreButton;"
+ <button class="primary"
+ id="errorTryAgain"
+ label="&welcomeback2.restoreButton;"
accesskey="&welcomeback2.restoreButton.access;"
oncommand="restoreSession();"/>
</hbox>
<input type="text" id="sessionData" style="display: none;"/>
</div>
</div>
</body>
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -133,16 +133,19 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource:///modules/FormValidationHandler.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
"resource://gre/modules/WebChannel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
"resource:///modules/ReaderParent.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonWatcher",
+ "resource://gre/modules/AddonWatcher.jsm");
+
const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
const PREF_PLUGINS_UPDATEURL = "plugins.update.url";
// Seconds of idle before trying to create a bookmarks backup.
const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 8 * 60;
// Minimum interval between backups. We try to not create more than one backup
// per interval.
const BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS = 1;
@@ -559,16 +562,86 @@ BrowserGlue.prototype = {
},
_onAppDefaults: function BG__onAppDefaults() {
// apply distribution customizations (prefs)
// other customizations are applied in _finalUIStartup()
this._distributionCustomizer.applyPrefDefaults();
},
+ _notifySlowAddon: function BG_notifySlowAddon(addonId) {
+ let addonCallback = function(addon) {
+ if (!addon) {
+ Cu.reportError("couldn't look up addon: " + addonId);
+ return;
+ }
+ let win = RecentWindow.getMostRecentBrowserWindow();
+
+ if (!win) {
+ return;
+ }
+
+ let brandBundle = win.document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let message = win.gNavigatorBundle.getFormattedString("addonwatch.slow", [addon.name, brandShortName]);
+ let notificationBox = win.document.getElementById("global-notificationbox");
+ let notificationId = 'addon-slow:' + addonId;
+ let notification = notificationBox.getNotificationWithValue(notificationId);
+ if(notification) {
+ notification.label = message;
+ } else {
+ let buttons = [
+ {
+ label: win.gNavigatorBundle.getFormattedString("addonwatch.disable.label", [addon.name]),
+ accessKey: win.gNavigatorBundle.getString("addonwatch.disable.accesskey"),
+ callback: function() {
+ addon.userDisabled = true;
+ if (addon.pendingOperations != addon.PENDING_NONE) {
+ let restartMessage = win.gNavigatorBundle.getFormattedString("addonwatch.restart.message", [addon.name, brandShortName]);
+ let restartButton = [
+ {
+ label: win.gNavigatorBundle.getFormattedString("addonwatch.restart.label", [brandShortName]),
+ accessKey: win.gNavigatorBundle.getString("addonwatch.restart.accesskey"),
+ callback: function() {
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
+ }
+ }
+ ];
+ const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ notificationBox.appendNotification(restartMessage, "restart-" + addonId, "",
+ priority, restartButton);
+ }
+ }
+ },
+ {
+ label: win.gNavigatorBundle.getString("addonwatch.ignoreSession.label"),
+ accessKey: win.gNavigatorBundle.getString("addonwatch.ignoreSession.accesskey"),
+ callback: function() {
+ AddonWatcher.ignoreAddonForSession(addonId);
+ }
+ },
+ {
+ label: win.gNavigatorBundle.getString("addonwatch.ignorePerm.label"),
+ accessKey: win.gNavigatorBundle.getString("addonwatch.ignorePerm.accesskey"),
+ callback: function() {
+ AddonWatcher.ignoreAddonPermanently(addonId);
+ }
+ },
+ ];
+
+ const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ notificationBox.appendNotification(message, notificationId, "",
+ priority, buttons);
+ }
+ };
+ AddonManager.getAddonByID(addonId, addonCallback);
+ },
+
// runs on startup, before the first command line handler is invoked
// (i.e. before the first window is opened)
_finalUIStartup: function BG__finalUIStartup() {
this._sanitizer.onStartup();
// check if we're in safe mode
if (Services.appinfo.inSafeMode) {
Services.ww.openWindow(null, "chrome://browser/content/safeMode.xul",
"_blank", "chrome,centerscreen,modal,resizable=no", null);
@@ -607,16 +680,18 @@ BrowserGlue.prototype = {
LoginManagerParent.init();
ReaderParent.init();
#ifdef NIGHTLY_BUILD
Services.prefs.addObserver(POLARIS_ENABLED, this, false);
#endif
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
+
+ AddonWatcher.init(this._notifySlowAddon);
},
_checkForOldBuildUpdates: function () {
// check for update if our build is old
if (Services.prefs.getBoolPref("app.update.enabled") &&
Services.prefs.getBoolPref("app.update.checkInstallTime")) {
let buildID = Services.appinfo.appBuildID;
--- a/browser/components/preferences/cookies.xul
+++ b/browser/components/preferences/cookies.xul
@@ -26,17 +26,17 @@
src="chrome://browser/locale/preferences/preferences.properties"/>
<keyset>
<key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
<key key="&focusSearch1.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/>
<key key="&focusSearch2.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/>
</keyset>
- <vbox flex="1" class="contentPane">
+ <vbox flex="1" class="contentPane largeDialogContainer">
<hbox align="center">
<label accesskey="&filter.accesskey;" control="filter">&filter.label;</label>
<textbox type="search" id="filter" flex="1"
aria-controls="cookiesList"
oncommand="gCookiesWindow.filter();"/>
</hbox>
<separator class="thin"/>
<label control="cookiesList" id="cookiesIntro" value="&cookiesonsystem.label;"/>
--- a/browser/components/preferences/fonts.xul
+++ b/browser/components/preferences/fonts.xul
@@ -17,16 +17,17 @@
title="&fontsDialog.title;"
dlgbuttons="accept,cancel,help"
ondialoghelp="openPrefsHelp()"
style="">
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
<prefpane id="FontsDialogPane"
+ class="largeDialogContainer"
helpTopic="prefs-fonts-and-colors">
<preferences id="fontPreferences">
<preference id="font.language.group" name="font.language.group" type="wstring"/>
<preference id="browser.display.use_document_fonts"
name="browser.display.use_document_fonts"
type="int"/>
<preference id="intl.charset.fallback.override" name="intl.charset.fallback.override" type="string"/>
--- a/browser/components/preferences/in-content/subdialogs.js
+++ b/browser/components/preferences/in-content/subdialogs.js
@@ -32,16 +32,22 @@ let gSubDialog = {
chromeBrowser.addEventListener("DOMTitleChanged", this.updateTitle, true);
// Similarly DOMFrameContentLoaded only fires on the top window
window.addEventListener("DOMFrameContentLoaded", this._onContentLoaded.bind(this), true);
// Wait for the stylesheets injected during DOMContentLoaded to load before showing the dialog
// otherwise there is a flicker of the stylesheet applying.
this._frame.addEventListener("load", this._onLoad.bind(this));
+
+ chromeBrowser.addEventListener("unload", function(aEvent) {
+ if (aEvent.target.location.href != "about:blank") {
+ this.close();
+ }
+ }.bind(this), true);
},
uninit: function() {
let chromeBrowser = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler;
chromeBrowser.removeEventListener("DOMTitleChanged", gSubDialog.updateTitle, true);
@@ -87,16 +93,18 @@ let gSubDialog = {
}
this._overlay.style.visibility = "";
// Clear the sizing inline styles.
this._frame.removeAttribute("style");
// Clear the sizing attributes
this._box.removeAttribute("width");
this._box.removeAttribute("height");
+ this._box.style.removeProperty("min-height");
+ this._box.style.removeProperty("min-width");
setTimeout(() => {
// Unload the dialog after the event listeners run so that the load of about:blank isn't
// cancelled by the ESC <key>.
this._frame.loadURI("about:blank");
}, 0);
},
@@ -139,20 +147,50 @@ let gSubDialog = {
_onLoad: function(aEvent) {
if (aEvent.target.contentWindow.location == "about:blank")
return;
// Do this on load to wait for the CSS to load and apply before calculating the size.
let docEl = this._frame.contentDocument.documentElement;
- // padding-bottom doesn't seem to be included in the scrollHeight of the document element in XUL
- // so add it ourselves.
- let paddingBottom = parseFloat(this._frame.contentWindow.getComputedStyle(docEl).paddingBottom);
+ let groupBoxTitle = document.getAnonymousElementByAttribute(this._box, "class", "groupbox-title");
+ let groupBoxTitleHeight = groupBoxTitle.clientHeight +
+ parseFloat(getComputedStyle(groupBoxTitle).borderBottomWidth);
+
+ let groupBoxBody = document.getAnonymousElementByAttribute(this._box, "class", "groupbox-body");
+ let boxVerticalPadding = 2 * parseFloat(getComputedStyle(groupBoxBody).paddingTop);
+ let boxHorizontalPadding = 2 * parseFloat(getComputedStyle(groupBoxBody).paddingLeft);
+ let frameWidth = docEl.style.width || docEl.scrollWidth + "px";
+ let frameHeight = docEl.style.height || docEl.scrollHeight + "px";
+ let boxVerticalBorder = 2 * parseFloat(getComputedStyle(this._box).borderTopWidth);
+ let boxHorizontalBorder = 2 * parseFloat(getComputedStyle(this._box).borderLeftWidth);
+
+ let frameRect = this._frame.getBoundingClientRect();
+ let boxRect = this._box.getBoundingClientRect();
+ let frameSizeDifference = (frameRect.top - boxRect.top) + (boxRect.bottom - frameRect.bottom);
- this._frame.style.width = docEl.style.width || docEl.scrollWidth + "px";
- this._frame.style.height = docEl.style.height || (docEl.scrollHeight + paddingBottom) + "px";
+ // Now check if the frame height we calculated is possible at this window size,
+ // accounting for titlebar, padding/border and some spacing.
+ let maxHeight = window.innerHeight - frameSizeDifference - 30;
+ if (frameHeight > maxHeight) {
+ // If not, we should probably let the dialog scroll:
+ frameHeight = maxHeight;
+ let containers = this._frame.contentDocument.querySelectorAll('.largeDialogContainer');
+ for (let container of containers) {
+ container.classList.add("doScroll");
+ }
+ }
+
+ this._frame.style.width = frameWidth;
+ this._frame.style.height = frameHeight;
+ this._box.style.minHeight = "calc(" +
+ (boxVerticalBorder + groupBoxTitleHeight + boxVerticalPadding) +
+ "px + " + frameHeight + ")";
+ this._box.style.minWidth = "calc(" +
+ (boxHorizontalBorder + boxHorizontalPadding) +
+ "px + " + frameWidth + ")";
this._overlay.style.visibility = "visible";
this._frame.focus();
this._overlay.style.opacity = ""; // XXX: focus hack continued from _onContentLoaded
},
};
--- a/browser/components/preferences/in-content/tests/browser_subdialogs.js
+++ b/browser/components/preferences/in-content/tests/browser_subdialogs.js
@@ -71,17 +71,17 @@ let gTests = [{
let dialog = yield dialogPromise;
let closingPromise = promiseDialogClosing(dialog);
info("cancelling the dialog");
dialog.document.documentElement.cancelDialog();
let closingEvent = yield closingPromise;
- ise(closingEvent.detail.button, "cancel", "closing event should indicate button was 'accept'");
+ ise(closingEvent.detail.button, "cancel", "closing event should indicate button was 'cancel'");
yield deferredClose.promise;
ise(rv.acceptCount, 0, "return value should NOT have been updated");
},
},
{
desc: "Check window.close on the dialog",
run: function* () {
@@ -114,16 +114,36 @@ let gTests = [{
yield EventUtils.synthesizeMouseAtCenter(content.document.getElementById("dialogClose"), {},
content.window);
yield deferredClose.promise;
ise(rv.acceptCount, 0, "return value should NOT have been updated");
},
},
{
+ desc: "Check that 'back' navigation will close the dialog",
+ run: function* () {
+ let rv = { acceptCount: 0 };
+ let deferredClose = Promise.defer();
+ let dialogPromise = openAndLoadSubDialog(gDialogURL, null, rv,
+ (aEvent) => dialogClosingCallback(deferredClose, aEvent));
+ let dialog = yield dialogPromise;
+
+ // XXX Without the call to promiseDialogClosing the test causes
+ // intermittent failures in browser_change_app_handler.js.
+ let unusedClosingPromise = promiseDialogClosing(dialog);
+
+ info("cancelling the dialog");
+ content.gSubDialog._frame.goBack();
+
+ yield deferredClose.promise;
+ ise(rv.acceptCount, 0, "return value should NOT have been updated");
+ },
+},
+{
desc: "Hitting escape in the dialog",
run: function* () {
let rv = { acceptCount: 0 };
let deferredClose = Promise.defer();
let dialogPromise = openAndLoadSubDialog(gDialogURL, null, rv,
(aEvent) => dialogClosingCallback(deferredClose, aEvent));
let dialog = yield dialogPromise;
--- a/browser/components/preferences/languages.xul
+++ b/browser/components/preferences/languages.xul
@@ -17,16 +17,17 @@
title="&languages.customize.Header;"
dlgbuttons="accept,cancel,help"
ondialoghelp="openPrefsHelp()"
style="width: &window.width;;">
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
<prefpane id="LanguagesDialogPane"
+ class="largeDialogContainer"
onpaneload="gLanguagesDialog.init();"
helpTopic="prefs-languages">
<preferences>
<preference id="intl.accept_languages" name="intl.accept_languages" type="wstring"/>
<preference id="pref.browser.language.disable_button.up"
name="pref.browser.language.disable_button.up"
type="bool"/>
--- a/browser/components/preferences/permissions.xul
+++ b/browser/components/preferences/permissions.xul
@@ -24,17 +24,17 @@
<stringbundle id="bundlePreferences"
src="chrome://browser/locale/preferences/preferences.properties"/>
<keyset>
<key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
</keyset>
- <vbox class="contentPane" flex="1">
+ <vbox class="contentPane largeDialogContainer" flex="1">
<description id="permissionsText" control="url"/>
<separator class="thin"/>
<label id="urlLabel" control="url" value="&address.label;" accesskey="&address.accesskey;"/>
<hbox align="start">
<textbox id="url" flex="1"
oninput="gPermissionManager.onHostInput(event.target);"
onkeypress="gPermissionManager.onHostKeyPress(event);"/>
</hbox>
--- a/browser/components/preferences/sanitize.xul
+++ b/browser/components/preferences/sanitize.xul
@@ -77,18 +77,18 @@
</row>
</rows>
</grid>
</groupbox>
<groupbox orient="horizontal">
<caption label="&dataSection.label;"/>
<grid flex="1">
<columns>
- <column dialogWidth="&column.width2;"
- subdialogWidth="&inContentColumn.width;"/>
+ <column dialogWidth="&sanitizePrefs2.column.width;"
+ subdialogWidth="&sanitizePrefs2.inContent.column.width;"/>
<column flex="1"/>
</columns>
<rows>
<row>
<checkbox label="&itemPasswords.label;"
accesskey="&itemPasswords.accesskey;"
preference="privacy.clearOnShutdown.passwords"/>
<checkbox label="&itemOfflineApps.label;"
--- a/browser/components/preferences/translation.xul
+++ b/browser/components/preferences/translation.xul
@@ -23,55 +23,57 @@
<stringbundle id="bundlePreferences"
src="chrome://browser/locale/preferences/preferences.properties"/>
<keyset>
<key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
</keyset>
- <vbox class="contentPane" flex="1">
- <label id="languagesLabel" control="permissionsTree">&noTranslationForLanguages.label;</label>
- <separator class="thin"/>
- <tree id="languagesTree" flex="1" style="height: 12em;"
- hidecolumnpicker="true"
- onkeypress="gTranslationExceptions.onLanguageKeyPress(event)"
- onselect="gTranslationExceptions.onLanguageSelected();">
- <treecols>
- <treecol id="languageCol" label="&treehead.languageName.label;" flex="1"/>
- </treecols>
- <treechildren/>
- </tree>
- </vbox>
- <hbox align="end">
- <hbox class="actionButtons" flex="1">
- <button id="removeLanguage" disabled="true"
- accesskey="&removeLanguage.accesskey;"
- icon="remove" label="&removeLanguage.label;"
- oncommand="gTranslationExceptions.onLanguageDeleted();"/>
- <button id="removeAllLanguages"
- icon="clear" label="&removeAllLanguages.label;"
- accesskey="&removeAllLanguages.accesskey;"
- oncommand="gTranslationExceptions.onAllLanguagesDeleted();"/>
- <spacer flex="1"/>
+ <vbox class="largeDialogContainer">
+ <vbox class="contentPane" flex="1">
+ <label id="languagesLabel" control="permissionsTree">&noTranslationForLanguages.label;</label>
+ <separator class="thin"/>
+ <tree id="languagesTree" flex="1" style="height: 12em;"
+ hidecolumnpicker="true"
+ onkeypress="gTranslationExceptions.onLanguageKeyPress(event)"
+ onselect="gTranslationExceptions.onLanguageSelected();">
+ <treecols>
+ <treecol id="languageCol" label="&treehead.languageName.label;" flex="1"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ </vbox>
+ <hbox align="end">
+ <hbox class="actionButtons" flex="1">
+ <button id="removeLanguage" disabled="true"
+ accesskey="&removeLanguage.accesskey;"
+ icon="remove" label="&removeLanguage.label;"
+ oncommand="gTranslationExceptions.onLanguageDeleted();"/>
+ <button id="removeAllLanguages"
+ icon="clear" label="&removeAllLanguages.label;"
+ accesskey="&removeAllLanguages.accesskey;"
+ oncommand="gTranslationExceptions.onAllLanguagesDeleted();"/>
+ <spacer flex="1"/>
+ </hbox>
</hbox>
- </hbox>
- <separator/>
- <vbox class="contentPane" flex="1">
- <label id="languagesLabel" control="permissionsTree">&noTranslationForSites.label;</label>
- <separator class="thin"/>
- <tree id="sitesTree" flex="1" style="height: 12em;"
- hidecolumnpicker="true"
- onkeypress="gTranslationExceptions.onSiteKeyPress(event)"
- onselect="gTranslationExceptions.onSiteSelected();">
- <treecols>
- <treecol id="siteCol" label="&treehead.siteName.label;" flex="1"/>
- </treecols>
- <treechildren/>
- </tree>
+ <separator/>
+ <vbox class="contentPane" flex="1">
+ <label id="languagesLabel" control="permissionsTree">&noTranslationForSites.label;</label>
+ <separator class="thin"/>
+ <tree id="sitesTree" flex="1" style="height: 12em;"
+ hidecolumnpicker="true"
+ onkeypress="gTranslationExceptions.onSiteKeyPress(event)"
+ onselect="gTranslationExceptions.onSiteSelected();">
+ <treecols>
+ <treecol id="siteCol" label="&treehead.siteName.label;" flex="1"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ </vbox>
</vbox>
<hbox align="end">
<hbox class="actionButtons" flex="1">
<button id="removeSite" disabled="true"
accesskey="&removeSite.accesskey;"
icon="remove" label="&removeSite.label;"
oncommand="gTranslationExceptions.onSiteDeleted();"/>
<button id="removeAllSites"
--- a/browser/components/search/test/browser_hiddenOneOffs_cleanup.js
+++ b/browser/components/search/test/browser_hiddenOneOffs_cleanup.js
@@ -33,19 +33,19 @@ add_task(function* test_remove() {
info("Removing testEngine_dupe.xml");
Services.search.removeEngine(Services.search.getEngineByName("FooDupe"));
let hiddenOneOffs =
Services.prefs.getCharPref("browser.search.hiddenOneOffs").split(",");
is(hiddenOneOffs.length, 1,
"hiddenOneOffs has the correct engine count post removal.");
- is(hiddenOneOffs.includes("FooDupe"), false,
+ is(hiddenOneOffs.some(x => x == "FooDupe"), false,
"Removed Engine is not in hiddenOneOffs after removal");
- is(hiddenOneOffs.includes("Foo"), true,
+ is(hiddenOneOffs.some(x => x == "Foo"), true,
"Current hidden engine is not affected by removal.");
info("Removing testEngine.xml");
Services.search.removeEngine(Services.search.getEngineByName("Foo"));
is(Services.prefs.getCharPref("browser.search.hiddenOneOffs"), "",
"hiddenOneOffs is empty after removing all hidden engines.");
});
@@ -56,19 +56,19 @@ add_task(function* test_add() {
Services.prefs.setCharPref("browser.search.hiddenOneOffs", testPref);
yield promiseNewEngine("testEngine_dupe.xml");
let hiddenOneOffs =
Services.prefs.getCharPref("browser.search.hiddenOneOffs").split(",");
is(hiddenOneOffs.length, 1,
"hiddenOneOffs has the correct number of hidden engines present post add.");
- is(hiddenOneOffs.includes("FooDupe"), false,
+ is(hiddenOneOffs.some(x => x == "FooDupe"), false,
"Added engine is not present in hidden list.");
- is(hiddenOneOffs.includes("Foo"), true,
+ is(hiddenOneOffs.some(x => x == "Foo"), true,
"Adding an engine does not remove engines from hidden list.");
});
registerCleanupFunction(() => {
info("Removing testEngine.xml");
Services.search.removeEngine(Services.search.getEngineByName("Foo"));
info("Removing testEngine_dupe.xml");
Services.search.removeEngine(Services.search.getEngineByName("FooDupe"));
--- a/browser/devtools/animationinspector/test/head.js
+++ b/browser/devtools/animationinspector/test/head.js
@@ -154,17 +154,22 @@ let openAnimationInspector = Task.async(
inspector.sidebar.select("animationinspector");
info("Waiting for the inspector and sidebar to be ready");
yield promise.all(initPromises);
let win = inspector.sidebar.getWindowForTab("animationinspector");
let {AnimationsController, AnimationsPanel} = win;
- yield AnimationsPanel.once(AnimationsPanel.PANEL_INITIALIZED);
+ info("Waiting for the animation controller and panel to be ready");
+ if (AnimationsPanel.initialized) {
+ yield AnimationsPanel.initialized;
+ } else {
+ yield AnimationsPanel.once(AnimationsPanel.PANEL_INITIALIZED);
+ }
return {
toolbox: toolbox,
inspector: inspector,
controller: AnimationsController,
panel: AnimationsPanel,
window: win
};
--- a/browser/devtools/netmonitor/netmonitor.xul
+++ b/browser/devtools/netmonitor/netmonitor.xul
@@ -494,30 +494,30 @@
flex="1">
<vbox id="security-info-connection"
class="tabpanel-summary-container">
<label class="plain tabpanel-summary-label"
value="&netmonitorUI.security.connection;"/>
<vbox class="security-info-section">
<hbox id="security-protocol-version"
class="tabpanel-summary-container"
- align="center">
+ align="baseline">
<label class="plain tabpanel-summary-label"
value="&netmonitorUI.security.protocolVersion;"/>
<label id="security-protocol-version-value"
class="plain tabpanel-summary-value devtools-monospace"
crop="end"
flex="1"/>
<image class="security-warning-icon"
id="security-warning-sslv3"
tooltiptext="&netmonitorUI.security.warning.sslv3;" />
</hbox>
<hbox id="security-ciphersuite"
class="tabpanel-summary-container"
- align="center">
+ align="baseline">
<label class="plain tabpanel-summary-label"
value="&netmonitorUI.security.cipherSuite;"/>
<label id="security-ciphersuite-value"
class="plain tabpanel-summary-value devtools-monospace"
crop="end"
flex="1"/>
<image class="security-warning-icon"
id="security-warning-cipher"
@@ -527,27 +527,27 @@
</vbox>
<vbox id="security-info-domain"
class="tabpanel-summary-container">
<label class="plain tabpanel-summary-label"
id="security-info-host-header"/>
<vbox class="security-info-section">
<hbox id="security-http-strict-transport-security"
class="tabpanel-summary-container"
- align="center">
+ align="baseline">
<label class="plain tabpanel-summary-label"
value="&netmonitorUI.security.hsts;"/>
<label id="security-http-strict-transport-security-value"
class="plain tabpanel-summary-value devtools-monospace"
crop="end"
flex="1"/>
</hbox>
<hbox id="security-public-key-pinning"
class="tabpanel-summary-container"
- align="center">
+ align="baseline">
<label class="plain tabpanel-summary-label"
value="&netmonitorUI.security.hpkp;"/>
<label id="security-public-key-pinning-value"
class="plain tabpanel-summary-value devtools-monospace"
crop="end"
flex="1"/>
</hbox>
</vbox>
@@ -558,116 +558,116 @@
value="&netmonitorUI.security.certificate;"/>
<vbox class="security-info-section">
<vbox class="tabpanel-summary-container">
<label class="plain tabpanel-summary-label"
value="&certmgr.subjectinfo.label;" flex="1"/>
</vbox>
<vbox class="security-info-section">
<hbox class="tabpanel-summary-container"
- align="center">
+ align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.cn;:"/>
<label id="security-cert-subject-cn"
class="plain tabpanel-summary-value devtools-monospace"
crop="end"
flex="1"/>
</hbox>
<hbox class="tabpanel-summary-container"
- align="center">
+ align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.o;:"/>
<label id="security-cert-subject-o"
class="plain tabpanel-summary-value devtools-monospace"
crop="end"
flex="1"/>
</hbox>
<hbox class="tabpanel-summary-container"
- align="center">
+ align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.ou;:"/>
<label id="security-cert-subject-ou"
class="plain tabpanel-summary-value devtools-monospace"
crop="end"
flex="1"/>
</hbox>
</vbox>
<vbox class="tabpanel-summary-container">
<label class="plain tabpanel-summary-label"
value="&certmgr.issuerinfo.label;" flex="1"/>
</vbox>
<vbox class="security-info-section">
<hbox class="tabpanel-summary-container"
- align="center">
+ align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.cn;:"/>
<label id="security-cert-issuer-cn"
class="plain tabpanel-summary-value devtools-monospace"
crop="end"
flex="1"/>
</hbox>
<hbox class="tabpanel-summary-container"
- align="center">
+ align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.o;:"/>
<label id="security-cert-issuer-o"
class="plain tabpanel-summary-value devtools-monospace"
crop="end"
flex="1"/>
</hbox>
<hbox class="tabpanel-summary-container"
- align="center">
+ align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.ou;:"/>
<label id="security-cert-issuer-ou"
class="plain tabpanel-summary-value devtools-monospace"
crop="end"
flex="1"/>
</hbox>
</vbox>
<vbox class="tabpanel-summary-container">
<label class="plain tabpanel-summary-label"
value="&certmgr.periodofvalidity.label;" flex="1"/>
</vbox>
<vbox class="security-info-section">
<hbox class="tabpanel-summary-container"
- align="center">
+ align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.begins;:"/>
<label id="security-cert-validity-begins"
class="plain tabpanel-summary-value devtools-monospace"
crop="end"
flex="1"/>
</hbox>
<hbox class="tabpanel-summary-container"
- align="center">
+ align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.expires;:"/>
<label id="security-cert-validity-expires"
class="plain tabpanel-summary-value devtools-monospace"
crop="end"
flex="1"/>
</hbox>
</vbox>
<vbox class="tabpanel-summary-container">
<label class="plain tabpanel-summary-label"
value="&certmgr.fingerprints.label;" flex="1"/>
</vbox>
<vbox class="security-info-section">
<hbox class="tabpanel-summary-container"
- align="center">
+ align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.sha256fingerprint;:"/>
<label id="security-cert-sha256-fingerprint"
class="plain tabpanel-summary-value devtools-monospace"
crop="end"
flex="1"/>
</hbox>
<hbox class="tabpanel-summary-container"
- align="center">
+ align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.sha1fingerprint;:"/>
<label id="security-cert-sha1-fingerprint"
class="plain tabpanel-summary-value devtools-monospace"
crop="end"
flex="1"/>
</hbox>
</vbox>
--- a/browser/devtools/performance/modules/compatibility.js
+++ b/browser/devtools/performance/modules/compatibility.js
@@ -76,17 +76,21 @@ function createMockAllocations () {
*/
function memoryActorSupported (target) {
// This `target` property is used only in tests to test
// instances where the memory actor is not available.
if (target.TEST_MOCK_MEMORY_ACTOR) {
return false;
}
- return !!target.getTrait("memoryActorAllocations");
+ // We need to test that both the root actor has `memoryActorAllocations`,
+ // which is in Gecko 38+, and also that the target has a memory actor. There
+ // are scenarios, like addon debugging, where memoryActorAllocations is available,
+ // but no memory actor (like addon debugging in Gecko 38+)
+ return !!target.getTrait("memoryActorAllocations") && target.hasActor("memory");
}
exports.memoryActorSupported = Task.async(memoryActorSupported);
/**
* Takes a TabTarget, and checks existence of a TimelineActor on
* the server, or if TEST_MOCK_TIMELINE_ACTOR is to be used.
*
* @param {TabTarget} target
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -35,17 +35,17 @@ support-files =
[browser_perf-front-profiler-03.js]
[browser_perf-front-profiler-04.js]
#[browser_perf-front-profiler-05.js] bug 1077464
#[browser_perf-front-profiler-06.js]
[browser_perf-front.js]
[browser_perf-jump-to-debugger-01.js]
[browser_perf-jump-to-debugger-02.js]
[browser_perf-options-01.js]
-[browser_perf-options-02.js]
+# [browser_perf-options-02.js] bug 1133230
[browser_perf-options-invert-call-tree-01.js]
[browser_perf-options-invert-call-tree-02.js]
[browser_perf-options-invert-flame-graph-01.js]
[browser_perf-options-invert-flame-graph-02.js]
[browser_perf-options-flatten-tree-recursion-01.js]
[browser_perf-options-flatten-tree-recursion-02.js]
[browser_perf-options-show-platform-data-01.js]
[browser_perf-options-show-platform-data-02.js]
--- a/browser/devtools/performance/test/browser_perf-compatibility-02.js
+++ b/browser/devtools/performance/test/browser_perf-compatibility-02.js
@@ -1,33 +1,33 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the recording model is populated correctly when using timeline
- * and memory actor mocks.
+ * and memory actor mocks, and the correct views are shown.
*/
const WAIT_TIME = 1000;
let test = Task.async(function*() {
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "performance", {
TEST_MOCK_MEMORY_ACTOR: true,
TEST_MOCK_TIMELINE_ACTOR: true
});
Services.prefs.setBoolPref(MEMORY_PREF, true);
- let { EVENTS, gFront, PerformanceController, PerformanceView } = panel.panelWin;
+ let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, JsCallTreeView } = panel.panelWin;
let { memory: memoryMock, timeline: timelineMock } = gFront.getMocksInUse();
ok(memoryMock, "memory should be mocked.");
ok(timelineMock, "timeline should be mocked.");
- yield startRecording(panel);
+ yield startRecording(panel, { waitForOverview: false });
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
- yield stopRecording(panel);
+ yield stopRecording(panel, { waitForOverview: false });
let {
label, duration, markers, frames, memory, ticks, allocations, profile
} = PerformanceController.getCurrentRecording().getAllData();
is(label, "", "Empty label for mock.");
is(typeof duration, "number", "duration is a number");
ok(duration > 0, "duration is not 0");
@@ -53,16 +53,33 @@ let test = Task.async(function*() {
ok(false, "The sample " + sample.toSource() + " doesn't have a root node.");
}
}
}
ok(sampleCount > 0,
"At least some samples have been iterated over, checking for root nodes.");
+ is($("#overview-pane").hidden, true,
+ "overview pane hidden when timeline mocked.");
+
+ is($("#select-waterfall-view").hidden, true,
+ "waterfall view button hidden when timeline mocked");
+ is($("#select-js-calltree-view").hidden, false,
+ "jscalltree view button not hidden when timeline/memory mocked");
+ is($("#select-js-flamegraph-view").hidden, true,
+ "jsflamegraph view button hidden when timeline mocked");
+ is($("#select-memory-calltree-view").hidden, true,
+ "memorycalltree view button hidden when memory mocked");
+ is($("#select-memory-flamegraph-view").hidden, true,
+ "memoryflamegraph view button hidden when memory mocked");
+
+ ok(DetailsView.isViewSelected(JsCallTreeView),
+ "JS Call Tree view selected by default when timeline/memory mocked.");
+
yield teardown(panel);
finish();
});
function isEmptyArray (array, name) {
ok(Array.isArray(array), `${name} is an array`);
is(array.length, 0, `${name} is empty`);
}
--- a/browser/devtools/performance/test/browser_perf-compatibility-04.js
+++ b/browser/devtools/performance/test/browser_perf-compatibility-04.js
@@ -1,24 +1,24 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the recording model is populated correctly when using timeline
- * and memory actor mocks.
+ * and memory actor mocks, and that the correct button/overview displays are shown.
*/
const WAIT_TIME = 1000;
let test = Task.async(function*() {
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "performance", {
TEST_MOCK_MEMORY_ACTOR: true
});
Services.prefs.setBoolPref(MEMORY_PREF, true);
- let { EVENTS, gFront, PerformanceController, PerformanceView } = panel.panelWin;
+ let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, WaterfallView } = panel.panelWin;
let { memory: memoryMock, timeline: timelineMock } = gFront.getMocksInUse();
ok(memoryMock, "memory should be mocked.");
ok(!timelineMock, "timeline should not be mocked.");
yield startRecording(panel);
yield busyWait(100);
@@ -52,16 +52,33 @@ let test = Task.async(function*() {
ok(false, "The sample " + sample.toSource() + " doesn't have a root node.");
}
}
}
ok(sampleCount > 0,
"At least some samples have been iterated over, checking for root nodes.");
+ is($("#overview-pane").hidden, false,
+ "overview pane not hidden when only memory mocked.");
+
+ is($("#select-waterfall-view").hidden, false,
+ "waterfall view button not hidden when memory mocked");
+ is($("#select-js-calltree-view").hidden, false,
+ "jscalltree view button not hidden when memory mocked");
+ is($("#select-js-flamegraph-view").hidden, false,
+ "jsflamegraph view button not hidden when memory mocked");
+ is($("#select-memory-calltree-view").hidden, true,
+ "memorycalltree view button hidden when memory mocked");
+ is($("#select-memory-flamegraph-view").hidden, true,
+ "memoryflamegraph view button hidden when memory mocked");
+
+ ok(DetailsView.isViewSelected(WaterfallView),
+ "Waterfall view selected by default when memory mocked.");
+
yield teardown(panel);
finish();
});
function isEmptyArray (array, name) {
ok(Array.isArray(array), `${name} is an array`);
is(array.length, 0, `${name} is empty`);
}
--- a/browser/devtools/performance/test/head.js
+++ b/browser/devtools/performance/test/head.js
@@ -261,17 +261,17 @@ function command (button) {
function click (win, button) {
EventUtils.sendMouseEvent({ type: "click" }, button, win);
}
function mousedown (win, button) {
EventUtils.sendMouseEvent({ type: "mousedown" }, button, win);
}
-function* startRecording(panel) {
+function* startRecording(panel, options={}) {
let win = panel.panelWin;
let clicked = panel.panelWin.PerformanceView.once(win.EVENTS.UI_START_RECORDING);
let willStart = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_WILL_START);
let hasStarted = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_STARTED);
let button = win.$("#main-record-button");
ok(!button.hasAttribute("checked"),
"The record button should not be checked yet.");
@@ -285,31 +285,31 @@ function* startRecording(panel) {
"The record button should now be checked.");
ok(button.hasAttribute("locked"),
"The record button should be locked.");
yield willStart;
let stateChanged = once(win.PerformanceView, win.EVENTS.UI_STATE_CHANGED);
yield hasStarted;
- let overviewRendered = once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED);
+ let overviewRendered = options.waitForOverview ? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED) : Promise.resolve();
yield stateChanged;
yield overviewRendered;
is(win.PerformanceView.getState(), "recording",
"The current state is 'recording'.");
ok(button.hasAttribute("checked"),
"The record button should still be checked.");
ok(!button.hasAttribute("locked"),
"The record button should not be locked.");
}
-function* stopRecording(panel) {
+function* stopRecording(panel, options={}) {
let win = panel.panelWin;
let clicked = panel.panelWin.PerformanceView.once(win.EVENTS.UI_STOP_RECORDING);
let willStop = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_WILL_STOP);
let hasStopped = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_STOPPED);
let button = win.$("#main-record-button");
ok(button.hasAttribute("checked"),
"The record button should already be checked.");
@@ -323,17 +323,17 @@ function* stopRecording(panel) {
"The record button should not be checked.");
ok(button.hasAttribute("locked"),
"The record button should be locked.");
yield willStop;
let stateChanged = once(win.PerformanceView, win.EVENTS.UI_STATE_CHANGED);
yield hasStopped;
- let overviewRendered = once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED);
+ let overviewRendered = options.waitForOverview ? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED) : Promise.resolve();
yield stateChanged;
yield overviewRendered;
is(win.PerformanceView.getState(), "recorded",
"The current state is 'recorded'.");
ok(!button.hasAttribute("checked"),
--- a/browser/devtools/performance/views/details.js
+++ b/browser/devtools/performance/views/details.js
@@ -1,29 +1,27 @@
/* 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";
-const DEFAULT_DETAILS_SUBVIEW = "waterfall";
-
/**
* Details view containing profiler call tree and markers waterfall. Manages
* subviews and toggles visibility between them.
*/
let DetailsView = {
/**
* Name to node+object mapping of subviews.
*/
components: {
- "waterfall": { id: "waterfall-view", view: WaterfallView },
+ "waterfall": { id: "waterfall-view", view: WaterfallView, requires: ["timeline"] },
"js-calltree": { id: "js-calltree-view", view: JsCallTreeView },
- "js-flamegraph": { id: "js-flamegraph-view", view: JsFlameGraphView },
- "memory-calltree": { id: "memory-calltree-view", view: MemoryCallTreeView, pref: "enable-memory" },
- "memory-flamegraph": { id: "memory-flamegraph-view", view: MemoryFlameGraphView, pref: "enable-memory" }
+ "js-flamegraph": { id: "js-flamegraph-view", view: JsFlameGraphView, requires: ["timeline"] },
+ "memory-calltree": { id: "memory-calltree-view", view: MemoryCallTreeView, requires: ["memory"], pref: "enable-memory" },
+ "memory-flamegraph": { id: "memory-flamegraph-view", view: MemoryFlameGraphView, requires: ["memory", "timeline"], pref: "enable-memory" }
},
/**
* Sets up the view with event binding, initializes subviews.
*/
initialize: Task.async(function *() {
this.el = $("#details-pane");
this.toolbar = $("#performance-toolbar-controls-detail-views");
@@ -31,17 +29,17 @@ let DetailsView = {
this._onViewToggle = this._onViewToggle.bind(this);
this._onRecordingStoppedOrSelected = this._onRecordingStoppedOrSelected.bind(this);
this.setAvailableViews = this.setAvailableViews.bind(this);
for (let button of $$("toolbarbutton[data-view]", this.toolbar)) {
button.addEventListener("command", this._onViewToggle);
}
- yield this.selectView(DEFAULT_DETAILS_SUBVIEW);
+ yield this.selectDefaultView();
yield this.setAvailableViews();
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.PREF_CHANGED, this.setAvailableViews);
}),
/**
@@ -57,32 +55,37 @@ let DetailsView = {
}
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.PREF_CHANGED, this.setAvailableViews);
}),
/**
- * Sets the possible views based off of prefs by hiding/showing the
+ * Sets the possible views based off of prefs and server actor support by hiding/showing the
* buttons that select them and going to default view if currently selected.
* Called when a preference changes in `devtools.performance.ui.`.
*/
setAvailableViews: Task.async(function* () {
- for (let [name, { view, pref }] of Iterator(this.components)) {
+ let mocks = gFront.getMocksInUse();
+ for (let [name, { view, pref, requires }] of Iterator(this.components)) {
let recording = PerformanceController.getCurrentRecording();
let isRecorded = recording && !recording.isRecording();
+ // View is enabled view prefs
let isEnabled = !pref || PerformanceController.getPref(pref);
- $(`toolbarbutton[data-view=${name}]`).hidden = !isRecorded || !isEnabled;
+ // View is supported by the server actor, and the requried actor is not being mocked
+ let isSupported = !requires || requires.every(r => !mocks[r]);
+
+ $(`toolbarbutton[data-view=${name}]`).hidden = !isRecorded || !(isEnabled && isSupported);
// If the view is currently selected and not enabled, go back to the
// default view.
if (!isEnabled && this.isViewSelected(view)) {
- yield this.selectView(DEFAULT_DETAILS_SUBVIEW);
+ yield this.selectDefaultView();
}
}
}),
/**
* Select one of the DetailView's subviews to be rendered,
* hiding the others.
*
@@ -102,16 +105,32 @@ let DetailsView = {
button.removeAttribute("checked");
}
}
this.emit(EVENTS.DETAILS_VIEW_SELECTED, viewName);
}),
/**
+ * Selects a default view based off of protocol support
+ * and preferences enabled.
+ */
+ selectDefaultView: function () {
+ let { timeline: mockTimeline } = gFront.getMocksInUse();
+ // If timelines are mocked, the first view available is the js-calltree.
+ if (mockTimeline) {
+ return this.selectView("js-calltree");
+ } else {
+ // In every other scenario with preferences and mocks, waterfall will
+ // be the default view.
+ return this.selectView("waterfall");
+ }
+ },
+
+ /**
* Checks if the provided view is currently selected.
*
* @param object viewObject
* @return boolean
*/
isViewSelected: function(viewObject) {
let selectedPanel = this.el.selectedPanel;
let selectedId = selectedPanel.id;
--- a/browser/devtools/performance/views/overview.js
+++ b/browser/devtools/performance/views/overview.js
@@ -21,16 +21,19 @@ const MEMORY_GRAPH_HEIGHT = 30; // px
* View handler for the overview panel's time view, displaying
* framerate, markers and memory over time.
*/
let OverviewView = {
/**
* Sets up the view with event binding.
*/
initialize: function () {
+ if (gFront.getMocksInUse().timeline) {
+ this.disable();
+ }
this._onRecordingWillStart = this._onRecordingWillStart.bind(this);
this._onRecordingStarted = this._onRecordingStarted.bind(this);
this._onRecordingWillStop = this._onRecordingWillStop.bind(this);
this._onRecordingStopped = this._onRecordingStopped.bind(this);
this._onRecordingSelected = this._onRecordingSelected.bind(this);
this._onRecordingTick = this._onRecordingTick.bind(this);
this._onGraphSelecting = this._onGraphSelecting.bind(this);
this._onPrefChanged = this._onPrefChanged.bind(this);
@@ -56,42 +59,70 @@ let OverviewView = {
PerformanceController.off(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.off(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
},
/**
+ * Disabled in the event we're using a Timeline mock, so we'll have no
+ * markers, ticks or memory data to show, so just block rendering and hide
+ * the panel.
+ */
+ disable: function () {
+ this._disabled = true;
+ $("#overview-pane").hidden = true;
+ },
+
+ /**
+ * Returns the disabled status.
+ *
+ * @return boolean
+ */
+ isDisabled: function () {
+ return this._disabled;
+ },
+
+ /**
* Sets the time interval selection for all graphs in this overview.
*
* @param object interval
* The { starTime, endTime }, in milliseconds.
*/
setTimeInterval: function(interval, options = {}) {
+ if (this.isDisabled()) {
+ return;
+ }
+
let recording = PerformanceController.getCurrentRecording();
if (recording == null) {
throw new Error("A recording should be available in order to set the selection.");
}
let mapStart = () => 0;
let mapEnd = () => recording.getDuration();
let selection = { start: interval.startTime, end: interval.endTime };
this._stopSelectionChangeEventPropagation = options.stopPropagation;
this.markersOverview.setMappedSelection(selection, { mapStart, mapEnd });
this._stopSelectionChangeEventPropagation = false;
},
/**
* Gets the time interval selection for all graphs in this overview.
*
* @return object
- * The { starTime, endTime }, in milliseconds.
+ * The { startTime, endTime }, in milliseconds.
*/
getTimeInterval: function() {
let recording = PerformanceController.getCurrentRecording();
+
+ if (this.isDisabled()) {
+ return { startTime: 0, endTime: recording.getDuration() };
+ }
+
if (recording == null) {
throw new Error("A recording should be available in order to get the selection.");
}
let mapStart = () => 0;
let mapEnd = () => recording.getDuration();
let selection = this.markersOverview.getMappedSelection({ mapStart, mapEnd });
return { startTime: selection.min, endTime: selection.max };
},
@@ -167,16 +198,19 @@ let OverviewView = {
/**
* Method for handling all the set up for rendering the overview graphs.
*
* @param number resolution
* The fps graph resolution. @see Graphs.jsm
*/
render: Task.async(function *(resolution) {
+ if (this.isDisabled()) {
+ return;
+ }
let recording = PerformanceController.getCurrentRecording();
let duration = recording.getDuration();
let markers = recording.getMarkers();
let memory = recording.getMemory();
let timestamps = recording.getTicks();
// Empty or older recordings might yield no markers, memory or timestamps.
if (markers && (yield this._markersGraphAvailable())) {
--- a/browser/devtools/webide/modules/runtimes.js
+++ b/browser/devtools/webide/modules/runtimes.js
@@ -1,18 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const {Cu, Ci} = require("chrome");
const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
-const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
-const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
+const {Connection} = require("devtools/client/connection-manager");
const {DebuggerServer} = require("resource://gre/modules/devtools/dbg-server.jsm");
+const {Simulators} = require("devtools/webide/simulators");
const discovery = require("devtools/toolkit/discovery/discovery");
const EventEmitter = require("devtools/toolkit/event-emitter");
const promise = require("promise");
loader.lazyRequireGetter(this, "AuthenticationResult",
"devtools/toolkit/security/auth", true);
loader.lazyRequireGetter(this, "DevToolsUtils",
"devtools/toolkit/DevToolsUtils");
@@ -188,36 +188,36 @@ exports.RuntimeScanners = RuntimeScanner
/* SCANNERS */
let SimulatorScanner = {
_runtimes: [],
enable() {
this._updateRuntimes = this._updateRuntimes.bind(this);
- Simulator.on("register", this._updateRuntimes);
- Simulator.on("unregister", this._updateRuntimes);
+ Simulators.on("updated", this._updateRuntimes);
this._updateRuntimes();
},
disable() {
- Simulator.off("register", this._updateRuntimes);
- Simulator.off("unregister", this._updateRuntimes);
+ Simulators.off("updated", this._updateRuntimes);
},
_emitUpdated() {
this.emit("runtime-list-updated");
},
_updateRuntimes() {
- this._runtimes = [];
- for (let name of Simulator.availableNames()) {
- this._runtimes.push(new SimulatorRuntime(name));
- }
- this._emitUpdated();
+ Simulators.getAll().then(simulators => {
+ this._runtimes = [];
+ for (let simulator of simulators) {
+ this._runtimes.push(new SimulatorRuntime(simulator));
+ }
+ this._emitUpdated();
+ });
},
scan() {
return promise.resolve();
},
listRuntimes: function() {
return this._runtimes;
@@ -537,38 +537,36 @@ WiFiRuntime.prototype = {
}
};
}
};
// For testing use only
exports._WiFiRuntime = WiFiRuntime;
-function SimulatorRuntime(name) {
- this.name = name;
+function SimulatorRuntime(simulator) {
+ this.simulator = simulator;
}
SimulatorRuntime.prototype = {
type: RuntimeTypes.SIMULATOR,
connect: function(connection) {
- let port = ConnectionManager.getFreeTCPPort();
- let simulator = Simulator.getByName(this.name);
- if (!simulator || !simulator.launch) {
- return promise.reject(new Error("Can't find simulator: " + this.name));
- }
- return simulator.launch({port: port}).then(() => {
+ return this.simulator.launch().then(port => {
connection.host = "localhost";
connection.port = port;
connection.keepConnecting = true;
- connection.once(Connection.Events.DISCONNECTED, simulator.close);
+ connection.once(Connection.Events.DISCONNECTED, e => this.simulator.kill());
connection.connect();
});
},
get id() {
- return this.name;
+ return this.simulator.id;
+ },
+ get name() {
+ return this.simulator.name;
},
};
// For testing use only
exports._SimulatorRuntime = SimulatorRuntime;
let gLocalRuntime = {
type: RuntimeTypes.LOCAL,
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/modules/simulator-process.js
@@ -0,0 +1,272 @@
+/* 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';
+
+const { Cc, Ci, Cu } = require("chrome");
+
+const Environment = require("sdk/system/environment").env;
+const Subprocess = require("sdk/system/child_process/subprocess");
+const { EventEmitter } = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
+const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+let platform = Services.appShell.hiddenDOMWindow.navigator.platform;
+let OS = "";
+if (platform.indexOf("Win") != -1) {
+ OS = "win32";
+} else if (platform.indexOf("Mac") != -1) {
+ OS = "mac64";
+} else if (platform.indexOf("Linux") != -1) {
+ if (platform.indexOf("x86_64") != -1) {
+ OS = "linux64";
+ } else {
+ OS = "linux32";
+ }
+}
+
+function SimulatorProcess() {}
+SimulatorProcess.prototype = {
+
+ // Check if B2G is running.
+ get isRunning() !!this.process,
+
+ // Start the process and connect the debugger client.
+ run() {
+
+ // Resolve B2G binary.
+ let b2g = this.b2gBinary;
+ if (!b2g || !b2g.exists()) {
+ throw Error("B2G executable not found.");
+ }
+
+ this.once("stdout", function () {
+ if (OS == "mac64") {
+ console.debug("WORKAROUND run osascript to show b2g-desktop window on OS=='mac64'");
+ // Escape double quotes and escape characters for use in AppleScript.
+ let path = b2g.path.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
+
+ Subprocess.call({
+ command: "/usr/bin/osascript",
+ arguments: ["-e", 'tell application "' + path + '" to activate'],
+ });
+ }
+ });
+
+ this.on("stdout", (e, data) => this.log(e, data.trim()));
+ this.on("stderr", (e, data) => this.log(e, data.trim()));
+
+ let environment;
+ if (OS.indexOf("linux") > -1) {
+ environment = ["TMPDIR=" + Services.dirsvc.get("TmpD", Ci.nsIFile).path];
+ if ("DISPLAY" in Environment) {
+ environment.push("DISPLAY=" + Environment.DISPLAY);
+ }
+ }
+
+ // Spawn a B2G instance.
+ this.process = Subprocess.call({
+ command: b2g,
+ arguments: this.args,
+ environment: environment,
+ stdout: data => this.emit("stdout", data),
+ stderr: data => this.emit("stderr", data),
+ // On B2G instance exit, reset tracked process, remote debugger port and
+ // shuttingDown flag, then finally emit an exit event.
+ done: result => {
+ console.log("B2G terminated with " + result.exitCode);
+ this.process = null;
+ this.emit("exit", result.exitCode);
+ }
+ });
+ },
+
+ // Request a B2G instance kill.
+ kill() {
+ let deferred = promise.defer();
+ if (this.process) {
+ this.once("exit", (e, exitCode) => {
+ this.shuttingDown = false;
+ deferred.resolve(exitCode);
+ });
+ if (!this.shuttingDown) {
+ this.shuttingDown = true;
+ this.emit("kill", null);
+ this.process.kill();
+ }
+ return deferred.promise;
+ } else {
+ return promise.resolve(undefined);
+ }
+ },
+
+ // Maybe log output messages.
+ log(level, message) {
+ if (!Services.prefs.getBoolPref("devtools.webide.logSimulatorOutput")) {
+ return;
+ }
+ if (level === "stderr" || level === "error") {
+ console.error(message);
+ return;
+ }
+ console.log(message);
+ },
+
+ // Compute B2G CLI arguments.
+ get args() {
+ let args = [];
+
+ let gaia = this.gaiaProfile;
+ if (!gaia || !gaia.exists()) {
+ throw Error("Gaia profile directory not found.");
+ }
+ args.push("-profile", gaia.path);
+
+ args.push("-start-debugger-server", "" + this.options.port);
+
+ // Ignore eventual zombie instances of b2g that are left over.
+ args.push("-no-remote");
+
+ return args;
+ },
+};
+
+EventEmitter.decorate(SimulatorProcess.prototype);
+
+
+function CustomSimulatorProcess(options) {
+ this.options = options;
+}
+
+let CSPp = CustomSimulatorProcess.prototype = Object.create(SimulatorProcess.prototype);
+
+// Compute B2G binary file handle.
+Object.defineProperty(CSPp, "b2gBinary", {
+ get: function() {
+ let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
+ file.initWithPath(this.options.b2gBinary);
+ return file;
+ }
+});
+
+// Compute Gaia profile file handle.
+Object.defineProperty(CSPp, "gaiaProfile", {
+ get: function() {
+ let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
+ file.initWithPath(this.options.gaiaProfile);
+ return file;
+ }
+});
+
+exports.CustomSimulatorProcess = CustomSimulatorProcess;
+
+
+function AddonSimulatorProcess(addon, options) {
+ this.addon = addon;
+ this.options = options;
+}
+
+let ASPp = AddonSimulatorProcess.prototype = Object.create(SimulatorProcess.prototype);
+
+// Compute B2G binary file handle.
+Object.defineProperty(ASPp, "b2gBinary", {
+ get: function() {
+ let file;
+ try {
+ let pref = "extensions." + this.addon.id + ".customRuntime";
+ file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
+ } catch(e) {}
+
+ if (!file) {
+ file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
+ file.append("b2g");
+ let binaries = {
+ win32: "b2g-bin.exe",
+ mac64: "B2G.app/Contents/MacOS/b2g-bin",
+ linux32: "b2g-bin",
+ linux64: "b2g-bin",
+ };
+ binaries[OS].split("/").forEach(node => file.append(node));
+ }
+ return file;
+ }
+});
+
+// Compute Gaia profile file handle.
+Object.defineProperty(ASPp, "gaiaProfile", {
+ get: function() {
+ let file;
+ try {
+ let pref = "extensions." + this.addon.id + ".gaiaProfile";
+ file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
+ } catch(e) {}
+
+ if (!file) {
+ file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
+ file.append("profile");
+ }
+ return file;
+ }
+});
+
+exports.AddonSimulatorProcess = AddonSimulatorProcess;
+
+
+function OldAddonSimulatorProcess(addon, options) {
+ this.addon = addon;
+ this.options = options;
+}
+
+let OASPp = OldAddonSimulatorProcess.prototype = Object.create(AddonSimulatorProcess.prototype);
+
+// Compute B2G binary file handle.
+Object.defineProperty(OASPp, "b2gBinary", {
+ get: function() {
+ let file;
+ try {
+ let pref = "extensions." + this.addon.id + ".customRuntime";
+ file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
+ } catch(e) {}
+
+ if (!file) {
+ file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
+ let version = this.addon.name.match(/\d+\.\d+/)[0].replace(/\./, "_");
+ file.append("resources");
+ file.append("fxos_" + version + "_simulator");
+ file.append("data");
+ file.append(OS == "linux32" ? "linux" : OS);
+ let binaries = {
+ win32: "b2g/b2g-bin.exe",
+ mac64: "B2G.app/Contents/MacOS/b2g-bin",
+ linux32: "b2g/b2g-bin",
+ linux64: "b2g/b2g-bin",
+ };
+ binaries[OS].split("/").forEach(node => file.append(node));
+ }
+ return file;
+ }
+});
+
+// Compute B2G CLI arguments.
+Object.defineProperty(OASPp, "args", {
+ get: function() {
+ let args = [];
+
+ let gaia = this.gaiaProfile;
+ if (!gaia || !gaia.exists()) {
+ throw Error("Gaia profile directory not found.");
+ }
+ args.push("-profile", gaia.path);
+
+ args.push("-dbgport", "" + this.options.port);
+
+ // Ignore eventual zombie instances of b2g that are left over.
+ args.push("-no-remote");
+
+ return args;
+ }
+});
+
+exports.OldAddonSimulatorProcess = OldAddonSimulatorProcess;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/modules/simulators.js
@@ -0,0 +1,96 @@
+/* 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/. */
+
+const { Cu } = require("chrome");
+const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm");
+const { EventEmitter } = Cu.import("resource://gre/modules/devtools/event-emitter.js");
+const { ConnectionManager } = require("devtools/client/connection-manager");
+const { AddonSimulatorProcess, OldAddonSimulatorProcess } = require("devtools/webide/simulator-process");
+const promise = require("promise");
+
+const SimulatorRegExp = new RegExp(Services.prefs.getCharPref("devtools.webide.simulatorAddonRegExp"));
+
+let Simulators = {
+ // TODO (Bug 1090949) Don't generate this list from installed simulator
+ // addons, but instead implement a persistent list of user-configured
+ // simulators.
+ getAll() {
+ let deferred = promise.defer();
+ AddonManager.getAllAddons(addons => {
+ let simulators = [];
+ for (let addon of addons) {
+ if (SimulatorRegExp.exec(addon.id)) {
+ simulators.push(new Simulator(addon));
+ }
+ }
+ // Sort simulators alphabetically by name.
+ simulators.sort((a, b) => {
+ return a.name.toLowerCase().localeCompare(b.name.toLowerCase())
+ });
+ deferred.resolve(simulators);
+ });
+ return deferred.promise;
+ },
+}
+EventEmitter.decorate(Simulators);
+exports.Simulators = Simulators;
+
+function update() {
+ Simulators.emit("updated");
+}
+AddonManager.addAddonListener({
+ onEnabled: update,
+ onDisabled: update,
+ onInstalled: update,
+ onUninstalled: update
+});
+
+
+function Simulator(addon) {
+ this.addon = addon;
+}
+
+Simulator.prototype = {
+ launch() {
+ // Close already opened simulation.
+ if (this.process) {
+ return this.kill().then(this.launch.bind(this));
+ }
+
+ let options = {
+ port: ConnectionManager.getFreeTCPPort()
+ };
+
+ if (this.version <= "1.3") {
+ // Support older simulator addons.
+ this.process = new OldAddonSimulatorProcess(this.addon, options);
+ } else {
+ this.process = new AddonSimulatorProcess(this.addon, options);
+ }
+ this.process.run();
+
+ return promise.resolve(options.port);
+ },
+
+ kill() {
+ let process = this.process;
+ if (!process) {
+ return promise.resolve();
+ }
+ this.process = null;
+ return process.kill();
+ },
+
+ get id() {
+ return this.addon.id;
+ },
+
+ get name() {
+ return this.addon.name.replace(" Simulator", "");
+ },
+
+ get version() {
+ return this.name.match(/\d+\.\d+/)[0];
+ },
+};
--- a/browser/devtools/webide/moz.build
+++ b/browser/devtools/webide/moz.build
@@ -15,16 +15,18 @@ MOCHITEST_CHROME_MANIFESTS += ['test/chr
EXTRA_JS_MODULES.devtools.webide += [
'modules/addons.js',
'modules/app-manager.js',
'modules/build.js',
'modules/config-view.js',
'modules/remote-resources.js',
'modules/runtimes.js',
+ 'modules/simulator-process.js',
+ 'modules/simulators.js',
'modules/tab-store.js',
'modules/utils.js'
]
JS_PREFERENCE_FILES += [
'webide-prefs.js',
]
--- a/browser/devtools/webide/webide-prefs.js
+++ b/browser/devtools/webide/webide-prefs.js
@@ -8,23 +8,25 @@ pref("devtools.webide.templatesURL", "ht
pref("devtools.webide.autoinstallADBHelper", true);
pref("devtools.webide.autoinstallFxdtAdapters", true);
pref("devtools.webide.autoConnectRuntime", true);
pref("devtools.webide.restoreLastProject", true);
pref("devtools.webide.enableLocalRuntime", false);
pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/#VERSION#/#OS#/fxos_#SLASHED_VERSION#_simulator-#OS#-latest.xpi");
pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozilla.org");
+pref("devtools.webide.simulatorAddonRegExp", "fxos_(.*)_simulator@mozilla\.org$");
pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");
pref("devtools.webide.adaptersAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxdt-adapters/#OS#/fxdt-adapters-#OS#-latest.xpi");
pref("devtools.webide.adaptersAddonID", "fxdevtools-adapters@mozilla.org");
pref("devtools.webide.monitorWebSocketURL", "ws://localhost:9000");
pref("devtools.webide.lastConnectedRuntime", "");
pref("devtools.webide.lastSelectedProject", "");
+pref("devtools.webide.logSimulatorOutput", false);
pref("devtools.webide.widget.autoinstall", true);
#ifdef MOZ_DEV_EDITION
pref("devtools.webide.widget.enabled", true);
pref("devtools.webide.widget.inNavbarByDefault", true);
#else
pref("devtools.webide.widget.enabled", false);
pref("devtools.webide.widget.inNavbarByDefault", false);
#endif
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -832,8 +832,11 @@ just addresses the organization to follo
<!ENTITY processHang.terminateScript.label "Stop Script">
<!ENTITY processHang.terminateScript.accessKey "S">
<!ENTITY processHang.debugScript.label "Debug Script">
<!ENTITY processHang.debugScript.accessKey "D">
<!ENTITY processHang.terminatePlugin.label "Kill Plugin">
<!ENTITY processHang.terminatePlugin.accessKey "P">
<!ENTITY processHang.terminateProcess.label "Kill Web Process">
<!ENTITY processHang.terminateProcess.accessKey "K">
+
+<!ENTITY emeLearnMoreContextMenu.label "Learn more about DRM…">
+<!ENTITY emeLearnMoreContextMenu.accesskey "D">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -35,16 +35,27 @@ xpinstallDisabledButton.accesskey=n
# http://developer.mozilla.org/en/docs/Localization_and_Plurals
# Also see https://bugzilla.mozilla.org/show_bug.cgi?id=570012 for mockups
addonDownloading=Add-on downloading;Add-ons downloading
addonDownloadCancelled=Add-on download cancelled.;Add-on downloads cancelled.
addonDownloadRestart=Restart Download;Restart Downloads
addonDownloadRestart.accessKey=R
addonDownloadCancelTooltip=Cancel
+addonwatch.slow=%S might be making %S run slowly
+addonwatch.disable.label=Disable %S
+addonwatch.disable.accesskey=D
+addonwatch.ignoreSession.label=Ignore for now
+addonwatch.ignoreSession.accesskey=I
+addonwatch.ignorePerm.label=Ignore permanently
+addonwatch.ignorePerm.accesskey=p
+addonwatch.restart.message=To disable %S you must restart %S
+addonwatch.restart.label=Restart %s
+addonwatch.restart.accesskey=R
+
# LOCALIZATION NOTE (addonsInstalled, addonsInstalledNeedsRestart):
# Semicolon-separated list of plural forms. See:
# http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 first add-on's name, #2 number of add-ons, #3 application name
addonsInstalled=#1 has been installed successfully.;#2 add-ons have been installed successfully.
addonsInstalledNeedsRestart=#1 will be installed after you restart #3.;#2 add-ons will be installed after you restart #3.
addonInstallRestartButton=Restart Now
addonInstallRestartButton.accesskey=R
--- a/browser/modules/BrowserUITelemetry.jsm
+++ b/browser/modules/BrowserUITelemetry.jsm
@@ -664,16 +664,17 @@ this.BrowserUITelemetry = {
"keywordfield", "searchselect", "shareselect", "frame", "showonlythisframe",
"openframeintab", "openframe", "reloadframe", "bookmarkframe", "saveframe",
"printframe", "viewframesource", "viewframeinfo",
"viewpartialsource-selection", "viewpartialsource-mathml",
"viewsource", "viewinfo", "spell-check-enabled",
"spell-add-dictionaries-main", "spell-dictionaries",
"spell-dictionaries-menu", "spell-add-dictionaries",
"bidi-text-direction-toggle", "bidi-page-direction-toggle", "inspect",
+ "media-eme-learn-more"
]),
_contextMenuInteractions: {},
registerContextMenuInteraction: function(keys, itemID) {
if (itemID) {
if (!this._contextMenuItemWhitelist.has(itemID)) {
itemID = "other-item";
--- a/browser/themes/shared/aboutSessionRestore.css
+++ b/browser/themes/shared/aboutSessionRestore.css
@@ -6,17 +6,17 @@
.title {
background-image: url("chrome://browser/skin/session-restore.svg");
}
treechildren::-moz-tree-image(icon),
treechildren::-moz-tree-image(noicon) {
padding-right: 2px;
- margin: 0px 2px;
+ margin: 0 2px;
width: 16px;
height: 16px;
}
treechildren::-moz-tree-image(noicon) {
list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
}
treechildren::-moz-tree-image(container, noicon) {
--- a/browser/themes/shared/aboutWelcomeBack.css
+++ b/browser/themes/shared/aboutWelcomeBack.css
@@ -15,8 +15,37 @@
*/
#tabList {
display: none;
}
#tabList[available] {
display: -moz-box;
}
+
+treechildren::-moz-tree-image(icon),
+treechildren::-moz-tree-image(noicon) {
+ padding-right: 2px;
+ margin: 0 2px;
+ width: 16px;
+ height: 16px;
+}
+
+treechildren::-moz-tree-image(noicon) {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+treechildren::-moz-tree-image(container, noicon) {
+ list-style-image: url("chrome://browser/skin/aboutSessionRestore-window-icon.png");
+}
+
+treechildren::-moz-tree-image(checked) {
+ list-style-image: url("chrome://global/skin/in-content/check.svg#check");
+}
+treechildren::-moz-tree-image(checked, selected) {
+ list-style-image: url("chrome://global/skin/in-content/check.svg#check-inverted");
+}
+
+treechildren::-moz-tree-image(partial) {
+ list-style-image: url("chrome://global/skin/in-content/check-partial.svg#check-partial");
+}
+treechildren::-moz-tree-image(partial, selected) {
+ list-style-image: url("chrome://global/skin/in-content/check-partial.svg#check-partial-inverted");
+}
\ No newline at end of file
--- a/browser/themes/shared/contextmenu.inc.css
+++ b/browser/themes/shared/contextmenu.inc.css
@@ -74,8 +74,12 @@
transform: scaleX(-1);
}
#context-navigation > .menuitem-iconic > .menu-iconic-left > .menu-iconic-icon {
width: 16px;
height: 16px;
margin: 7px;
}
+
+#context-media-eme-learnmore {
+ list-style-image: url("chrome://browser/skin/drm-icon.svg#chains");
+}
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -345,16 +345,21 @@ description > html|a {
#dialogFrame {
-moz-box-flex: 1;
/* Default dialog dimensions */
height: 20em;
width: 66ch;
}
+.largeDialogContainer.doScroll {
+ overflow-y: auto;
+ -moz-box-flex: 1;
+}
+
/**
* End Dialog
*/
/**
* Sync migration
*/
#sync-migrate-upgrade-description {
--- a/browser/themes/shared/tabbrowser/crashed.svg
+++ b/browser/themes/shared/tabbrowser/crashed.svg
@@ -1,16 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="22 22 16 16" enable-background="new 22 22 16 16" xml:space="preserve">
-<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="30" y1="23" x2="30" y2="37">
- <stop offset="0" style="stop-color:#E63B2E"/>
- <stop offset="1" style="stop-color:#C33931"/>
-</linearGradient>
-<circle fill="url(#SVGID_1_)" cx="30" cy="30" r="7"/>
-<g>
- <path fill="#FFFFFF" d="M31.03,33.304c0,0.6-0.479,1.092-1.091,1.092c-0.6,0-1.079-0.492-1.079-1.092
- c0-0.588,0.479-1.079,1.079-1.079C30.551,32.225,31.03,32.716,31.03,33.304z M29.171,31.133l-0.24-5.253h2.015l-0.24,5.253H29.171z
- "/>
-</g>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ viewBox="22 22 16 16">
+ <linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="30" y1="23" x2="30" y2="37">
+ <stop offset="0" style="stop-color: #e63b2e"/>
+ <stop offset="1" style="stop-color: #c33931"/>
+ </linearGradient>
+ <circle fill="url(#gradient)" cx="30" cy="30" r="7"/>
+ <path fill="#fff" d="M31.03,33.304c0,0.6-0.479,1.092-1.091,1.092c-0.6,0-1.079-0.492-1.079-1.092 c0-0.588,0.479-1.079,1.079-1.079C30.551,32.225,31.03,32.716,31.03,33.304z M29.171,31.133l-0.24-5.253h2.015l-0.24,5.253H29.171z"/>
</svg>
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -703,16 +703,17 @@ GK_ATOM(oncommandupdate, "oncommandupdat
GK_ATOM(oncomplete, "oncomplete")
GK_ATOM(oncompositionend, "oncompositionend")
GK_ATOM(oncompositionstart, "oncompositionstart")
GK_ATOM(oncompositionupdate, "oncompositionupdate")
GK_ATOM(onconfigurationchange, "onconfigurationchange")
GK_ATOM(onconnect, "onconnect")
GK_ATOM(onconnected, "onconnected")
GK_ATOM(onconnecting, "onconnecting")
+GK_ATOM(onconnectionstatechanged, "onconnectionstatechanged")
GK_ATOM(oncontextmenu, "oncontextmenu")
GK_ATOM(oncopy, "oncopy")
GK_ATOM(oncurrentchannelchanged, "oncurrentchannelchanged")
GK_ATOM(oncurrentsourcechanged, "oncurrentsourcechanged")
GK_ATOM(oncut, "oncut")
GK_ATOM(ondatachange, "ondatachange")
GK_ATOM(ondataerror, "ondataerror")
GK_ATOM(ondblclick, "ondblclick")
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -147,16 +147,20 @@ DOMInterfaces = {
'BluetoothDevice': {
'nativeType': 'mozilla::dom::bluetooth::BluetoothDevice',
},
'BluetoothDiscoveryHandle': {
'nativeType': 'mozilla::dom::bluetooth::BluetoothDiscoveryHandle',
},
+'BluetoothGatt': {
+ 'nativeType': 'mozilla::dom::bluetooth::BluetoothGatt',
+},
+
'BluetoothManager': {
'nativeType': 'mozilla::dom::bluetooth::BluetoothManager',
},
'BluetoothPairingHandle': {
'nativeType': 'mozilla::dom::bluetooth::BluetoothPairingHandle',
},
--- a/dom/bluetooth2/BluetoothCommon.h
+++ b/dom/bluetooth2/BluetoothCommon.h
@@ -191,16 +191,22 @@ extern bool gBluetoothDebugFlag;
#define DEVICE_UNPAIRED_ID "deviceunpaired"
/**
* When receiving a query about current play status from remote device, we'll
* dispatch an event.
*/
#define REQUEST_MEDIA_PLAYSTATUS_ID "requestmediaplaystatus"
+/**
+ * When a remote BLE device gets connected / disconnected, we'll dispatch an
+ * event
+ */
+#define GATT_CONNECTION_STATE_CHANGED_ID "connectionstatechanged"
+
// Bluetooth address format: xx:xx:xx:xx:xx:xx (or xx_xx_xx_xx_xx_xx)
#define BLUETOOTH_ADDRESS_LENGTH 17
#define BLUETOOTH_ADDRESS_NONE "00:00:00:00:00:00"
#define BLUETOOTH_ADDRESS_BYTES 6
// Bluetooth stack internal error, such as I/O error
#define ERR_INTERNAL_ERROR "InternalError"
--- a/dom/bluetooth2/BluetoothDevice.cpp
+++ b/dom/bluetooth2/BluetoothDevice.cpp
@@ -1,31 +1,35 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
-#include "BluetoothClassOfDevice.h"
-#include "BluetoothDevice.h"
#include "BluetoothReplyRunnable.h"
#include "BluetoothService.h"
#include "BluetoothUtils.h"
-#include "mozilla/dom/bluetooth/BluetoothTypes.h"
#include "mozilla/dom/BluetoothAttributeEvent.h"
#include "mozilla/dom/BluetoothDevice2Binding.h"
+#include "mozilla/dom/bluetooth/BluetoothClassOfDevice.h"
+#include "mozilla/dom/bluetooth/BluetoothDevice.h"
+#include "mozilla/dom/bluetooth/BluetoothGatt.h"
+#include "mozilla/dom/bluetooth/BluetoothTypes.h"
#include "mozilla/dom/Promise.h"
using namespace mozilla;
using namespace mozilla::dom;
USING_BLUETOOTH_NAMESPACE
-NS_IMPL_CYCLE_COLLECTION_INHERITED(BluetoothDevice, DOMEventTargetHelper, mCod)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(BluetoothDevice,
+ DOMEventTargetHelper,
+ mCod,
+ mGatt)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothDevice)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
NS_IMPL_ADDREF_INHERITED(BluetoothDevice, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(BluetoothDevice, DOMEventTargetHelper)
class FetchUuidsTask MOZ_FINAL : public BluetoothReplyRunnable
{
@@ -297,13 +301,26 @@ BluetoothDevice::DispatchAttributeEvent(
init.mAttrs = value;
nsRefPtr<BluetoothAttributeEvent> event =
BluetoothAttributeEvent::Constructor(this,
NS_LITERAL_STRING("attributechanged"),
init);
DispatchTrustedEvent(event);
}
+BluetoothGatt*
+BluetoothDevice::GetGatt()
+{
+ NS_ENSURE_TRUE(mType == BluetoothDeviceType::Le ||
+ mType == BluetoothDeviceType::Dual,
+ nullptr);
+ if (!mGatt) {
+ mGatt = new BluetoothGatt(GetOwner(), mAddress);
+ }
+
+ return mGatt;
+}
+
JSObject*
BluetoothDevice::WrapObject(JSContext* aContext)
{
return BluetoothDeviceBinding::Wrap(aContext, this);
}
--- a/dom/bluetooth2/BluetoothDevice.h
+++ b/dom/bluetooth2/BluetoothDevice.h
@@ -5,29 +5,30 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_bluetooth_bluetoothdevice_h__
#define mozilla_dom_bluetooth_bluetoothdevice_h__
#include "mozilla/Attributes.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/dom/BluetoothDevice2Binding.h"
-#include "BluetoothCommon.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
#include "nsString.h"
#include "nsCOMPtr.h"
namespace mozilla {
namespace dom {
class Promise;
}
}
BEGIN_BLUETOOTH_NAMESPACE
class BluetoothClassOfDevice;
+class BluetoothGatt;
class BluetoothNamedValue;
class BluetoothValue;
class BluetoothSignal;
class BluetoothSocket;
class BluetoothDevice MOZ_FINAL : public DOMEventTargetHelper
, public BluetoothSignalObserver
{
@@ -64,16 +65,18 @@ public:
aUuids = mUuids;
}
BluetoothDeviceType Type() const
{
return mType;
}
+ BluetoothGatt* GetGatt();
+
/****************************************************************************
* Event Handlers
***************************************************************************/
IMPL_EVENT_HANDLER(attributechanged);
/****************************************************************************
* Methods (Web API Implementation)
***************************************************************************/
@@ -168,16 +171,21 @@ private:
* Cached UUID list of services which this device provides.
*/
nsTArray<nsString> mUuids;
/**
* Type of this device. Can be unknown/classic/le/dual.
*/
BluetoothDeviceType mType;
+
+ /**
+ * GATT client object to interact with the remote device.
+ */
+ nsRefPtr<BluetoothGatt> mGatt;
};
END_BLUETOOTH_NAMESPACE
/**
* Explicit Specialization of Function Templates
*
* Allows customizing the template code for a given set of template arguments.
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth2/BluetoothGatt.cpp
@@ -0,0 +1,217 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "BluetoothReplyRunnable.h"
+#include "BluetoothService.h"
+#include "BluetoothUtils.h"
+#include "mozilla/dom/bluetooth/BluetoothGatt.h"
+#include "mozilla/dom/bluetooth/BluetoothTypes.h"
+#include "mozilla/dom/BluetoothGattBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "nsIUUIDGenerator.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+USING_BLUETOOTH_NAMESPACE
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothGatt)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothGatt,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothGatt,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothGatt)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(BluetoothGatt, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(BluetoothGatt, DOMEventTargetHelper)
+
+BluetoothGatt::BluetoothGatt(nsPIDOMWindow* aWindow,
+ const nsAString& aDeviceAddr)
+ : DOMEventTargetHelper(aWindow)
+ , mAppUuid(EmptyString())
+ , mClientIf(0)
+ , mConnectionState(BluetoothConnectionState::Disconnected)
+ , mDeviceAddr(aDeviceAddr)
+{
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(!mDeviceAddr.IsEmpty());
+}
+
+BluetoothGatt::~BluetoothGatt()
+{
+ BluetoothService* bs = BluetoothService::Get();
+ // bs can be null on shutdown, where destruction might happen.
+ NS_ENSURE_TRUE_VOID(bs);
+
+ if (mClientIf > 0) {
+ nsRefPtr<BluetoothVoidReplyRunnable> result =
+ new BluetoothVoidReplyRunnable(nullptr);
+ bs->UnregisterGattClientInternal(mClientIf, result);
+ }
+
+ bs->UnregisterBluetoothSignalHandler(mAppUuid, this);
+}
+
+void
+BluetoothGatt::GenerateUuid(nsAString &aUuidString)
+{
+ nsresult rv;
+ nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
+ do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsID uuid;
+ rv = uuidGenerator->GenerateUUIDInPlace(&uuid);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format
+ char uuidBuffer[NSID_LENGTH];
+ uuid.ToProvidedString(uuidBuffer);
+ NS_ConvertASCIItoUTF16 uuidString(uuidBuffer);
+
+ // Remove {} and the null terminator
+ aUuidString.Assign(Substring(uuidString, 1, NSID_LENGTH - 3));
+}
+
+void
+BluetoothGatt::DisconnectFromOwner()
+{
+ DOMEventTargetHelper::DisconnectFromOwner();
+
+ BluetoothService* bs = BluetoothService::Get();
+ NS_ENSURE_TRUE_VOID(bs);
+
+ if (mClientIf > 0) {
+ nsRefPtr<BluetoothVoidReplyRunnable> result =
+ new BluetoothVoidReplyRunnable(nullptr);
+ bs->UnregisterGattClientInternal(mClientIf, result);
+ }
+
+ bs->UnregisterBluetoothSignalHandler(mAppUuid, this);
+}
+
+already_AddRefed<Promise>
+BluetoothGatt::Connect(ErrorResult& aRv)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+ NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+ BT_ENSURE_TRUE_REJECT(
+ mConnectionState == BluetoothConnectionState::Disconnected,
+ NS_ERROR_DOM_INVALID_STATE_ERR);
+
+ BluetoothService* bs = BluetoothService::Get();
+ BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
+
+ if (mAppUuid.IsEmpty()) {
+ GenerateUuid(mAppUuid);
+ BT_ENSURE_TRUE_REJECT(!mAppUuid.IsEmpty(),
+ NS_ERROR_DOM_OPERATION_ERR);
+ bs->RegisterBluetoothSignalHandler(mAppUuid, this);
+ }
+
+ UpdateConnectionState(BluetoothConnectionState::Connecting);
+ nsRefPtr<BluetoothReplyRunnable> result =
+ new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
+ promise,
+ NS_LITERAL_STRING("ConnectGattClient"));
+ bs->ConnectGattClientInternal(mAppUuid,
+ mDeviceAddr,
+ result);
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+BluetoothGatt::Disconnect(ErrorResult& aRv)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+ NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+ BT_ENSURE_TRUE_REJECT(
+ mConnectionState == BluetoothConnectionState::Connected,
+ NS_ERROR_DOM_INVALID_STATE_ERR);
+
+ BluetoothService* bs = BluetoothService::Get();
+ BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
+
+ UpdateConnectionState(BluetoothConnectionState::Disconnecting);
+ nsRefPtr<BluetoothReplyRunnable> result =
+ new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
+ promise,
+ NS_LITERAL_STRING("DisconnectGattClient"));
+ bs->DisconnectGattClientInternal(mAppUuid, mDeviceAddr, result);
+
+ return promise.forget();
+}
+
+void
+BluetoothGatt::UpdateConnectionState(BluetoothConnectionState aState)
+{
+ BT_API2_LOGR("GATT connection state changes to: %d", int(aState));
+ mConnectionState = aState;
+
+ // Dispatch connectionstatechanged event to application
+ nsCOMPtr<nsIDOMEvent> event;
+ nsresult rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = event->InitEvent(NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+ false,
+ false);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ DispatchTrustedEvent(event);
+}
+
+void
+BluetoothGatt::Notify(const BluetoothSignal& aData)
+{
+ BT_LOGD("[D] %s", NS_ConvertUTF16toUTF8(aData.name()).get());
+
+ BluetoothValue v = aData.value();
+ if (aData.name().EqualsLiteral("ClientRegistered")) {
+ MOZ_ASSERT(v.type() == BluetoothValue::Tuint32_t);
+ mClientIf = v.get_uint32_t();
+ } else if (aData.name().EqualsLiteral("ClientUnregistered")) {
+ mClientIf = 0;
+ } else if (aData.name().EqualsLiteral(GATT_CONNECTION_STATE_CHANGED_ID)) {
+ MOZ_ASSERT(v.type() == BluetoothValue::Tbool);
+
+ BluetoothConnectionState state =
+ v.get_bool() ? BluetoothConnectionState::Connected
+ : BluetoothConnectionState::Disconnected;
+ UpdateConnectionState(state);
+ } else {
+ BT_WARNING("Not handling GATT signal: %s",
+ NS_ConvertUTF16toUTF8(aData.name()).get());
+ }
+}
+
+JSObject*
+BluetoothGatt::WrapObject(JSContext* aContext)
+{
+ return BluetoothGattBinding::Wrap(aContext, this);
+}
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth2/BluetoothGatt.h
@@ -0,0 +1,116 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_bluetooth_bluetoothgatt_h__
+#define mozilla_dom_bluetooth_bluetoothgatt_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/BluetoothGattBinding.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace dom {
+class Promise;
+}
+}
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothReplyRunnable;
+class BluetoothService;
+class BluetoothSignal;
+class BluetoothValue;
+
+class BluetoothGatt MOZ_FINAL : public DOMEventTargetHelper
+ , public BluetoothSignalObserver
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothGatt, DOMEventTargetHelper)
+
+ /****************************************************************************
+ * Attribute Getters
+ ***************************************************************************/
+ BluetoothConnectionState ConnectionState() const
+ {
+ return mConnectionState;
+ }
+
+ /****************************************************************************
+ * Event Handlers
+ ***************************************************************************/
+ IMPL_EVENT_HANDLER(connectionstatechanged);
+
+ /****************************************************************************
+ * Methods (Web API Implementation)
+ ***************************************************************************/
+ already_AddRefed<Promise> Connect(ErrorResult& aRv);
+ already_AddRefed<Promise> Disconnect(ErrorResult& aRv);
+
+ /****************************************************************************
+ * Others
+ ***************************************************************************/
+ void Notify(const BluetoothSignal& aParam); // BluetoothSignalObserver
+
+ nsPIDOMWindow* GetParentObject() const
+ {
+ return GetOwner();
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+ virtual void DisconnectFromOwner() MOZ_OVERRIDE;
+
+ BluetoothGatt(nsPIDOMWindow* aOwner,
+ const nsAString& aDeviceAddr);
+
+private:
+ ~BluetoothGatt();
+
+ /**
+ * Update mConnectionState to aState and fire
+ * connectionstatechanged event to the application.
+ *
+ * @param aState [in] New connection state
+ */
+ void UpdateConnectionState(BluetoothConnectionState aState);
+
+ /**
+ * Generate a random uuid.
+ *
+ * @param aUuidString [out] String to store the generated uuid.
+ */
+ void GenerateUuid(nsAString &aUuidString);
+
+ /****************************************************************************
+ * Variables
+ ***************************************************************************/
+ /**
+ * Random generated UUID of this GATT client.
+ */
+ nsString mAppUuid;
+
+ /**
+ * Id of the GATT client interface given by bluetooth stack.
+ * 0 if the client is not registered yet, nonzero otherwise.
+ */
+ int mClientIf;
+
+ /**
+ * Connection state of this remote device.
+ */
+ BluetoothConnectionState mConnectionState;
+
+ /**
+ * Address of the remote device.
+ */
+ nsString mDeviceAddr;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif
--- a/dom/bluetooth2/BluetoothService.h
+++ b/dom/bluetooth2/BluetoothService.h
@@ -302,16 +302,41 @@ public:
virtual nsresult
SendSinkMessage(const nsAString& aDeviceAddresses,
const nsAString& aMessage) = 0;
virtual nsresult
SendInputMessage(const nsAString& aDeviceAddresses,
const nsAString& aMessage) = 0;
+ /**
+ * Connect to a remote GATT server. (platform specific implementation)
+ */
+ virtual void
+ ConnectGattClientInternal(const nsAString& aAppUuid,
+ const nsAString& aDeviceAddress,
+ BluetoothReplyRunnable* aRunnable) = 0;
+
+ /**
+ * Disconnect GATT client from a remote GATT server.
+ * (platform specific implementation)
+ */
+ virtual void
+ DisconnectGattClientInternal(const nsAString& aAppUuid,
+ const nsAString& aDeviceAddress,
+ BluetoothReplyRunnable* aRunnable) = 0;
+
+ /**
+ * Unregister a GATT client. (platform specific implementation)
+ */
+ virtual void
+ UnregisterGattClientInternal(int aClientIf,
+ BluetoothReplyRunnable* aRunnable) = 0;
+
+
bool
IsEnabled() const
{
return mEnabled;
}
bool
IsToggling() const;
--- a/dom/bluetooth2/BluetoothUtils.cpp
+++ b/dom/bluetooth2/BluetoothUtils.cpp
@@ -35,16 +35,40 @@ UuidToString(const BluetoothUuid& aUuid,
ntohl(uuid0), ntohs(uuid1),
ntohs(uuid2), ntohs(uuid3),
ntohl(uuid4), ntohs(uuid5));
aString.Truncate();
aString.AssignLiteral(uuidStr);
}
+void
+StringToUuid(const char* aString, BluetoothUuid& aUuid)
+{
+ uint32_t uuid0, uuid4;
+ uint16_t uuid1, uuid2, uuid3, uuid5;
+
+ sscanf(aString, "%08x-%04hx-%04hx-%04hx-%08x%04hx",
+ &uuid0, &uuid1, &uuid2, &uuid3, &uuid4, &uuid5);
+
+ uuid0 = htonl(uuid0);
+ uuid1 = htons(uuid1);
+ uuid2 = htons(uuid2);
+ uuid3 = htons(uuid3);
+ uuid4 = htonl(uuid4);
+ uuid5 = htons(uuid5);
+
+ memcpy(&aUuid.mUuid[0], &uuid0, 4);
+ memcpy(&aUuid.mUuid[4], &uuid1, 2);
+ memcpy(&aUuid.mUuid[6], &uuid2, 2);
+ memcpy(&aUuid.mUuid[8], &uuid3, 2);
+ memcpy(&aUuid.mUuid[10], &uuid4, 4);
+ memcpy(&aUuid.mUuid[14], &uuid5, 2);
+}
+
bool
SetJsObject(JSContext* aContext,
const BluetoothValue& aValue,
JS::Handle<JSObject*> aObj)
{
MOZ_ASSERT(aContext && aObj);
if (aValue.type() != BluetoothValue::TArrayOfBluetoothNamedValue) {
--- a/dom/bluetooth2/BluetoothUtils.h
+++ b/dom/bluetooth2/BluetoothUtils.h
@@ -14,16 +14,24 @@ BEGIN_BLUETOOTH_NAMESPACE
class BluetoothNamedValue;
class BluetoothValue;
class BluetoothReplyRunnable;
void
UuidToString(const BluetoothUuid& aUuid, nsAString& aString);
+/**
+ * Convert xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx uuid string to BluetoothUuid object.
+ * This utility function is used by gecko internal only to convert uuid string
+ * created by gecko back to BluetoothUuid representation.
+ */
+void
+StringToUuid(const char* aString, BluetoothUuid& aUuid);
+
bool
SetJsObject(JSContext* aContext,
const BluetoothValue& aValue,
JS::Handle<JSObject*> aObj);
bool
BroadcastSystemMessage(const nsAString& aType,
const BluetoothValue& aData);
--- a/dom/bluetooth2/bluedroid/BluetoothGattManager.cpp
+++ b/dom/bluetooth2/bluedroid/BluetoothGattManager.cpp
@@ -1,39 +1,100 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BluetoothGattManager.h"
+
#include "BluetoothCommon.h"
+#include "BluetoothInterface.h"
+#include "BluetoothReplyRunnable.h"
+#include "BluetoothService.h"
#include "BluetoothUtils.h"
-#include "BluetoothInterface.h"
-
+#include "MainThreadUtils.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
-#include "MainThreadUtils.h"
#include "nsIObserverService.h"
#include "nsThreadUtils.h"
+#define ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(runnable) \
+ do { \
+ if (!sBluetoothGattInterface) { \
+ NS_NAMED_LITERAL_STRING(errorStr, \
+ "BluetoothGattClientInterface is not ready"); \
+ DispatchBluetoothReply(runnable, BluetoothValue(), errorStr); \
+ return; \
+ } \
+ } while(0)
+
using namespace mozilla;
USING_BLUETOOTH_NAMESPACE
namespace {
StaticRefPtr<BluetoothGattManager> sBluetoothGattManager;
static BluetoothGattInterface* sBluetoothGattInterface;
static BluetoothGattClientInterface* sBluetoothGattClientInterface;
} // anonymous namespace
bool BluetoothGattManager::mInShutdown = false;
-/*
- * Static functions
- */
+class BluetoothGattClient;
+static StaticAutoPtr<nsTArray<nsRefPtr<BluetoothGattClient> > > sClients;
+
+class BluetoothGattClient MOZ_FINAL : public nsISupports
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ BluetoothGattClient(const nsAString& aAppUuid, const nsAString& aDeviceAddr)
+ : mAppUuid(aAppUuid)
+ , mDeviceAddr(aDeviceAddr)
+ , mClientIf(0)
+ , mConnId(0)
+ { }
+
+ ~BluetoothGattClient()
+ {
+ mConnectRunnable = nullptr;
+ mDisconnectRunnable = nullptr;
+ mUnregisterClientRunnable = nullptr;
+ }
+
+ nsString mAppUuid;
+ nsString mDeviceAddr;
+ int mClientIf;
+ int mConnId;
+ nsRefPtr<BluetoothReplyRunnable> mConnectRunnable;
+ nsRefPtr<BluetoothReplyRunnable> mDisconnectRunnable;
+ nsRefPtr<BluetoothReplyRunnable> mUnregisterClientRunnable;
+};
+
+NS_IMPL_ISUPPORTS0(BluetoothGattClient)
+
+class UuidComparator
+{
+public:
+ bool Equals(const nsRefPtr<BluetoothGattClient>& aClient,
+ const nsAString& aAppUuid) const
+ {
+ return aClient->mAppUuid.Equals(aAppUuid);
+ }
+};
+
+class ClientIfComparator
+{
+public:
+ bool Equals(const nsRefPtr<BluetoothGattClient>& aClient,
+ int aClientIf) const
+ {
+ return aClient->mClientIf == aClientIf;
+ }
+};
BluetoothGattManager*
BluetoothGattManager::Get()
{
MOZ_ASSERT(NS_IsMainThread());
// If sBluetoothGattManager already exists, exit early
if (sBluetoothGattManager) {
@@ -98,16 +159,20 @@ BluetoothGattManager::InitGattInterface(
}
return;
}
sBluetoothGattClientInterface =
sBluetoothGattInterface->GetBluetoothGattClientInterface();
NS_ENSURE_TRUE_VOID(sBluetoothGattClientInterface);
+ if (!sClients) {
+ sClients = new nsTArray<nsRefPtr<BluetoothGattClient> >;
+ }
+
BluetoothGattManager* gattManager = BluetoothGattManager::Get();
sBluetoothGattInterface->Init(gattManager,
new InitGattResultHandler(aRes));
}
class BluetoothGattManager::CleanupResultHandler MOZ_FINAL
: public BluetoothGattResultHandler
{
@@ -124,16 +189,18 @@ public:
mRes->OnError(NS_ERROR_FAILURE);
}
}
void Cleanup() MOZ_OVERRIDE
{
sBluetoothGattClientInterface = nullptr;
sBluetoothGattInterface = nullptr;
+ sClients = nullptr;
+
if (mRes) {
mRes->Deinit();
}
}
private:
nsRefPtr<BluetoothProfileResultHandler> mRes;
};
@@ -171,44 +238,455 @@ BluetoothGattManager::DeinitGattInterfac
// behave as if GATT was initialized.
nsRefPtr<nsRunnable> r = new CleanupResultHandlerRunnable(aRes);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch cleanup-result-handler runnable");
}
}
}
+class BluetoothGattManager::RegisterClientResultHandler MOZ_FINAL
+ : public BluetoothGattClientResultHandler
+{
+public:
+ RegisterClientResultHandler(BluetoothGattClient* aClient)
+ : mClient(aClient)
+ {
+ MOZ_ASSERT(mClient);
+ }
+
+ void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
+ {
+ BT_WARNING("BluetoothGattClientInterface::RegisterClient failed: %d",
+ (int)aStatus);
+
+ BluetoothService* bs = BluetoothService::Get();
+ NS_ENSURE_TRUE_VOID(bs);
+
+ // Notify BluetoothGatt for client disconnected
+ BluetoothSignal signal(
+ NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+ mClient->mAppUuid,
+ BluetoothValue(false)); // Disconnected
+ bs->DistributeSignal(signal);
+
+ // Reject the connect request
+ if (mClient->mConnectRunnable) {
+ NS_NAMED_LITERAL_STRING(errorStr, "Register GATT client failed");
+ DispatchBluetoothReply(mClient->mConnectRunnable,
+ BluetoothValue(),
+ errorStr);
+ mClient->mConnectRunnable = nullptr;
+ }
+
+ sClients->RemoveElement(mClient);
+ }
+
+private:
+ nsRefPtr<BluetoothGattClient> mClient;
+};
+
+class BluetoothGattManager::UnregisterClientResultHandler MOZ_FINAL
+ : public BluetoothGattClientResultHandler
+{
+public:
+ UnregisterClientResultHandler(BluetoothGattClient* aClient)
+ : mClient(aClient)
+ {
+ MOZ_ASSERT(mClient);
+ }
+
+ void UnregisterClient() MOZ_OVERRIDE
+ {
+ MOZ_ASSERT(mClient->mUnregisterClientRunnable);
+ BluetoothService* bs = BluetoothService::Get();
+ NS_ENSURE_TRUE_VOID(bs);
+
+ // Notify BluetoothGatt to clear the clientIf
+ BluetoothSignal signal(
+ NS_LITERAL_STRING("ClientUnregistered"),
+ mClient->mAppUuid,
+ BluetoothValue(true));
+ bs->DistributeSignal(signal);
+
+ // Resolve the unregister request
+ DispatchBluetoothReply(mClient->mUnregisterClientRunnable,
+ BluetoothValue(true),
+ EmptyString());
+ mClient->mUnregisterClientRunnable = nullptr;
+
+ sClients->RemoveElement(mClient);
+ }
+
+ void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
+ {
+ BT_WARNING("BluetoothGattClientInterface::UnregisterClient failed: %d",
+ (int)aStatus);
+ MOZ_ASSERT(mClient->mUnregisterClientRunnable);
+
+ // Reject the unregister request
+ NS_NAMED_LITERAL_STRING(errorStr, "Unregister GATT client failed");
+ DispatchBluetoothReply(mClient->mUnregisterClientRunnable,
+ BluetoothValue(),
+ errorStr);
+ mClient->mUnregisterClientRunnable = nullptr;
+ }
+
+private:
+ nsRefPtr<BluetoothGattClient> mClient;
+};
+
+void
+BluetoothGattManager::UnregisterClient(int aClientIf,
+ BluetoothReplyRunnable* aRunnable)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aRunnable);
+
+ ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+ size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
+ ClientIfComparator());
+
+ // Reject the unregister request if the client is not found
+ if (index == sClients->NoIndex) {
+ NS_NAMED_LITERAL_STRING(errorStr, "Unregister GATT client failed");
+ DispatchBluetoothReply(aRunnable,
+ BluetoothValue(),
+ errorStr);
+ return;
+ }
+
+ nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+ client->mUnregisterClientRunnable = aRunnable;
+
+ sBluetoothGattClientInterface->UnregisterClient(
+ aClientIf,
+ new UnregisterClientResultHandler(client));
+}
+
+class BluetoothGattManager::ConnectResultHandler MOZ_FINAL
+ : public BluetoothGattClientResultHandler
+{
+public:
+ ConnectResultHandler(BluetoothGattClient* aClient)
+ : mClient(aClient)
+ {
+ MOZ_ASSERT(mClient);
+ }
+
+ void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
+ {
+ BT_WARNING("BluetoothGattClientInterface::Connect failed: %d",
+ (int)aStatus);
+ MOZ_ASSERT(mClient->mConnectRunnable);
+
+ BluetoothService* bs = BluetoothService::Get();
+ NS_ENSURE_TRUE_VOID(bs);
+
+ // Notify BluetoothGatt for client disconnected
+ BluetoothSignal signal(
+ NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+ mClient->mAppUuid,
+ BluetoothValue(false)); // Disconnected
+ bs->DistributeSignal(signal);
+
+ // Reject the connect request
+ NS_NAMED_LITERAL_STRING(errorStr, "Connect failed");
+ DispatchBluetoothReply(mClient->mConnectRunnable,
+ BluetoothValue(),
+ errorStr);
+ mClient->mConnectRunnable = nullptr;
+ }
+
+private:
+ nsRefPtr<BluetoothGattClient> mClient;
+};
+
+void
+BluetoothGattManager::Connect(const nsAString& aAppUuid,
+ const nsAString& aDeviceAddr,
+ BluetoothReplyRunnable* aRunnable)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aRunnable);
+
+ ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+ size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+ if (index == sClients->NoIndex) {
+ index = sClients->Length();
+ sClients->AppendElement(new BluetoothGattClient(aAppUuid, aDeviceAddr));
+ }
+
+ nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+ client->mConnectRunnable = aRunnable;
+
+ if (client->mClientIf > 0) {
+ sBluetoothGattClientInterface->Connect(client->mClientIf,
+ aDeviceAddr,
+ true, // direct connect
+ new ConnectResultHandler(client));
+ } else {
+ BluetoothUuid uuid;
+ StringToUuid(NS_ConvertUTF16toUTF8(aAppUuid).get(), uuid);
+
+ // connect will be proceeded after client registered
+ sBluetoothGattClientInterface->RegisterClient(
+ uuid, new RegisterClientResultHandler(client));
+ }
+}
+
+class BluetoothGattManager::DisconnectResultHandler MOZ_FINAL
+ : public BluetoothGattClientResultHandler
+{
+public:
+ DisconnectResultHandler(BluetoothGattClient* aClient)
+ : mClient(aClient)
+ {
+ MOZ_ASSERT(mClient);
+ }
+
+ void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
+ {
+ BT_WARNING("BluetoothGattClientInterface::Disconnect failed: %d",
+ (int)aStatus);
+ MOZ_ASSERT(mClient->mDisconnectRunnable);
+
+ BluetoothService* bs = BluetoothService::Get();
+ NS_ENSURE_TRUE_VOID(bs);
+
+ // Notify BluetoothGatt that the client remains connected
+ BluetoothSignal signal(
+ NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+ mClient->mAppUuid,
+ BluetoothValue(true)); // Connected
+ bs->DistributeSignal(signal);
+
+ // Reject the disconnect request
+ NS_NAMED_LITERAL_STRING(errorStr, "Disconnect failed");
+ DispatchBluetoothReply(mClient->mDisconnectRunnable,
+ BluetoothValue(),
+ errorStr);
+ mClient->mDisconnectRunnable = nullptr;
+ }
+
+private:
+ nsRefPtr<BluetoothGattClient> mClient;
+};
+
+void
+BluetoothGattManager::Disconnect(const nsAString& aAppUuid,
+ const nsAString& aDeviceAddr,
+ BluetoothReplyRunnable* aRunnable)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aRunnable);
+
+ ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+ size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+
+ // Reject the disconnect request if the client is not found
+ if (index == sClients->NoIndex) {
+ NS_NAMED_LITERAL_STRING(errorStr, "Disconnect failed");
+ DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
+ return;
+ }
+
+ nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+ client->mDisconnectRunnable = aRunnable;
+
+ sBluetoothGattClientInterface->Disconnect(
+ client->mClientIf,
+ aDeviceAddr,
+ client->mConnId,
+ new DisconnectResultHandler(client));
+}
+
//
// Notification Handlers
//
void
BluetoothGattManager::RegisterClientNotification(int aStatus,
int aClientIf,
const BluetoothUuid& aAppUuid)
-{ }
+{
+ BT_API2_LOGR("Client Registered, clientIf = %d", aClientIf);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsString uuid;
+ UuidToString(aAppUuid, uuid);
+
+ size_t index = sClients->IndexOf(uuid, 0 /* Start */, UuidComparator());
+ NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+ nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+ BluetoothService* bs = BluetoothService::Get();
+ NS_ENSURE_TRUE_VOID(bs);
+
+ if (aStatus) { // operation failed
+ BT_API2_LOGR(
+ "RegisterClient failed, clientIf = %d, status = %d, appUuid = %s",
+ aClientIf, aStatus, NS_ConvertUTF16toUTF8(uuid).get());
+
+ // Notify BluetoothGatt for client disconnected
+ BluetoothSignal signal(
+ NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+ uuid, BluetoothValue(false)); // Disconnected
+ bs->DistributeSignal(signal);
+
+ // Reject the connect request
+ if (client->mConnectRunnable) {
+ NS_NAMED_LITERAL_STRING(errorStr,
+ "Connect failed due to registration failed");
+ DispatchBluetoothReply(client->mConnectRunnable,
+ BluetoothValue(),
+ errorStr);
+ client->mConnectRunnable = nullptr;
+ }
+
+ sClients->RemoveElement(client);
+ return;
+ }
+
+ client->mClientIf = aClientIf;
+
+ // Notify BluetoothGatt to update the clientIf
+ BluetoothSignal signal(
+ NS_LITERAL_STRING("ClientRegistered"),
+ uuid, BluetoothValue(uint32_t(aClientIf)));
+ bs->DistributeSignal(signal);
+
+ // Client just registered, proceed remaining connect request.
+ if (client->mConnectRunnable) {
+ sBluetoothGattClientInterface->Connect(
+ aClientIf, client->mDeviceAddr, true /* direct connect */,
+ new ConnectResultHandler(client));
+ }
+}
void
BluetoothGattManager::ScanResultNotification(
const nsAString& aBdAddr, int aRssi,
const BluetoothGattAdvData& aAdvData)
{ }
void
BluetoothGattManager::ConnectNotification(int aConnId,
int aStatus,
int aClientIf,
- const nsAString& aBdAddr)
-{ }
+ const nsAString& aDeviceAddr)
+{
+ BT_API2_LOGR();
+ MOZ_ASSERT(NS_IsMainThread());
+
+ BluetoothService* bs = BluetoothService::Get();
+ NS_ENSURE_TRUE_VOID(bs);
+
+ size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
+ ClientIfComparator());
+ NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+ nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+ if (aStatus) { // operation failed
+ BT_API2_LOGR("Connect failed, clientIf = %d, connId = %d, status = %d",
+ aClientIf, aConnId, aStatus);
+
+ // Notify BluetoothGatt that the client remains disconnected
+ BluetoothSignal signal(
+ NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+ client->mAppUuid,
+ BluetoothValue(false)); // Disconnected
+ bs->DistributeSignal(signal);
+
+ // Reject the connect request
+ if (client->mConnectRunnable) {
+ NS_NAMED_LITERAL_STRING(errorStr, "Connect failed");
+ DispatchBluetoothReply(client->mConnectRunnable,
+ BluetoothValue(),
+ errorStr);
+ client->mConnectRunnable = nullptr;
+ }
+
+ return;
+ }
+
+ client->mConnId = aConnId;
+
+ // Notify BluetoothGatt for client connected
+ BluetoothSignal signal(
+ NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+ client->mAppUuid,
+ BluetoothValue(true)); // Connected
+ bs->DistributeSignal(signal);
+
+ // Resolve the connect request
+ if (client->mConnectRunnable) {
+ DispatchBluetoothReply(client->mConnectRunnable,
+ BluetoothValue(true),
+ EmptyString());
+ client->mConnectRunnable = nullptr;
+ }
+}
void
BluetoothGattManager::DisconnectNotification(int aConnId,
int aStatus,
int aClientIf,
- const nsAString& aBdAddr)
-{ }
+ const nsAString& aDeviceAddr)
+{
+ BT_API2_LOGR();
+ MOZ_ASSERT(NS_IsMainThread());
+
+ BluetoothService* bs = BluetoothService::Get();
+ NS_ENSURE_TRUE_VOID(bs);
+
+ size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
+ ClientIfComparator());
+ NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+ nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+ if (aStatus) { // operation failed
+ // Notify BluetoothGatt that the client remains connected
+ BluetoothSignal signal(
+ NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+ client->mAppUuid,
+ BluetoothValue(true)); // Connected
+ bs->DistributeSignal(signal);
+
+ // Reject the disconnect request
+ if (client->mDisconnectRunnable) {
+ NS_NAMED_LITERAL_STRING(errorStr, "Disconnect failed");
+ DispatchBluetoothReply(client->mDisconnectRunnable,
+ BluetoothValue(),
+ errorStr);
+ client->mDisconnectRunnable = nullptr;
+ }
+
+ return;
+ }
+
+ client->mConnId = 0;
+
+ // Notify BluetoothGatt for client disconnected
+ BluetoothSignal signal(
+ NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+ client->mAppUuid,
+ BluetoothValue(false)); // Disconnected
+ bs->DistributeSignal(signal);
+
+ // Resolve the disconnect request
+ if (client->mDisconnectRunnable) {
+ DispatchBluetoothReply(client->mDisconnectRunnable,
+ BluetoothValue(true),
+ EmptyString());
+ client->mDisconnectRunnable = nullptr;
+ }
+}
void
BluetoothGattManager::SearchCompleteNotification(int aConnId, int aStatus)
{ }
void
BluetoothGattManager::SearchResultNotification(
int aConnId, const BluetoothGattServiceId& aServiceId)
--- a/dom/bluetooth2/bluedroid/BluetoothGattManager.h
+++ b/dom/bluetooth2/bluedroid/BluetoothGattManager.h
@@ -7,32 +7,50 @@
#ifndef mozilla_dom_bluetooth_bluetoothgattmanager_h__
#define mozilla_dom_bluetooth_bluetoothgattmanager_h__
#include "BluetoothCommon.h"
#include "BluetoothInterface.h"
#include "BluetoothProfileManagerBase.h"
BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothReplyRunnable;
+
class BluetoothGattManager MOZ_FINAL : public nsIObserver
, public BluetoothGattNotificationHandler
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
static BluetoothGattManager* Get();
static void InitGattInterface(BluetoothProfileResultHandler* aRes);
static void DeinitGattInterface(BluetoothProfileResultHandler* aRes);
virtual ~BluetoothGattManager();
+ void Connect(const nsAString& aAppUuid,
+ const nsAString& aDeviceAddr,
+ BluetoothReplyRunnable* aRunnable);
+
+ void Disconnect(const nsAString& aAppUuid,
+ const nsAString& aDeviceAddr,
+ BluetoothReplyRunnable* aRunnable);
+
+ void UnregisterClient(int aClientIf,
+ BluetoothReplyRunnable* aRunnable);
+
private:
class CleanupResultHandler;
class CleanupResultHandlerRunnable;
class InitGattResultHandler;
+ class RegisterClientResultHandler;
+ class UnregisterClientResultHandler;
+ class ConnectResultHandler;
+ class DisconnectResultHandler;
BluetoothGattManager();
void HandleShutdown();
void RegisterClientNotification(int aStatus,
int aClientIf,
const BluetoothUuid& aAppUuid) MOZ_OVERRIDE;
--- a/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp
@@ -50,16 +50,25 @@
do { \
if (!sBtInterface || !IsEnabled()) { \
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth is not ready"); \
DispatchBluetoothReply(runnable, BluetoothValue(), errorStr); \
return; \
} \
} while(0)
+#define ENSURE_GATT_MGR_IS_READY_VOID(gatt, runnable) \
+ do { \
+ if (!gatt) { \
+ NS_NAMED_LITERAL_STRING(replyError, "GattManager is not ready"); \
+ DispatchBluetoothReply(runnable, BluetoothValue(), replyError); \
+ return; \
+ } \
+ } while(0)
+
using namespace mozilla;
using namespace mozilla::ipc;
USING_BLUETOOTH_NAMESPACE
static nsString sAdapterBdAddress;
static nsString sAdapterBdName;
static bool sAdapterDiscoverable(false);
static bool sAdapterDiscovering(false);
@@ -1103,16 +1112,64 @@ BluetoothServiceBluedroid::IgnoreWaiting
}
void
BluetoothServiceBluedroid::ToggleCalls(BluetoothReplyRunnable* aRunnable)
{
}
//
+// GATT Client
+//
+
+void
+BluetoothServiceBluedroid::ConnectGattClientInternal(
+ const nsAString& aAppUuid, const nsAString& aDeviceAddress,
+ BluetoothReplyRunnable* aRunnable)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+ BluetoothGattManager* gatt = BluetoothGattManager::Get();
+ ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+ gatt->Connect(aAppUuid, aDeviceAddress, aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::DisconnectGattClientInternal(
+ const nsAString& aAppUuid, const nsAString& aDeviceAddress,
+ BluetoothReplyRunnable* aRunnable)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+ BluetoothGattManager* gatt = BluetoothGattManager::Get();
+ ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+ gatt->Disconnect(aAppUuid, aDeviceAddress, aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::UnregisterGattClientInternal(
+ int aClientIf, BluetoothReplyRunnable* aRunnable)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+ BluetoothGattManager* gatt = BluetoothGattManager::Get();
+ ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+ gatt->UnregisterClient(aClientIf, aRunnable);
+}
+
+//
// Bluetooth notifications
//
/* |ProfileDeinitResultHandler| collects the results of all profile
* result handlers and calls |Proceed| after all results handlers
* have been run.
*/
class BluetoothServiceBluedroid::ProfileDeinitResultHandler MOZ_FINAL
--- a/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.h
+++ b/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.h
@@ -166,16 +166,34 @@ public:
SendSinkMessage(const nsAString& aDeviceAddresses,
const nsAString& aMessage) MOZ_OVERRIDE;
virtual nsresult
SendInputMessage(const nsAString& aDeviceAddresses,
const nsAString& aMessage) MOZ_OVERRIDE;
//
+ // GATT Client
+ //
+
+ virtual void
+ ConnectGattClientInternal(const nsAString& aAppUuid,
+ const nsAString& aDeviceAddress,
+ BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+ virtual void
+ DisconnectGattClientInternal(const nsAString& aAppUuid,
+ const nsAString& aDeviceAddress,
+ BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+ virtual void
+ UnregisterGattClientInternal(int aClientIf,
+ BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+ //
// Bluetooth notifications
//
virtual void AdapterStateChangedNotification(bool aState) MOZ_OVERRIDE;
virtual void AdapterPropertiesNotification(
BluetoothStatus aStatus, int aNumProperties,
const BluetoothProperty* aProperties) MOZ_OVERRIDE;
--- a/dom/bluetooth2/bluez/BluetoothDBusService.cpp
+++ b/dom/bluetooth2/bluez/BluetoothDBusService.cpp
@@ -4261,8 +4261,28 @@ BluetoothDBusService::UpdateNotification
MOZ_ASSERT(!sAdapterPath.IsEmpty());
nsAutoString deviceAddress;
a2dp->GetAddress(deviceAddress);
Task* task = new UpdateNotificationTask(deviceAddress, aEventId, aData);
DispatchToDBusThread(task);
}
+
+void
+BluetoothDBusService::ConnectGattClientInternal(
+ const nsAString& aAppUuid, const nsAString& aDeviceAddress,
+ BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::DisconnectGattClientInternal(
+ const nsAString& aAppUuid, const nsAString& aDeviceAddress,
+ BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::UnregisterGattClientInternal(
+ int aClientIf, BluetoothReplyRunnable* aRunnable)
+{
+}
--- a/dom/bluetooth2/bluez/BluetoothDBusService.h
+++ b/dom/bluetooth2/bluez/BluetoothDBusService.h
@@ -179,16 +179,30 @@ public:
virtual nsresult
SendSinkMessage(const nsAString& aDeviceAddresses,
const nsAString& aMessage) MOZ_OVERRIDE;
virtual nsresult
SendInputMessage(const nsAString& aDeviceAddresses,
const nsAString& aMessage) MOZ_OVERRIDE;
+
+ virtual void
+ ConnectGattClientInternal(const nsAString& aAppUuid,
+ const nsAString& aDeviceAddress,
+ BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+ virtual void
+ DisconnectGattClientInternal(const nsAString& aAppUuid,
+ const nsAString& aDeviceAddress,
+ BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+ virtual void
+ UnregisterGattClientInternal(int aClientIf,
+ BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
private:
nsresult SendGetPropertyMessage(const nsAString& aPath,
const char* aInterface,
void (*aCB)(DBusMessage *, void *),
BluetoothReplyRunnable* aRunnable);
nsresult SendDiscoveryMessage(const char* aMessageName,
BluetoothReplyRunnable* aRunnable);
--- a/dom/bluetooth2/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth2/ipc/BluetoothParent.cpp
@@ -245,16 +245,22 @@ BluetoothParent::RecvPBluetoothRequestCo
return actor->DoRequest(aRequest.get_IgnoreWaitingCallRequest());
case Request::TToggleCallsRequest:
return actor->DoRequest(aRequest.get_ToggleCallsRequest());
#endif
case Request::TSendMetaDataRequest:
return actor->DoRequest(aRequest.get_SendMetaDataRequest());
case Request::TSendPlayStatusRequest:
return actor->DoRequest(aRequest.get_SendPlayStatusRequest());
+ case Request::TConnectGattClientRequest:
+ return actor->DoRequest(aRequest.get_ConnectGattClientRequest());
+ case Request::TDisconnectGattClientRequest:
+ return actor->DoRequest(aRequest.get_DisconnectGattClientRequest());
+ case Request::TUnregisterGattClientRequest:
+ return actor->DoRequest(aRequest.get_UnregisterGattClientRequest());
default:
MOZ_CRASH("Unknown type!");
}
MOZ_CRASH("Should never get here!");
}
PBluetoothRequestParent*
@@ -679,8 +685,46 @@ BluetoothRequestParent::DoRequest(const
MOZ_ASSERT(mRequestType == Request::TSendPlayStatusRequest);
mService->SendPlayStatus(aRequest.duration(),
aRequest.position(),
aRequest.playStatus(),
mReplyRunnable.get());
return true;
}
+
+bool
+BluetoothRequestParent::DoRequest(const ConnectGattClientRequest& aRequest)
+{
+ MOZ_ASSERT(mService);
+ MOZ_ASSERT(mRequestType == Request::TConnectGattClientRequest);
+
+ mService->ConnectGattClientInternal(aRequest.appUuid(),
+ aRequest.deviceAddress(),
+ mReplyRunnable.get());
+
+ return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(const DisconnectGattClientRequest& aRequest)
+{
+ MOZ_ASSERT(mService);
+ MOZ_ASSERT(mRequestType == Request::TDisconnectGattClientRequest);
+
+ mService->DisconnectGattClientInternal(aRequest.appUuid(),
+ aRequest.deviceAddress(),
+ mReplyRunnable.get());
+
+ return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(const UnregisterGattClientRequest& aRequest)
+{
+ MOZ_ASSERT(mService);
+ MOZ_ASSERT(mRequestType == Request::TUnregisterGattClientRequest);
+
+ mService->UnregisterGattClientInternal(aRequest.clientIf(),
+ mReplyRunnable.get());
+
+ return true;
+}
--- a/dom/bluetooth2/ipc/BluetoothParent.h
+++ b/dom/bluetooth2/ipc/BluetoothParent.h
@@ -211,13 +211,22 @@ protected:
DoRequest(const ToggleCallsRequest& aRequest);
#endif
bool
DoRequest(const SendMetaDataRequest& aRequest);
bool
DoRequest(const SendPlayStatusRequest& aRequest);
+
+ bool
+ DoRequest(const ConnectGattClientRequest& aRequest);
+
+ bool
+ DoRequest(const DisconnectGattClientRequest& aRequest);
+
+ bool
+ DoRequest(const UnregisterGattClientRequest& aRequest);
};
END_BLUETOOTH_NAMESPACE
#endif // mozilla_dom_bluetooth_ipc_bluetoothparent_h__
--- a/dom/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
@@ -136,17 +136,17 @@ BluetoothServiceChildProcess::GetPairedD
request.addresses().AppendElements(aDeviceAddresses);
SendRequest(aRunnable, request);
return NS_OK;
}
nsresult
BluetoothServiceChildProcess::FetchUuidsInternal(
- const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
+ const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
{
SendRequest(aRunnable, FetchUuidsRequest(nsString(aDeviceAddress)));
return NS_OK;
}
nsresult
BluetoothServiceChildProcess::StopDiscoveryInternal(
BluetoothReplyRunnable* aRunnable)
@@ -374,16 +374,41 @@ BluetoothServiceChildProcess::SendPlaySt
const nsAString& aPlayStatus,
BluetoothReplyRunnable* aRunnable)
{
SendRequest(aRunnable,
SendPlayStatusRequest(aDuration, aPosition,
nsString(aPlayStatus)));
}
+void
+BluetoothServiceChildProcess::ConnectGattClientInternal(
+ const nsAString& aAppUuid, const nsAString& aDeviceAddress,
+ BluetoothReplyRunnable* aRunnable)
+{
+ SendRequest(aRunnable, ConnectGattClientRequest(nsString(aAppUuid),
+ nsString(aDeviceAddress)));
+}
+
+void
+BluetoothServiceChildProcess::DisconnectGattClientInternal(
+ const nsAString& aAppUuid, const nsAString& aDeviceAddress,
+ BluetoothReplyRunnable* aRunnable)
+{
+ SendRequest(aRunnable,
+ DisconnectGattClientRequest(nsString(aAppUuid), nsString(aDeviceAddress)));
+}
+
+void
+BluetoothServiceChildProcess::UnregisterGattClientInternal(
+ int aClientIf, BluetoothReplyRunnable* aRunnable)
+{
+ SendRequest(aRunnable, UnregisterGattClientRequest(aClientIf));
+}
+
nsresult
BluetoothServiceChildProcess::HandleStartup()
{
// Don't need to do anything here for startup since our Create function takes
// care of the actor machinery.
return NS_OK;
}
--- a/dom/bluetooth2/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth2/ipc/BluetoothServiceChildProcess.h
@@ -187,16 +187,30 @@ public:
virtual nsresult
SendSinkMessage(const nsAString& aDeviceAddresses,
const nsAString& aMessage) MOZ_OVERRIDE;
virtual nsresult
SendInputMessage(const nsAString& aDeviceAddresses,
const nsAString& aMessage) MOZ_OVERRIDE;
+ virtual void
+ ConnectGattClientInternal(const nsAString& aAppUuid,
+ const nsAString& aDeviceAddress,
+ BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+ virtual void
+ DisconnectGattClientInternal(const nsAString& aAppUuid,
+ const nsAString& aDeviceAddress,
+ BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+ virtual void
+ UnregisterGattClientInternal(int aClientIf,
+ BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
protected:
BluetoothServiceChildProcess();
virtual ~BluetoothServiceChildProcess();
void
NoteDeadActor();
void
--- a/dom/bluetooth2/ipc/PBluetooth.ipdl
+++ b/dom/bluetooth2/ipc/PBluetooth.ipdl
@@ -171,16 +171,33 @@ struct SendMetaDataRequest
struct SendPlayStatusRequest
{
int64_t duration;
int64_t position;
nsString playStatus;
};
+struct ConnectGattClientRequest
+{
+ nsString appUuid;
+ nsString deviceAddress;
+};
+
+struct DisconnectGattClientRequest
+{
+ nsString appUuid;
+ nsString deviceAddress;
+};
+
+struct UnregisterGattClientRequest
+{
+ int clientIf;
+};
+
union Request
{
GetAdaptersRequest;
StartBluetoothRequest;
StopBluetoothRequest;
SetPropertyRequest;
GetPropertyRequest;
StartDiscoveryRequest;
@@ -203,16 +220,19 @@ union Request
ConnectScoRequest;
DisconnectScoRequest;
IsScoConnectedRequest;
AnswerWaitingCallRequest;
IgnoreWaitingCallRequest;
ToggleCallsRequest;
SendMetaDataRequest;
SendPlayStatusRequest;
+ ConnectGattClientRequest;
+ DisconnectGattClientRequest;
+ UnregisterGattClientRequest;
};
protocol PBluetooth
{
manager PContent;
manages PBluetoothRequest;
/**
--- a/dom/bluetooth2/moz.build
+++ b/dom/bluetooth2/moz.build
@@ -5,16 +5,17 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
if CONFIG['MOZ_B2G_BT']:
SOURCES += [
'BluetoothAdapter.cpp',
'BluetoothClassOfDevice.cpp',
'BluetoothDevice.cpp',
'BluetoothDiscoveryHandle.cpp',
+ 'BluetoothGatt.cpp',
'BluetoothHidManager.cpp',
'BluetoothInterface.cpp',
'BluetoothManager.cpp',
'BluetoothPairingHandle.cpp',
'BluetoothPairingListener.cpp',
'BluetoothProfileController.cpp',
'BluetoothReplyRunnable.cpp',
'BluetoothService.cpp',
@@ -117,16 +118,17 @@ EXPORTS.mozilla.dom.bluetooth.ipc += [
]
EXPORTS.mozilla.dom.bluetooth += [
'BluetoothAdapter.h',
'BluetoothClassOfDevice.h',
'BluetoothCommon.h',
'BluetoothDevice.h',
'BluetoothDiscoveryHandle.h',
+ 'BluetoothGatt.h',
'BluetoothManager.h',
'BluetoothPairingHandle.h',
'BluetoothPairingListener.h',
]
IPDL_SOURCES += [
'ipc/BluetoothTypes.ipdlh',
'ipc/PBluetooth.ipdl',
--- a/dom/camera/GonkRecorderProfiles.def
+++ b/dom/camera/GonkRecorderProfiles.def
@@ -1,10 +1,10 @@
/*
- * Copyright (C) 2012 Mozilla Foundation
+ * Copyright (C) 2012-2015 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
@@ -35,19 +35,20 @@ DEF_GONK_RECORDER_PROFILE(CAMCORDER_QUAL
DEF_GONK_RECORDER_PROFILE(CAMCORDER_QUALITY_1080P, "1080p")
/**
* The following profiles do not appear in all versions of the
* MediaProfiles.h and must be detected at runtime. Additionally some
* profiles may have more than one resolution, depending on the camera.
*/
-DEF_GONK_RECORDER_PROFILE_DETECT("fwvga", 864, 480)
-DEF_GONK_RECORDER_PROFILE_DETECT("fwvga", 854, 480)
-DEF_GONK_RECORDER_PROFILE_DETECT("wvga", 800, 480)
-DEF_GONK_RECORDER_PROFILE_DETECT("wvga", 768, 480)
-DEF_GONK_RECORDER_PROFILE_DETECT("vga", 640, 480)
-DEF_GONK_RECORDER_PROFILE_DETECT("hvga", 480, 320)
-DEF_GONK_RECORDER_PROFILE_DETECT("wqvga", 400, 240)
-DEF_GONK_RECORDER_PROFILE_DETECT("qvga", 320, 240)
+DEF_GONK_RECORDER_PROFILE_DETECT("4kuhd", 3840, 2160)
+DEF_GONK_RECORDER_PROFILE_DETECT("fwvga", 864, 480)
+DEF_GONK_RECORDER_PROFILE_DETECT("fwvga", 854, 480)
+DEF_GONK_RECORDER_PROFILE_DETECT("wvga", 800, 480)
+DEF_GONK_RECORDER_PROFILE_DETECT("wvga", 768, 480)
+DEF_GONK_RECORDER_PROFILE_DETECT("vga", 640, 480)
+DEF_GONK_RECORDER_PROFILE_DETECT("hvga", 480, 320)
+DEF_GONK_RECORDER_PROFILE_DETECT("wqvga", 400, 240)
+DEF_GONK_RECORDER_PROFILE_DETECT("qvga", 320, 240)
#undef DEF_GONK_RECORDER_PROFILE
#undef DEF_GONK_RECORDER_PROFILE_DETECT
--- a/dom/media/omx/OmxDecoder.cpp
+++ b/dom/media/omx/OmxDecoder.cpp
@@ -595,17 +595,17 @@ bool OmxDecoder::ReadVideo(VideoFrame *a
continue;
} else if (err != OK) {
OD_LOG("Unexpected error when seeking to %lld", aTimeUs);
break;
}
// For some codecs, the length of first decoded frame after seek is 0.
// Need to ignore it and continue to find the next one
if (mVideoBuffer->range_length() == 0) {
- ReleaseVideoBuffer();
+ PostReleaseVideoBuffer(mVideoBuffer, FenceHandle());
findNextBuffer = true;
}
}
aDoSeek = false;
} else {
err = mVideoSource->read(&mVideoBuffer);
}
--- a/dom/nfc/gonk/Nfc.js
+++ b/dom/nfc/gonk/Nfc.js
@@ -173,18 +173,20 @@ XPCOMUtils.defineLazyGetter(this, "gMess
dump("invalid target");
return;
}
target.sendAsyncMessage("NFC:DOMEvent", options);
},
setFocusApp: function setFocusApp(id, isFocus) {
- // if calling setNFCFocus(true) on the same browser-element, ignore.
- if (isFocus && (id == this.focusApp)) {
+ // if calling setNFCFocus(true) on the browser-element which is already
+ // focused, or calling setNFCFocus(false) on the browser-element which has
+ // lost focus already, ignore.
+ if (isFocus == (id == this.focusApp)) {
return;
}
if (this.focusApp != NFC.SYSTEM_APP_ID) {
this.onFocusChanged(this.focusApp, false);
}
if (isFocus) {
--- a/dom/system/gonk/android_audio/AudioSystem.h
+++ b/dom/system/gonk/android_audio/AudioSystem.h
@@ -309,17 +309,17 @@ typedef enum {
AUDIO_DEVICE_IN_VOICE_CALL |
AUDIO_DEVICE_IN_BACK_MIC |
AUDIO_DEVICE_IN_ANC_HEADSET |
AUDIO_DEVICE_IN_FM_RX |
AUDIO_DEVICE_IN_FM_RX_A2DP |
AUDIO_DEVICE_IN_DEFAULT),
AUDIO_DEVICE_IN_ALL_SCO = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
} audio_devices_t;
-#else
+#elif ANDROID_VERSION < 21
enum {
AUDIO_DEVICE_NONE = 0x0,
/* reserved bits */
AUDIO_DEVICE_BIT_IN = 0x80000000,
AUDIO_DEVICE_BIT_DEFAULT = 0x40000000,
/* output devices */
AUDIO_DEVICE_OUT_EARPIECE = 0x1,
AUDIO_DEVICE_OUT_SPEAKER = 0x2,
@@ -411,16 +411,143 @@ enum {
AUDIO_DEVICE_IN_FM_RX |
AUDIO_DEVICE_IN_FM_RX_A2DP |
AUDIO_DEVICE_IN_PROXY |
AUDIO_DEVICE_IN_DEFAULT),
AUDIO_DEVICE_IN_ALL_SCO = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
};
typedef uint32_t audio_devices_t;
+#else
+enum {
+ AUDIO_DEVICE_NONE = 0x0,
+ /* reserved bits */
+ AUDIO_DEVICE_BIT_IN = 0x80000000,
+ AUDIO_DEVICE_BIT_DEFAULT = 0x40000000,
+ /* output devices */
+ AUDIO_DEVICE_OUT_EARPIECE = 0x1,
+ AUDIO_DEVICE_OUT_SPEAKER = 0x2,
+ AUDIO_DEVICE_OUT_WIRED_HEADSET = 0x4,
+ AUDIO_DEVICE_OUT_WIRED_HEADPHONE = 0x8,
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO = 0x10,
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET = 0x20,
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT = 0x40,
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP = 0x80,
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100,
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200,
+ AUDIO_DEVICE_OUT_AUX_DIGITAL = 0x400,
+ AUDIO_DEVICE_OUT_HDMI = AUDIO_DEVICE_OUT_AUX_DIGITAL,
+ /* uses an analog connection (multiplexed over the USB connector pins for instance) */
+ AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET = 0x800,
+ AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000,
+ /* USB accessory mode: your Android device is a USB device and the dock is a USB host */
+ AUDIO_DEVICE_OUT_USB_ACCESSORY = 0x2000,
+ /* USB host mode: your Android device is a USB host and the dock is a USB device */
+ AUDIO_DEVICE_OUT_USB_DEVICE = 0x4000,
+ AUDIO_DEVICE_OUT_REMOTE_SUBMIX = 0x8000,
+ /* Telephony voice TX path */
+ AUDIO_DEVICE_OUT_TELEPHONY_TX = 0x10000,
+ /* Analog jack with line impedance detected */
+ AUDIO_DEVICE_OUT_LINE = 0x20000,
+ /* HDMI Audio Return Channel */
+ AUDIO_DEVICE_OUT_HDMI_ARC = 0x40000,
+ /* S/PDIF out */
+ AUDIO_DEVICE_OUT_SPDIF = 0x80000,
+ /* FM transmitter out */
+ AUDIO_DEVICE_OUT_FM = 0x100000,
+ /* Line out for av devices */
+ AUDIO_DEVICE_OUT_AUX_LINE = 0x200000,
+ /* limited-output speaker device for acoustic safety */
+ AUDIO_DEVICE_OUT_SPEAKER_SAFE = 0x400000,
+ AUDIO_DEVICE_OUT_DEFAULT = AUDIO_DEVICE_BIT_DEFAULT,
+ AUDIO_DEVICE_OUT_ALL = (AUDIO_DEVICE_OUT_EARPIECE |
+ AUDIO_DEVICE_OUT_SPEAKER |
+ AUDIO_DEVICE_OUT_WIRED_HEADSET |
+ AUDIO_DEVICE_OUT_WIRED_HEADPHONE |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
+ AUDIO_DEVICE_OUT_AUX_DIGITAL |
+ AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET |
+ AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET |
+ AUDIO_DEVICE_OUT_USB_ACCESSORY |
+ AUDIO_DEVICE_OUT_USB_DEVICE |
+ AUDIO_DEVICE_OUT_REMOTE_SUBMIX |
+ AUDIO_DEVICE_OUT_TELEPHONY_TX |
+ AUDIO_DEVICE_OUT_LINE |
+ AUDIO_DEVICE_OUT_HDMI_ARC |
+ AUDIO_DEVICE_OUT_SPDIF |
+ AUDIO_DEVICE_OUT_FM |
+ AUDIO_DEVICE_OUT_AUX_LINE |
+ AUDIO_DEVICE_OUT_SPEAKER_SAFE |
+ AUDIO_DEVICE_OUT_DEFAULT),
+ AUDIO_DEVICE_OUT_ALL_A2DP = (AUDIO_DEVICE_OUT_BLUETOOTH_A2DP |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER),
+ AUDIO_DEVICE_OUT_ALL_SCO = (AUDIO_DEVICE_OUT_BLUETOOTH_SCO |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT),
+ AUDIO_DEVICE_OUT_ALL_USB = (AUDIO_DEVICE_OUT_USB_ACCESSORY |
+ AUDIO_DEVICE_OUT_USB_DEVICE),
+ /* input devices */
+ AUDIO_DEVICE_IN_COMMUNICATION = AUDIO_DEVICE_BIT_IN | 0x1,
+ AUDIO_DEVICE_IN_AMBIENT = AUDIO_DEVICE_BIT_IN | 0x2,
+ AUDIO_DEVICE_IN_BUILTIN_MIC = AUDIO_DEVICE_BIT_IN | 0x4,
+ AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET = AUDIO_DEVICE_BIT_IN | 0x8,
+ AUDIO_DEVICE_IN_WIRED_HEADSET = AUDIO_DEVICE_BIT_IN | 0x10,
+ AUDIO_DEVICE_IN_AUX_DIGITAL = AUDIO_DEVICE_BIT_IN | 0x20,
+ AUDIO_DEVICE_IN_HDMI = AUDIO_DEVICE_IN_AUX_DIGITAL,
+ /* Telephony voice RX path */
+ AUDIO_DEVICE_IN_VOICE_CALL = AUDIO_DEVICE_BIT_IN | 0x40,
+ AUDIO_DEVICE_IN_BACK_MIC = AUDIO_DEVICE_BIT_IN | 0x80,
+ AUDIO_DEVICE_IN_REMOTE_SUBMIX = AUDIO_DEVICE_BIT_IN | 0x100,
+ AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET = AUDIO_DEVICE_BIT_IN | 0x200,
+ AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET = AUDIO_DEVICE_BIT_IN | 0x400,
+ AUDIO_DEVICE_IN_USB_ACCESSORY = AUDIO_DEVICE_BIT_IN | 0x800,
+ AUDIO_DEVICE_IN_USB_DEVICE = AUDIO_DEVICE_BIT_IN | 0x1000,
+ /* FM tuner input */
+ AUDIO_DEVICE_IN_FM_TUNER = AUDIO_DEVICE_BIT_IN | 0x2000,
+ /* TV tuner input */
+ AUDIO_DEVICE_IN_TV_TUNER = AUDIO_DEVICE_BIT_IN | 0x4000,
+ /* Analog jack with line impedance detected */
+ AUDIO_DEVICE_IN_LINE = AUDIO_DEVICE_BIT_IN | 0x8000,
+ /* S/PDIF in */
+ AUDIO_DEVICE_IN_SPDIF = AUDIO_DEVICE_BIT_IN | 0x10000,
+ AUDIO_DEVICE_IN_BLUETOOTH_A2DP = AUDIO_DEVICE_BIT_IN | 0x20000,
+ AUDIO_DEVICE_IN_LOOPBACK = AUDIO_DEVICE_BIT_IN | 0x40000,
+ AUDIO_DEVICE_IN_DEFAULT = AUDIO_DEVICE_BIT_IN | AUDIO_DEVICE_BIT_DEFAULT,
+ AUDIO_DEVICE_IN_ALL = (AUDIO_DEVICE_IN_COMMUNICATION |
+ AUDIO_DEVICE_IN_AMBIENT |
+ AUDIO_DEVICE_IN_BUILTIN_MIC |
+ AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET |
+ AUDIO_DEVICE_IN_WIRED_HEADSET |
+ AUDIO_DEVICE_IN_AUX_DIGITAL |
+ AUDIO_DEVICE_IN_VOICE_CALL |
+ AUDIO_DEVICE_IN_BACK_MIC |
+ AUDIO_DEVICE_IN_REMOTE_SUBMIX |
+ AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET |
+ AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET |
+ AUDIO_DEVICE_IN_USB_ACCESSORY |
+ AUDIO_DEVICE_IN_USB_DEVICE |
+ AUDIO_DEVICE_IN_FM_TUNER |
+ AUDIO_DEVICE_IN_TV_TUNER |
+ AUDIO_DEVICE_IN_LINE |
+ AUDIO_DEVICE_IN_SPDIF |
+ AUDIO_DEVICE_IN_BLUETOOTH_A2DP |
+ AUDIO_DEVICE_IN_LOOPBACK |
+ AUDIO_DEVICE_IN_DEFAULT),
+ AUDIO_DEVICE_IN_ALL_SCO = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
+ AUDIO_DEVICE_IN_ALL_USB = (AUDIO_DEVICE_IN_USB_ACCESSORY |
+ AUDIO_DEVICE_IN_USB_DEVICE),
+};
+
+typedef uint32_t audio_devices_t;
#endif
/* device connection states used for audio_policy->set_device_connection_state()
* */
typedef enum {
AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
AUDIO_POLICY_DEVICE_STATE_AVAILABLE,
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -460,16 +460,18 @@ this.NETWORK_CREG_TECH_EVDOA = 8;
this.NETWORK_CREG_TECH_HSDPA = 9;
this.NETWORK_CREG_TECH_HSUPA = 10;
this.NETWORK_CREG_TECH_HSPA = 11;
this.NETWORK_CREG_TECH_EVDOB = 12;
this.NETWORK_CREG_TECH_EHRPD = 13;
this.NETWORK_CREG_TECH_LTE = 14;
this.NETWORK_CREG_TECH_HSPAP = 15;
this.NETWORK_CREG_TECH_GSM = 16;
+this.NETWORK_CREG_TECH_DCHSPAP_1 = 18; // Some devices reports as 18
+this.NETWORK_CREG_TECH_DCHSPAP_2 = 19; // Some others report it as 19
this.CELL_INFO_TYPE_GSM = 1;
this.CELL_INFO_TYPE_CDMA = 2;
this.CELL_INFO_TYPE_LTE = 3;
this.CELL_INFO_TYPE_WCDMA = 4;
// Order matters.
this.AUDIO_STATE_NO_CALL = 0;
@@ -2908,17 +2910,20 @@ this.GECKO_RADIO_TECH = [
"evdoa",
"hsdpa",
"hsupa",
"hspa",
"evdob",
"ehrpd",
"lte",
"hspa+",
- "gsm"
+ "gsm",
+ null,
+ "hspa+", // DC-HSPA+
+ "hspa+"
];
this.GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN = -1;
// Call forwarding action. Must be in sync with nsIMobileConnectionService interface
this.CALL_FORWARD_ACTION_DISABLE = 0;
this.CALL_FORWARD_ACTION_ENABLE = 1;
this.CALL_FORWARD_ACTION_QUERY_STATUS = 2;
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -4221,16 +4221,18 @@ RilObject.prototype = {
case NETWORK_CREG_TECH_EDGE:
case NETWORK_CREG_TECH_UMTS:
case NETWORK_CREG_TECH_HSDPA:
case NETWORK_CREG_TECH_HSUPA:
case NETWORK_CREG_TECH_HSPA:
case NETWORK_CREG_TECH_LTE:
case NETWORK_CREG_TECH_HSPAP:
case NETWORK_CREG_TECH_GSM:
+ case NETWORK_CREG_TECH_DCHSPAP_1:
+ case NETWORK_CREG_TECH_DCHSPAP_2:
return true;
}
return false;
},
/**
* Process radio technology change.
@@ -6189,16 +6191,18 @@ RilObject.prototype[REQUEST_GET_NEIGHBOR
cellId.gsmCellId = this.parseInt(cid.substring(4), -1, 16);
cellId.gsmLocationAreaCode = this.parseInt(cid.substring(0, 4), -1, 16);
break;
case NETWORK_CREG_TECH_UMTS:
case NETWORK_CREG_TECH_HSDPA:
case NETWORK_CREG_TECH_HSUPA:
case NETWORK_CREG_TECH_HSPA:
case NETWORK_CREG_TECH_HSPAP:
+ case NETWORK_CREG_TECH_DCHSPAP_1:
+ case NETWORK_CREG_TECH_DCHSPAP_2:
cellId.wcdmaPsc = this.parseInt(cid, -1, 16);
break;
}
neighboringCellIds.push(cellId);
}
options.result = neighboringCellIds;
--- a/dom/webidl/BluetoothDevice2.webidl
+++ b/dom/webidl/BluetoothDevice2.webidl
@@ -8,16 +8,22 @@
interface BluetoothDevice : EventTarget
{
readonly attribute DOMString address;
readonly attribute BluetoothClassOfDevice cod;
readonly attribute DOMString name;
readonly attribute boolean paired;
readonly attribute BluetoothDeviceType type;
+ /**
+ * Retrieve the BluetoothGatt interface to interact with remote BLE devices.
+ * This attribute is null if the device type is not dual or le.
+ */
+ readonly attribute BluetoothGatt? gatt;
+
[Cached, Pure]
readonly attribute sequence<DOMString> uuids;
// Fired when attribute(s) of BluetoothDevice changed
attribute EventHandler onattributechanged;
/**
* Fetch the up-to-date UUID list of each bluetooth service that the device
new file mode 100644
--- /dev/null
+++ b/dom/webidl/BluetoothGatt.webidl
@@ -0,0 +1,38 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+[CheckPermissions="bluetooth"]
+interface BluetoothGatt : EventTarget
+{
+ readonly attribute BluetoothConnectionState connectionState;
+
+ // Fired when attribute connectionState changed
+ attribute EventHandler onconnectionstatechanged;
+
+ /**
+ * Connect/Disconnect to the remote BLE device if the connectionState is
+ * disconnected/connected. Otherwise, the Promise will be rejected directly.
+ *
+ * If current connectionState is disconnected/connected,
+ * 1) connectionState change to connecting/disconnecting along with a
+ * connectionstatechanged event.
+ * 2) connectionState change to connected/disconnected if the operation
+ * succeeds. Otherwise, change to disconnected/connected.
+ * 3) Promise is resolved or rejected according to the operation result.
+ */
+ [NewObject]
+ Promise<void> connect();
+ [NewObject]
+ Promise<void> disconnect();
+};
+
+enum BluetoothConnectionState
+{
+ "disconnected",
+ "disconnecting",
+ "connected",
+ "connecting"
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -627,16 +627,17 @@ if CONFIG['MOZ_DEBUG']:
if CONFIG['MOZ_B2G_BT']:
if CONFIG['MOZ_B2G_BT_API_V2']:
WEBIDL_FILES += [
'BluetoothAdapter2.webidl',
'BluetoothClassOfDevice.webidl',
'BluetoothDevice2.webidl',
'BluetoothDiscoveryHandle.webidl',
+ 'BluetoothGatt.webidl',
'BluetoothManager2.webidl',
'BluetoothPairingHandle.webidl',
'BluetoothPairingListener.webidl',
]
else:
WEBIDL_FILES += [
'BluetoothAdapter.webidl',
'BluetoothDevice.webidl',
--- a/dom/xbl/test/mochitest.ini
+++ b/dom/xbl/test/mochitest.ini
@@ -32,14 +32,15 @@ skip-if = (buildapp == 'b2g' && toolkit
[test_bug468210.xhtml]
[test_bug481558.html]
[test_bug526178.xhtml]
[test_bug542406.xhtml]
[test_bug591198.html]
[test_bug639338.xhtml]
[test_bug790265.xhtml]
[test_bug821850.html]
+skip-if = buildapp == 'mulet'
[test_bug844783.html]
[test_bug872273.xhtml]
[test_bug946815.html]
skip-if = toolkit != "gonk"
[test_bug1086996.xhtml]
[test_bug1098628_throw_from_construct.xhtml]
index b7159c24b8a6b2b91202f7a3e52aa44d65a14908..441067672e0418d6b0ae25ea2febd866e81167af
GIT binary patch
literal 2130
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
OEALG8pwv4Cru+fp41Od4
index f350cd393d94a043c5756c40e4b73b93d8ca92fd..cf033ace8a7f4895cf8e54a90f17ad199fe17912
GIT binary patch
literal 2760
zc$|G!dpMM78y`$e&Sygo?+i+;Ic1EQHD$(_43QX7N?r_RnZu63G*&qlyN;>QIF&?c
zZR=PLF@$PYkuAGOp>&|6O^DsTv77zAYp?zGd9U|<p8LLjzx)0@_kI8IW_Wq-R8uxm
zhCm={3|E@BY^TcJoiIh&x4tMZK(-kQUHpYUyf9%LC}2S-p}b%gz~F%4EN>PVdicv*
zEJp}L!Gg{77y5hbB8BicC~ysf5^?yDwdJ@�NtnSwbL~70%|8k#nsbNPrzmM*87B
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
F{2x<^d_MpH
index a3b4725cea1ec4e3a891ded2a5d7bd70d70b7f50..153b7339657e245b35c339c9c7b5f063a3f9c4a3
GIT binary patch
literal 3593
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
F{|9PQDU<*J
index 6599b66391d1d381a6d724d7ce846b15e1427cb2..ae8b0f40bfbb365e1eed213ddaeee7cb4459d54d
GIT binary patch
literal 3244
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
JB{l)E{{{4gswe;e
index 1259d00f770c74afdef2f6d4b498d71ea77b33fb..6dd2bd8beeb16e0066f5e32710d29c371bb25b29
GIT binary patch
literal 2016
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
CGC~pn
index 4e529badb4f47efc85bdc77ea04262a82a30ddba..bb26138b552711982d7a50e7bc186f06658420d2
GIT binary patch
literal 2572
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
E1+1nXga7~l
index 43244bfc79cce347e93feb903c18f7993eb2004b..c803546da756433ce00abcfde5ede6fcaa20d8dd
GIT binary patch
literal 3383
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
index f0a0a5dc8e4edd3d44bc8763deee0cb9cb015301..103561b642e5649c46a7a2af99440ae265e10542
GIT binary patch
literal 3060
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
ZfY;6CRZ49~+SmTWZE$#Og{5!Ye*l|EMc)7b
index 076276acf6f83f35f2b8e2fe4e38db6cf157fda7..84cde557460efe99928d07a876d99076bfde282f
GIT binary patch
literal 2034
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
BD^~yj
index 6da5782e86ed44591466a07e745ea2d7c9d24e4c..b7e37b1fa066510ee627bde90e7c5dcb06a5ca8f
GIT binary patch
literal 2594
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
X-adK8ne16L_iCegZlG3rgr@%m{Z>1S
index f8f61d22116c7a5362cf460d5eaa230fd187182f..8ce1f9453906a258aea9df37e0a7d65a988ef745
GIT binary patch
literal 3405
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
index 1d7553d52a084553aae96bc20d1b265cd55ce344..2ead80fa4e5bfd552bb485ce57c9adfb78b1c23b
GIT binary patch
literal 3105
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
Xt~&tGxBF#q{n75`?CDgw8<X;HUHN1D
--- a/embedding/test/mochitest.ini
+++ b/embedding/test/mochitest.ini
@@ -4,11 +4,11 @@ support-files =
bug293834_form.html
[test_bug293834.html]
[test_bug499115.html]
[test_nsFind.html]
[test_private_window_from_content.html]
# Next two tests are disabled in e10s because of bug 989501.
[test_window_open_position_constraint.html]
-skip-if = toolkit == 'android' || e10s
+skip-if = toolkit == 'android' || e10s || buildapp == 'mulet'
[test_window_open_units.html]
-skip-if = toolkit == 'android' || e10s
+skip-if = toolkit == 'android' || e10s || buildapp == 'mulet'
--- a/layout/base/tests/marionette/test_selectioncarets.py
+++ b/layout/base/tests/marionette/test_selectioncarets.py
@@ -1,16 +1,17 @@
# -*- coding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from by import By
from marionette import Actions
from marionette_test import MarionetteTestCase
+from math import ceil, floor
from selection import SelectionManager
from gestures import long_press_without_contextmenu
class SelectionCaretsTest(MarionetteTestCase):
_long_press_time = 1 # 1 second
_input_selector = (By.ID, 'input')
_textarea_selector = (By.ID, 'textarea')
@@ -180,27 +181,34 @@ class SelectionCaretsTest(MarionetteTest
self._long_press_to_select(el, x, y)
target_content = sel.selected_content
# Move the left caret to the position of the right caret to trigger
# carets overlapping.
(caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()
- # Caret width is 29px, so we make a series of hit tests for the two
- # tilted carets. If any of the hits is missed, selection would be
- # collapsed and both two carets should not be draggable.
+ # We make two hit tests targeting the left edge of the left tilted caret
+ # and the right edge of the right tilted caret. If either of the hits is
+ # missed, selection would be collapsed and both carets should not be
+ # draggable.
(caret3_x, caret3_y), (caret4_x, caret4_y) = sel.selection_carets_location()
- right_x = int(caret4_x + 0.5)
- for i in range (right_x, right_x + 29, + 1):
- self.actions.press(el, i, caret4_y).release().perform()
+
+ # The following values are from ua.css.
+ caret_width = 44
+ caret_margin_left = -23
+ tilt_right_margin_left = 18
+ tilt_left_margin_left = -17
- left_x = int(caret3_x - 0.5)
- for i in range (left_x, left_x - 29, - 1):
- self.actions.press(el, i, caret3_y).release().perform()
+ left_caret_left_edge_x = caret3_x + caret_margin_left + tilt_left_margin_left
+ el.tap(ceil(left_caret_left_edge_x), caret3_y)
+
+ right_caret_right_edge_x = (caret4_x + caret_margin_left +
+ tilt_right_margin_left + caret_width)
+ el.tap(floor(right_caret_right_edge_x), caret4_y)
# Drag the left caret back to the initial selection, the first word.
self.actions.flick(el, caret3_x, caret3_y, caret1_x, caret1_y).perform()
assertFunc(target_content, sel.selected_content)
########################################################################
# <input> test cases with selection carets enabled
@@ -375,13 +383,11 @@ class SelectionCaretsTest(MarionetteTest
def test_content_non_editable_handle_tilt_when_carets_overlap_to_each_other(self):
self.openTestHtml(enabled=True)
self._test_handle_tilt_when_carets_overlap_to_each_other(self._content, self.assertEqual)
########################################################################
# <div> contenteditable2 test cases with selection carets enabled
########################################################################
- def test_contenteditable_minimum_select_one_character(self):
+ def test_contenteditable2_minimum_select_one_character(self):
self.openTestHtml(enabled=True)
self._test_minimum_select_one_character(self._contenteditable2, self.assertEqual)
-
-
--- a/layout/style/ua.css
+++ b/layout/style/ua.css
@@ -320,43 +320,43 @@ div:-moz-native-anonymous.moz-selectionc
position: absolute;
}
div:-moz-native-anonymous.moz-touchcaret,
div:-moz-native-anonymous.moz-selectioncaret-left,
div:-moz-native-anonymous.moz-selectioncaret-right,
div:-moz-native-anonymous.moz-selectioncaret-left > div,
div:-moz-native-anonymous.moz-selectioncaret-right > div {
- width: 29px;
- height: 31px;
+ width: 44px;
+ height: 47px;
background-position: center center;
background-size: 100% 100%;
z-index: 2147483647;
}
div:-moz-native-anonymous.moz-touchcaret,
div:-moz-native-anonymous.moz-selectioncaret-left > div,
div:-moz-native-anonymous.moz-selectioncaret-right > div {
background-image: url("resource://gre/res/text_caret.png");
}
div:-moz-native-anonymous.moz-touchcaret,
div:-moz-native-anonymous.moz-selectioncaret-left,
div:-moz-native-anonymous.moz-selectioncaret-right {
- margin-left: -15px;
+ margin-left: -23px;
}
div:-moz-native-anonymous.moz-selectioncaret-left.tilt > div {
background-image: url("resource://gre/res/text_caret_tilt_left.png");
- margin-left: -14px;
+ margin-left: -17px;
}
div:-moz-native-anonymous.moz-selectioncaret-right.tilt > div {
background-image: url("resource://gre/res/text_caret_tilt_right.png");
- margin-left: 14px;
+ margin-left: 18px;
}
@media (min-resolution: 1.5dppx) {
div:-moz-native-anonymous.moz-touchcaret,
div:-moz-native-anonymous.moz-selectioncaret-left > div,
div:-moz-native-anonymous.moz-selectioncaret-right > div {
background-image: url("resource://gre/res/text_caret@1.5x.png");
}
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -275,22 +275,24 @@ pref("browser.search.suggest.prompted",
pref("browser.search.loadFromJars", true);
pref("browser.search.jarURIs", "chrome://browser/locale/searchplugins/");
// tell the search service that we don't really expose the "current engine"
pref("browser.search.noCurrentEngine", true);
// Control media casting & mirroring features
pref("browser.casting.enabled", true);
-pref("browser.mirroring.enabled", true);
#ifdef RELEASE_BUILD
// Roku does not yet support mirroring in production
pref("browser.mirroring.enabled.roku", false);
+// Chromecast mirroring is broken (bug 1131084)
+pref("browser.mirroring.enabled", false);
#else
pref("browser.mirroring.enabled.roku", true);
+pref("browser.mirroring.enabled", true);
#endif
// Enable sparse localization by setting a few package locale overrides
pref("chrome.override_package.global", "browser");
pref("chrome.override_package.mozapps", "browser");
pref("chrome.override_package.passwordmgr", "browser");
// enable xul error pages
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -9,16 +9,17 @@ skip-if = android_version == "10"
# disabled on 2.3; bug 941624, bug 1063509, bug 1073374, bug 1087221, bug 1088023, bug 1088027, bug 1090206
skip-if = android_version == "10" || processor == "x86"
[testAddSearchEngine]
# disabled on Android 2.3; bug 979552
skip-if = android_version == "10"
[testAdobeFlash]
skip-if = processor == "x86"
[testANRReporter]
+[testAppConstants]
[testAwesomebar]
[testAxisLocking]
# disabled on x86 only; bug 927476
skip-if = processor == "x86"
# [testBookmark] # see bug 915350
[testBookmarksPanel]
# disabled on 2.3; bug 979615
skip-if = android_version == "10"
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testAppConstants.java
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.tests;
+
+public class testAppConstants extends JavascriptTest {
+ public testAppConstants() {
+ super("testAppConstants.js");
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testAppConstants.js
@@ -0,0 +1,15 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+
+add_task(function* testAppConstants() {
+ let packageName = AppConstants.ANDROID_PACKAGE_NAME
+ do_check_neq(packageName, "@ANDROID_PACKAGE_NAME@");
+ do_check_true(packageName.length > 0);
+});
+
+
+run_next_test();
--- a/mobile/android/chrome/content/FindHelper.js
+++ b/mobile/android/chrome/content/FindHelper.js
@@ -145,27 +145,14 @@ var FindHelper = {
// this should never happen
Cu.reportError("Warning: selected tab changed during find!");
// fall through and restore viewport on the initial tab anyway
}
this._targetTab.setViewport(JSON.parse(this._initialViewport));
this._targetTab.sendViewportUpdate();
}
} else {
- // Defines the space around the highlighted element as a factor of the element's size.
- const spacingFactor = 6;
-
- // We replace the start of the zoom rect to keep the highlighted word in the middle.
- // We divide this offset by two to consider a spacing on each side of the rect.
- let x = aData.rect.x + (aData.rect.width * (1 - spacingFactor)) / 2;
- let y = aData.rect.y + (aData.rect.height * (1 - spacingFactor)) / 2;
-
- let rect = new Rect(Math.max(x, 0),
- Math.max(y, 0),
- // we use a bigger viewport than just the highlighted word
- aData.rect.width * spacingFactor,
- aData.rect.height * spacingFactor);
-
- ZoomHelper.zoomToRect(rect);
+ // Disabled until bug 1014113 is fixed
+ // ZoomHelper.zoomToRect(aData.rect);
this._viewportChanged = true;
}
}
};
--- a/mobile/android/chrome/content/ZoomHelper.js
+++ b/mobile/android/chrome/content/ZoomHelper.js
@@ -118,21 +118,16 @@ var ZoomHelper = {
}
},
/* Zoom to a specific part of the screen defined by a rect,
* optionally keeping a particular part of it in view
* if it is really tall.
*/
zoomToRect: function(aRect, aClickY = -1) {
- if(aRect.isEmpty()) {
- // Protect from empty or negative-sized rects & potentials NaN in following calculations
- return;
- }
-
let viewport = BrowserApp.selectedTab.getViewport();
let rect = {
x: aRect.x,
y: aRect.y,
w: aRect.width,
h: Math.min(aRect.width * viewport.cssHeight / viewport.cssWidth, aRect.height)
};
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4460,16 +4460,22 @@ pref("dom.mozSettings.SettingsManager.ve
pref("dom.mozSettings.SettingsRequestManager.verbose.enabled", false);
pref("dom.mozSettings.SettingsService.verbose.enabled", false);
// Controlling whether we want to allow forcing some Settings
// IndexedDB transactions to be opened as readonly or keep everything as
// readwrite.
pref("dom.mozSettings.allowForceReadOnly", false);
+// The interval at which to check for slow running addons
+pref("browser.addon-watch.interval", 120000);
+pref("browser.addon-watch.ignore", "[\"mochikit@mozilla.org\",\"special-powers@mozilla.org\"]");
+// the percentage of time addons are allowed to use without being labeled slow
+pref("browser.addon-watch.percentage-limit", 1);
+
// RequestSync API is disabled by default.
pref("dom.requestSync.enabled", false);
// Search service settings
pref("browser.search.log", false);
pref("browser.search.update", true);
pref("browser.search.update.log", false);
pref("browser.search.update.interval", 21600);
--- a/security/manager/boot/src/StaticHPKPins.h
+++ b/security/manager/boot/src/StaticHPKPins.h
@@ -1075,9 +1075,9 @@ static const TransportSecurityPreload kP
{ "youtube.com", true, false, false, -1, &kPinset_google_root_pems },
{ "ytimg.com", true, false, false, -1, &kPinset_google_root_pems },
};
// Pinning Preload List Length = 346;
static const int32_t kUnknownId = -1;
-static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1431774962838000);
+static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1432379583107000);
--- a/security/manager/boot/src/nsSTSPreloadList.errors
+++ b/security/manager/boot/src/nsSTSPreloadList.errors
@@ -62,39 +62,42 @@ codereview.chromium.org: did not receive
console.python.org: did not receive HSTS header
coursella.com: did not receive HSTS header
cr.search.yahoo.com: did not receive HSTS header
crate.io: did not receive HSTS header
crbug.com: did not receive HSTS header
crowdcurity.com: did not receive HSTS header
crowdjuris.com: could not connect to host
crypto.is: did not receive HSTS header
-csawctf.poly.edu: did not receive HSTS header
+csawctf.poly.edu: could not connect to host
ct.search.yahoo.com: did not receive HSTS header
cujanovic.com: did not receive HSTS header
cyanogenmod.xxx: could not connect to host
cybershambles.com: could not connect to host
daylightcompany.com: did not receive HSTS header
de.search.yahoo.com: did not receive HSTS header
decibelios.li: did not receive HSTS header
destinationbijoux.fr: max-age too low: 2678400
+devh.de: did not receive HSTS header
digitaldaddy.net: could not connect to host
discovery.lookout.com: did not receive HSTS header
dk.search.yahoo.com: did not receive HSTS header
dl.google.com: did not receive HSTS header (error ignored - included regardless)
do.search.yahoo.com: did not receive HSTS header
docs.google.com: did not receive HSTS header (error ignored - included regardless)
domaris.de: did not receive HSTS header
download.jitsi.org: did not receive HSTS header
drive.google.com: did not receive HSTS header (error ignored - included regardless)
dropcam.com: did not receive HSTS header
dynaloop.net: did not receive HSTS header
dzlibs.io: could not connect to host
+e-aut.net: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
ed.gs: did not receive HSTS header
edmodo.com: did not receive HSTS header
+egit.co: could not connect to host
email.lookout.com: could not connect to host
en-maktoob.search.yahoo.com: did not receive HSTS header
encrypted.google.com: did not receive HSTS header (error ignored - included regardless)
epoxate.com: did not receive HSTS header
errors.zenpayroll.com: could not connect to host
es.search.yahoo.com: did not receive HSTS header
esec.rs: did not receive HSTS header
espanol.search.yahoo.com: did not receive HSTS header
@@ -125,17 +128,18 @@ gparent.org: did not receive HSTS header
gr.search.yahoo.com: did not receive HSTS header
grandmascookieblog.com: did not receive HSTS header
greplin.com: could not connect to host
groups.google.com: did not receive HSTS header (error ignored - included regardless)
gunnarhafdal.com: could not connect to host
hachre.de: did not receive HSTS header
hackerone-user-content.com: could not connect to host
haste.ch: could not connect to host
-hicoria.com: could not connect to host
+hatoko.net: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
+helpadmin.net: could not connect to host
history.google.com: did not receive HSTS header (error ignored - included regardless)
hk.search.yahoo.com: did not receive HSTS header
hn.search.yahoo.com: did not receive HSTS header
hoerbuecher-und-hoerspiele.de: did not receive HSTS header
honeytracks.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
horosho.in: could not connect to host
hostedtalkgadget.google.com: did not receive HSTS header (error ignored - included regardless)
howrandom.org: could not connect to host
@@ -150,16 +154,17 @@ inertianetworks.com: did not receive HST
intercom.io: did not receive HSTS header
iop.intuit.com: max-age too low: 86400
irccloud.com: did not receive HSTS header
it.search.yahoo.com: did not receive HSTS header
itriskltd.com: did not receive HSTS header
jonas-keidel.de: could not connect to host
jonathan.ir: did not receive HSTS header
jottit.com: could not connect to host
+k-dev.de: could not connect to host
keymaster.lookout.com: did not receive HSTS header
kirkforcongress.com: did not receive HSTS header
kitsta.com: could not connect to host
kiwiirc.com: max-age too low: 5256000
klaxn.com: could not connect to host
klaxn.org: could not connect to host
koop-bremen.de: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
kr.search.yahoo.com: did not receive HSTS header
@@ -177,28 +182,33 @@ lt.search.yahoo.com: did not receive HST
lu.search.yahoo.com: did not receive HSTS header
lumi.do: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
luxus-russen.de: could not connect to host
lv.search.yahoo.com: did not receive HSTS header
m.gparent.org: could not connect to host
mail.google.com: did not receive HSTS header (error ignored - included regardless)
maktoob.search.yahoo.com: did not receive HSTS header
malaysia.search.yahoo.com: did not receive HSTS header
-man3s.jp: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
manage.zenpayroll.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
market.android.com: did not receive HSTS header (error ignored - included regardless)
marshut.net: could not connect to host
+matatall.com: could not connect to host
+mattmccutchen.net: could not connect to host
+mediacru.sh: could not connect to host
megashur.se: did not receive HSTS header
megaxchange.com: did not receive HSTS header
+meinebo.it: could not connect to host
minikneet.nl: did not receive HSTS header
-mirindadomo.ru: did not receive HSTS header
+miniku.net: did not receive HSTS header
+mirindadomo.ru: could not connect to host
mobilethreat.net: could not connect to host
mobilethreatnetwork.net: could not connect to host
mocloud.eu: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
movelaria.com.br: did not receive HSTS header
+mqas.net: could not connect to host
mt.search.yahoo.com: did not receive HSTS header
mu.search.yahoo.com: did not receive HSTS header
mw.search.yahoo.com: did not receive HSTS header
mx.search.yahoo.com: did not receive HSTS header
my.alfresco.com: did not receive HSTS header
mydigipass.com: did not receive HSTS header
mykolab.com: did not receive HSTS header
mykreuzfahrt.de: did not receive HSTS header
@@ -209,17 +219,16 @@ nexth.de: could not connect to host
nexth.net: could not connect to host
nexth.us: could not connect to host
ni.search.yahoo.com: did not receive HSTS header
nl.search.yahoo.com: did not receive HSTS header
no.search.yahoo.com: did not receive HSTS header
noexpect.org: could not connect to host
np.search.yahoo.com: did not receive HSTS header
npw.net: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
-nutsandboltsmedia.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
nz.search.yahoo.com: did not receive HSTS header
openshift.redhat.com: did not receive HSTS header
otakurepublic.com: did not receive HSTS header
ottospora.nl: could not connect to host
pa.search.yahoo.com: did not receive HSTS header
passwordbox.com: did not receive HSTS header
passwords.google.com: did not receive HSTS header (error ignored - included regardless)
pe.search.yahoo.com: did not receive HSTS header
@@ -246,17 +255,16 @@ riftnetwork.net: did not receive HSTS he
riseup.net: did not receive HSTS header
rme.li: did not receive HSTS header
ro.search.yahoo.com: did not receive HSTS header
robteix.com: did not receive HSTS header
roddis.net: did not receive HSTS header
ru.search.yahoo.com: did not receive HSTS header
rw.search.yahoo.com: did not receive HSTS header
sah3.net: could not connect to host
-samba.org: could not connect to host
saturngames.co.uk: could not connect to host
script.google.com: did not receive HSTS header (error ignored - included regardless)
se.search.yahoo.com: did not receive HSTS header
search.yahoo.com: did not receive HSTS header
security.google.com: did not receive HSTS header (error ignored - included regardless)
semenkovich.com: did not receive HSTS header
seomobo.com: did not receive HSTS header
seowarp.net: max-age too low: 1576800
@@ -269,22 +277,20 @@ silentcircle.org: could not connect to h
simon.butcher.name: max-age too low: 2629743
simplyfixit.co.uk: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
sites.google.com: did not receive HSTS header (error ignored - included regardless)
sol.io: could not connect to host
souyar.de: could not connect to host
souyar.net: could not connect to host
souyar.us: could not connect to host
spdysync.com: did not receive HSTS header
-spongepowered.org: did not receive HSTS header
spreadsheets.google.com: did not receive HSTS header (error ignored - included regardless)
square.com: did not receive HSTS header
ssl.google-analytics.com: did not receive HSTS header (error ignored - included regardless)
ssl.panoramio.com: did not receive HSTS header
-staticanime.net: did not receive HSTS header
stocktrade.de: could not connect to host
suite73.org: could not connect to host
sunshinepress.org: could not connect to host
surfeasy.com: did not receive HSTS header
suzukikenichi.com: did not receive HSTS header
sv.search.yahoo.com: did not receive HSTS header
t.facebook.com: did not receive HSTS header
tablet.facebook.com: did not receive HSTS header
@@ -323,25 +329,27 @@ wohnungsbau-ludwigsburg.de: did not rece
www.apollo-auto.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
www.calyxinstitute.org: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
www.cueup.com: could not connect to host
www.developer.mydigipass.com: could not connect to host
www.elanex.biz: did not receive HSTS header
www.gmail.com: did not receive HSTS header (error ignored - included regardless)
www.googlemail.com: did not receive HSTS header (error ignored - included regardless)
www.greplin.com: could not connect to host
+www.heliosnet.com: could not connect to host
www.jitsi.org: did not receive HSTS header
www.logentries.com: did not receive HSTS header
www.moneybookers.com: did not receive HSTS header
www.neonisi.com: could not connect to host
www.paycheckrecords.com: max-age too low: 86400
www.rme.li: did not receive HSTS header
www.sandbox.mydigipass.com: could not connect to host
www.surfeasy.com: did not receive HSTS header
xa.search.yahoo.com: did not receive HSTS header
+xplore-dna.net: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
xtream-hosting.com: could not connect to host
xtream-hosting.de: could not connect to host
xtream-hosting.eu: could not connect to host
xtreamhosting.eu: could not connect to host
za.search.yahoo.com: did not receive HSTS header
zh.search.yahoo.com: did not receive HSTS header
zoo24.de: could not connect to host
zotero.org: did not receive HSTS header
--- a/security/manager/boot/src/nsSTSPreloadList.inc
+++ b/security/manager/boot/src/nsSTSPreloadList.inc
@@ -3,17 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*****************************************************************************/
/* This is an automatically generated file. If you're not */
/* nsSiteSecurityService.cpp, you shouldn't be #including it. */
/*****************************************************************************/
#include <stdint.h>
-const PRTime gPreloadListExpirationTime = INT64_C(1434194152739000);
+const PRTime gPreloadListExpirationTime = INT64_C(1434798773028000);
class nsSTSPreload
{
public:
const char *mHost;
const bool mIncludeSubdomains;
};
@@ -26,28 +26,31 @@ static const nsSTSPreload kSTSPreloadLis
{ "1a-vermessung.at", true },
{ "1a-werkstattgeraete.de", true },
{ "2048game.co.uk", true },
{ "302.nyc", true },
{ "accounts.firefox.com", true },
{ "accounts.google.com", true },
{ "aclu.org", false },
{ "activiti.alfresco.com", false },
+ { "acus.gov", true },
{ "adamkostecki.de", true },
{ "addvocate.com", true },
{ "adlershop.ch", true },
{ "admin.google.com", true },
{ "adorai.tk", true },
{ "adsfund.org", true },
+ { "agrimap.com", true },
{ "ahoyconference.com", true },
{ "ahwatukeefoothillsmontessori.com", true },
{ "aids.gov", true },
{ "aie.de", true },
{ "airlea.com", true },
{ "aiticon.com", true },
+ { "ajouin.com", true },
{ "aladdinschools.appspot.com", false },
{ "alainwolf.net", true },
{ "alecvannoten.be", true },
{ "alexsexton.com", true },
{ "alexyang.me", true },
{ "alpha.irccloud.com", false },
{ "anadoluefessporkulubu.org", true },
{ "andreasbreitenlohner.de", true },
@@ -80,16 +83,17 @@ static const nsSTSPreload kSTSPreloadLis
{ "archlinux.de", true },
{ "arendburgers.nl", true },
{ "arguggi.co.uk", true },
{ "arivo.com.br", true },
{ "arlen.io", true },
{ "atavio.at", true },
{ "atavio.ch", true },
{ "atavio.de", true },
+ { "atishchenko.com", true },
{ "atlantischild.hu", true },
{ "atlassian.net", true },
{ "atte.fi", true },
{ "auf-feindgebiet.de", true },
{ "authentication.io", true },
{ "azabani.com", true },
{ "balcan-underground.net", true },
{ "baldwinkoo.com", true },
@@ -106,23 +110,25 @@ static const nsSTSPreload kSTSPreloadLis
{ "beamitapp.com", true },
{ "beastowner.com", true },
{ "beastowner.li", true },
{ "bedeta.de", true },
{ "bedreid.dk", true },
{ "beneathvt.com", true },
{ "benjamin.pe", true },
{ "benjamins.com", true },
+ { "bentertain.de", true },
{ "best-wedding-quotes.com", true },
{ "bfelob.gov", true },
{ "bgneuesheim.de", true },
{ "bhatia.at", true },
{ "biathloncup.ru", true },
{ "big-andy.co.uk", true },
{ "bigbrownpromotions.com.au", true },
+ { "bit.voyage", true },
{ "bitbucket.org", false },
{ "bitfactory.ws", true },
{ "bitmex.com", true },
{ "bitmon.net", true },
{ "bitpod.de", true },
{ "bjornjohansen.no", true },
{ "bl4ckb0x.com", true },
{ "bl4ckb0x.de", true },
@@ -152,16 +158,17 @@ static const nsSTSPreload kSTSPreloadLis
{ "bugzil.la", true },
{ "bugzilla.mozilla.org", true },
{ "bulktrade.de", true },
{ "business.facebook.com", false },
{ "business.lookout.com", false },
{ "buttercoin.com", true },
{ "buzzconcert.com", true },
{ "bytepark.de", false },
+ { "bzv-fr.eu", true },
{ "ca.gparent.org", false },
{ "cackette.com", true },
{ "call.me", true },
{ "calomel.org", true },
{ "camolist.com", true },
{ "cao.gov", true },
{ "caremad.io", true },
{ "carezone.com", false },
@@ -195,96 +202,104 @@ static const nsSTSPreload kSTSPreloadLis
{ "cloudcert.org", true },
{ "cloudns.com.au", true },
{ "cloudsecurityalliance.org", true },
{ "cloudstoragemaus.com", true },
{ "cloudup.com", true },
{ "code-poets.co.uk", true },
{ "code.facebook.com", false },
{ "code.google.com", true },
+ { "codepoints.net", true },
{ "codepref.com", true },
+ { "codepx.com", true },
{ "codereview.appspot.com", false },
{ "codereview.chromium.org", true },
{ "coffeeetc.co.uk", true },
{ "coinapult.com", true },
{ "comdurav.com", true },
{ "comssa.org.au", true },
- { "config.schokokeks.org", true },
+ { "config.schokokeks.org", false },
{ "conformal.com", true },
{ "conrad-kostecki.de", true },
{ "controlcenter.gigahost.dk", true },
{ "cor-ser.es", true },
{ "cordial-restaurant.com", true },
{ "cotonea.de", true },
{ "crm.onlime.ch", false },
{ "crowdjuris.com", true },
{ "crypto.cat", false },
{ "cryptobin.org", true },
{ "cryptopartyatx.org", true },
{ "cs50.harvard.edu", true },
+ { "cs50.net", true },
{ "cspbuilder.info", true },
{ "csuw.net", true },
{ "cube.de", true },
{ "cupcake.io", true },
{ "cupcake.is", true },
{ "curiosity-driven.org", true },
{ "curlybracket.co.uk", true },
{ "cyanogenmod.xxx", true },
{ "cybershambles.com", true },
{ "cybozu.com", true },
{ "cyon.ch", true },
{ "cyphertite.com", true },
+ { "cyprus-company-service.com", true },
{ "czbix.com", true },
{ "daphne.informatik.uni-freiburg.de", true },
{ "darchoods.net", false },
+ { "darkpony.ru", true },
+ { "darom.jp", true },
{ "data-abundance.com", true },
{ "data.qld.gov.au", false },
{ "datenkeks.de", true },
{ "daveoc64.co.uk", true },
{ "davidlyness.com", true },
+ { "davidnoren.com", true },
{ "dccode.gov", true },
{ "deadbeef.ninja", true },
{ "dealcruiser.nl", true },
{ "debtkit.co.uk", true },
{ "dedimax.de", true },
{ "dee.pe", true },
{ "denh.am", true },
{ "depechemode-live.com", true },
{ "derevtsov.com", false },
{ "derhil.de", true },
{ "detectify.com", false },
{ "developer.mydigipass.com", false },
{ "developers.facebook.com", false },
- { "devh.de", true },
{ "diamante.ro", true },
{ "die-besten-weisheiten.de", true },
{ "dillonkorman.com", true },
{ "dinamoelektrik.com", true },
{ "dist.torproject.org", false },
{ "dixmag.com", true },
{ "dl.google.com", true },
{ "dlc.viasinc.com", true },
{ "dm.lookout.com", false },
{ "dm.mylookout.com", false },
{ "doc.python.org", true },
{ "docs.google.com", true },
{ "docs.python.org", true },
{ "domains.google.com", true },
{ "donmez.ws", false },
+ { "donotcall.gov", true },
{ "dreadbyte.com", true },
{ "drive.google.com", true },
{ "dropbox.com", true },
{ "dylanscott.com.au", true },
{ "dzlibs.io", true },
{ "e-kontakti.fi", true },
{ "earmarks.gov", true },
{ "easysimplecrm.com", false },
{ "ebanking.indovinabank.com.vn", false },
{ "ecdn.cz", true },
{ "ecfs.link", true },
+ { "ecg.fr", false },
{ "ecosystem.atlassian.net", true },
{ "edit.yahoo.com", false },
{ "eduroam.no", true },
{ "edyou.eu", true },
{ "ef.gy", true },
{ "eff.org", true },
{ "egfl.org.uk", true },
{ "egit.co", true },
@@ -301,41 +316,46 @@ static const nsSTSPreload kSTSPreloadLis
{ "errors.zenpayroll.com", false },
{ "espra.com", true },
{ "ethack.org", true },
{ "ethitter.com", true },
{ "eurotramp.com", true },
{ "everhome.de", true },
{ "evstatus.com", true },
{ "exiahost.com", true },
+ { "exon.io", true },
{ "expatads.com", true },
{ "explodie.org", true },
+ { "extendwings.com", true },
{ "f-droid.org", true },
{ "fabhub.io", true },
{ "fabianfischer.de", true },
{ "facebook.com", false },
{ "factor.cc", false },
{ "fairbill.com", true },
{ "fakturoid.cz", true },
{ "fant.dk", true },
{ "faq.lookout.com", false },
{ "fassadenverkleidung24.de", true },
{ "fastcomcorp.net", true },
+ { "federalregister.gov", true },
+ { "fedorahosted.org", true },
{ "fedorapeople.org", true },
{ "feedbin.com", false },
{ "ferienhaus-polchow-ruegen.de", false },
{ "fewo-thueringer-wald.de", true },
{ "fiken.no", true },
{ "filedir.com", false },
{ "filip-prochazka.com", true },
{ "finn.io", true },
{ "firebaseio-demo.com", true },
{ "firebaseio.com", true },
{ "firefart.at", true },
{ "firemail.io", true },
+ { "firma-offshore.com", true },
{ "firstlook.org", true },
{ "fischer-its.com", true },
{ "fj.simple.com", false },
{ "flamer-scene.com", true },
{ "fleximus.org", false },
{ "floobits.com", true },
{ "florianmitrea.uk", true },
{ "flynn.io", true },
@@ -343,32 +363,37 @@ static const nsSTSPreload kSTSPreloadLis
{ "forewordreviews.com", true },
{ "forodeespanol.com", true },
{ "forum.linode.com", false },
{ "forum.quantifiedself.com", true },
{ "fralef.me", false },
{ "frederik-braun.com", true },
{ "freenetproject.org", true },
{ "freeshell.de", true },
+ { "freesounding.com", true },
+ { "freesounding.ru", true },
{ "freethought.org.au", true },
{ "fronteers.nl", true },
+ { "ftccomplaintassistant.gov", true },
{ "fundingempire.com", true },
{ "futos.de", true },
{ "gambitnash.co.uk", true },
{ "gamercredo.com", true },
{ "garron.net", true },
{ "gavick.com", true },
{ "gaytorrent.ru", true },
{ "gemeinfreie-lieder.de", true },
{ "gerardozamudio.mx", true },
{ "gernert-server.de", true },
{ "get.zenpayroll.com", false },
{ "getable.com", true },
{ "getcloak.com", false },
{ "getdigitized.net", true },
+ { "getfedora.org", true },
+ { "getfittedstore.com", true },
{ "getssl.uz", true },
{ "giacomopelagatti.it", true },
{ "github.com", true },
{ "gizzo.sk", true },
{ "glass.google.com", true },
{ "globuli-info.de", true },
{ "glossopnorthendafc.co.uk", true },
{ "gmail.com", false },
@@ -396,16 +421,17 @@ static const nsSTSPreload kSTSPreloadLis
{ "hackerone.com", true },
{ "hansvaneijsden.com", true },
{ "happylifestyle.com", true },
{ "harvestapp.com", true },
{ "hasilocke.de", true },
{ "haste.ch", true },
{ "haufschild.de", true },
{ "hausverbrauch.de", true },
+ { "hda.me", true },
{ "heartlandrentals.com", true },
{ "heha.co", false },
{ "heid.ws", true },
{ "heijblok.com", true },
{ "helichat.de", true },
{ "help.simpletax.ca", false },
{ "helpium.de", true },
{ "hemlockhillscabinrentals.com", true },
@@ -424,16 +450,17 @@ static const nsSTSPreload kSTSPreloadLis
{ "hostinginnederland.nl", true },
{ "hostix.de", true },
{ "howrandom.org", true },
{ "howsmyssl.com", true },
{ "howsmytls.com", true },
{ "hpac-portal.com", true },
{ "hrackydomino.cz", true },
{ "hsmr.cc", true },
+ { "hsr.gov", true },
{ "hstsfail.appspot.com", true },
{ "html5.org", true },
{ "httpswatch.com", true },
{ "i5y.co.uk", true },
{ "iamcarrico.com", true },
{ "ian.sh", true },
{ "iban.is", true },
{ "id-co.in", true },
@@ -444,23 +471,25 @@ static const nsSTSPreload kSTSPreloadLis
{ "ihrlotto.de", true },
{ "ikkatsu-satei.jp", true },
{ "ilikerainbows.co.uk", true },
{ "imaginary.ca", true },
{ "imouto.my", false },
{ "in.xero.com", false },
{ "inb4.us", true },
{ "inbox.google.com", true },
+ { "iniiter.com", true },
{ "inkbunny.net", true },
{ "inleaked.com", true },
{ "innophate-security.com", true },
{ "innophate-security.nl", true },
{ "insouciant.org", true },
{ "instasex.ch", true },
{ "interserved.com", true },
+ { "ionas-law.ro", true },
{ "iranianlawschool.com", true },
{ "iridiumbrowser.de", true },
{ "irische-segenswuensche.info", true },
{ "ironfistdesign.com", true },
{ "isitchristmas.com", true },
{ "it-schwerin.de", true },
{ "itdashboard.gov", true },
{ "itsamurai.ru", true },
@@ -489,16 +518,17 @@ static const nsSTSPreload kSTSPreloadLis
{ "k-dev.de", true },
{ "kaheim.de", true },
{ "kardize24.pl", true },
{ "karmaspa.se", true },
{ "kartonmodellbau.org", true },
{ "kdex.de", true },
{ "kdyby.org", true },
{ "keeleysam.com", true },
+ { "keepa.com", false },
{ "keepclean.me", true },
{ "keeperapp.com", true },
{ "keepersecurity.com", true },
{ "kernel-error.de", true },
{ "kevincox.ca", true },
{ "keycdn.com", true },
{ "keyerror.com", true },
{ "khanovaskola.cz", true },
@@ -512,50 +542,54 @@ static const nsSTSPreload kSTSPreloadLis
{ "kirkforsenate.com", true },
{ "kitsta.com", true },
{ "klarmobil-empfehlen.de", true },
{ "klatschreime.de", true },
{ "klausbrinch.dk", true },
{ "klaxn.com", true },
{ "kleidertauschpartys.de", true },
{ "klingeletest.de", true },
+ { "knip.ch", true },
{ "knowledgehook.com", true },
{ "koenvdheuvel.me", true },
{ "komandakovalchuk.com", true },
{ "konklone.com", true },
{ "koop-bremen.de", true },
{ "koordinate.net", true },
{ "kosho.org", true },
{ "kpebetka.net", true },
{ "kraken.io", true },
{ "kuppingercole.com", true },
{ "kupschke.net", true },
{ "kura.io", true },
+ { "laf.in.net", true },
{ "lagerauftrag.info", true },
{ "lancejames.com", true },
{ "lasst-uns-beten.de", true },
{ "lastpass.com", false },
{ "launchkey.com", true },
{ "lavalite.de", true },
{ "lb-toner.de", true },
{ "leadbook.ru", true },
{ "ledgerscope.net", false },
{ "leibniz-remscheid.de", true },
+ { "leifdreizler.com", true },
{ "leonardcamacho.me", true },
{ "leonklingele.de", true },
{ "les-corsaires.net", true },
{ "libraryfreedomproject.org", true },
{ "liebel.org", true },
{ "lighting-centres.co.uk", true },
{ "lilpwny.com", true },
{ "limpid.nl", true },
{ "lingolia.com", true },
{ "linode.com", false },
{ "linux-admin-california.com", true },
{ "linx.net", true },
+ { "livej.am", true },
{ "ljs.io", true },
{ "lobste.rs", true },
{ "lockify.com", true },
{ "lodash.com", true },
{ "loenshotel.de", true },
{ "loftboard.eu", true },
{ "logentries.com", false },
{ "login.corp.google.com", true },
@@ -568,88 +602,98 @@ static const nsSTSPreload kSTSPreloadLis
{ "lolicore.ch", true },
{ "lookout.com", false },
{ "ludwig.im", true },
{ "luelistan.net", true },
{ "lukonet.com", true },
{ "lumi.do", false },
{ "luneta.nearbuysystems.com", false },
{ "m.facebook.com", false },
+ { "m0wef.uk", true },
{ "mach-politik.ch", true },
{ "madars.org", true },
{ "maff.scot", false },
{ "magneticanvil.com", true },
+ { "mahamed91.pw", true },
{ "mail.de", true },
{ "mail.google.com", true },
{ "mail.yahoo.com", false },
{ "mailbox.org", true },
{ "makeitdynamic.com", true },
{ "makeyourlaws.org", true },
{ "malnex.de", true },
{ "man3s.jp", true },
{ "manage.zenpayroll.com", false },
{ "manageprojects.com", true },
{ "manager.linode.com", false },
{ "mandala-ausmalbilder.de", true },
{ "manicode.com", true },
{ "market.android.com", true },
{ "markusueberallassetmanagement.de", true },
{ "marshut.net", true },
+ { "massivum.de", true },
{ "matatall.com", true },
{ "mathiasbynens.be", true },
{ "matteomarescotti.it", true },
{ "mattmccutchen.net", true },
{ "max.gov", true },
{ "mbasic.facebook.com", false },
{ "mbp.banking.co.at", false },
{ "md5file.com", true },
{ "mdfnet.se", true },
{ "meamod.com", true },
{ "mediacru.sh", true },
{ "medium.com", true },
{ "meetfinch.com", true },
{ "mega.co.nz", false },
+ { "megaplan.cz", true },
+ { "megaplan.ru", true },
{ "meinebo.it", true },
{ "members.mayfirst.org", false },
{ "members.nearlyfreespeech.net", false },
{ "miasarafina.de", true },
{ "michalspacek.cz", true },
{ "mig5.net", true },
{ "mike-bland.com", true },
+ { "miketabor.com", true },
{ "mikewest.org", true },
{ "miku.hatsune.my", false },
{ "mimeit.de", true },
{ "minecraftvoter.com", true },
+ { "mineover.es", true },
{ "minez-nightswatch.com", true },
{ "minikneet.com", true },
- { "miniku.net", true },
{ "minnesotadata.com", true },
{ "mirrorx.com", true },
{ "miskatonic.org", true },
{ "mkcert.org", true },
{ "mkw.st", true },
{ "mnsure.org", true },
{ "mobilcom-debitel-empfehlen.de", true },
{ "mobile.usaa.com", false },
+ { "mokote.com", true },
{ "mondwandler.de", true },
{ "morethanadream.lv", true },
{ "moriz.de", true },
{ "mothereff.in", true },
{ "mountainmusicpromotions.com", true },
{ "mountainroseherbs.com", true },
{ "movlib.org", true },
{ "mpreserver.com", true },
{ "mqas.net", true },
+ { "mr-hosting.com", true },
+ { "msa-aesch.ch", true },
{ "msc-seereisen.net", true },
{ "mths.be", true },
{ "mtouch.facebook.com", false },
{ "mudcrab.us", true },
{ "mujadin.se", true },
{ "munich-rage.de", true },
{ "musicgamegalaxy.de", true },
+ { "mutamatic.com", true },
{ "mutantmonkey.in", true },
{ "mutantmonkey.info", true },
{ "mutantmonkey.sexy", true },
{ "mvno.io", true },
{ "mwe.st", true },
{ "my.onlime.ch", false },
{ "my.usa.gov", true },
{ "my.xero.com", false },
@@ -690,25 +734,29 @@ static const nsSTSPreload kSTSPreloadLis
{ "nu3.com", true },
{ "nu3.de", true },
{ "nu3.dk", true },
{ "nu3.fi", true },
{ "nu3.fr", true },
{ "nu3.no", true },
{ "nu3.se", true },
{ "null-sec.ru", true },
+ { "nutsandboltsmedia.com", false },
{ "nwgh.org", true },
+ { "nymphetomania.net", true },
{ "oakslighting.co.uk", true },
+ { "offshore-firma.org", true },
{ "okmx.de", true },
{ "omitech.co.uk", true },
{ "onedot.nl", true },
{ "onedrive.com", true },
{ "onedrive.live.com", false },
{ "onsitemassageco.com", true },
{ "ooonja.de", true },
+ { "openacademies.com", true },
{ "opendesk.cc", true },
{ "oplop.appspot.com", true },
{ "opsmate.com", false },
{ "optimus.io", true },
{ "orbograph-hrcm.com", true },
{ "oscarvk.ch", true },
{ "osterkraenzchen.de", true },
{ "otakuworld.de", true },
@@ -746,70 +794,80 @@ static const nsSTSPreload kSTSPreloadLis
{ "pdf.yt", true },
{ "peercraft.com", true },
{ "pentesterlab.com", true },
{ "perfectionis.me", true },
{ "personaldatabasen.no", true },
{ "pestici.de", true },
{ "petrolplus.ru", true },
{ "pharmaboard.de", true },
+ { "phoenix.dj", true },
{ "phoenixlogan.com", true },
{ "phryanjr.com", false },
{ "phurl.de", true },
{ "picksin.club", true },
{ "pieperhome.de", true },
{ "pierre-schmitz.com", true },
{ "pixel.facebook.com", false },
{ "pixi.me", true },
{ "play.google.com", true },
{ "plothost.com", true },
{ "plus.google.com", false },
{ "plus.sandbox.google.com", false },
+ { "pmg-offshore-company.com", true },
+ { "pmg-purchase.com", true },
+ { "pmg-purchase.net", true },
{ "polymathematician.com", true },
+ { "polypho.nyc", true },
{ "portal.tirol.gv.at", true },
{ "posteo.de", false },
{ "powerplannerapp.com", true },
{ "prakharprasad.com", true },
{ "prefontaine.name", true },
{ "privategiant.com", true },
{ "profiles.google.com", true },
{ "progressiveplanning.com", true },
{ "projektzentrisch.de", true },
{ "propagandism.org", true },
{ "prowhisky.de", true },
{ "proximato.com", true },
{ "puac.de", true },
{ "pubkey.is", true },
{ "publications.qld.gov.au", false },
+ { "puiterwijk.org", true },
{ "pult.co", false },
{ "pwd.ovh", true },
{ "pypa.io", true },
{ "pypi.python.org", true },
{ "python.org", false },
{ "qetesh.de", true },
{ "quuz.org", true },
{ "r3s1stanc3.me", true },
{ "rad-route.de", true },
{ "rafaelcz.de", true },
{ "ragingserenity.com", true },
{ "raiseyourflag.com", true },
{ "rasing.me", true },
{ "raspass.me", true },
{ "ravchat.com", true },
+ { "redletter.link", true },
{ "redports.org", true },
{ "redteam-pentesting.de", true },
{ "reedloden.com", true },
+ { "reg.ru", true },
{ "reishunger.de", true },
+ { "release-monitoring.org", true },
{ "reliable-mail.de", true },
{ "research.facebook.com", false },
{ "reserve-online.net", true },
{ "residentsinsurance.co.uk", true },
{ "reviews.anime.my", true },
{ "riccy.org", true },
{ "riesenmagnete.de", true },
+ { "rika.me", true },
{ "rippleunion.com", true },
{ "rlalique.com", true },
{ "roland.io", true },
{ "romab.com", true },
{ "romans-place.me.uk", true },
{ "romulusapp.com", true },
{ "room-checkin24.de", true },
{ "rosenkeller.org", true },
@@ -829,22 +887,23 @@ static const nsSTSPreload kSTSPreloadLis
{ "samuelkeeley.com", true },
{ "sanatfilan.com", false },
{ "sandbox.mydigipass.com", false },
{ "save.gov", true },
{ "saveaward.gov", true },
{ "savetheinternet.eu", true },
{ "savvytime.com", true },
{ "schachburg.de", true },
- { "schokokeks.org", true },
+ { "schokokeks.org", false },
{ "schreiber-netzwerk.eu", true },
{ "schwarzer.it", true },
{ "sciencex.com", true },
{ "scotthelme.co.uk", true },
{ "scrambl.is", true },
+ { "scrambler.in", false },
{ "scribe.systems", true },
{ "script.google.com", true },
{ "sdsl-speedtest.de", true },
{ "secure.facebook.com", false },
{ "securesuisse.ch", true },
{ "securify.nl", true },
{ "security-carpet.com", true },
{ "security.google.com", true },
@@ -869,16 +928,17 @@ static const nsSTSPreload kSTSPreloadLis
{ "simple.com", false },
{ "simpletax.ca", false },
{ "simplia.cz", true },
{ "simplystudio.com", true },
{ "siraweb.org", true },
{ "siriad.com", true },
{ "sites.google.com", true },
{ "sitesten.com", true },
+ { "sjoorm.com", true },
{ "skhosting.eu", true },
{ "skogsbruket.fi", true },
{ "skogskultur.fi", true },
{ "skydrive.live.com", false },
{ "slack.com", true },
{ "slattery.co", true },
{ "slevomat.cz", true },
{ "slidebatch.com", true },
@@ -890,30 +950,32 @@ static const nsSTSPreload kSTSPreloadLis
{ "sorz.org", true },
{ "sour.is", true },
{ "southside-crew.com", true },
{ "souvik.me", true },
{ "spartantheatre.org", true },
{ "spawn.cz", true },
{ "spencerbaer.com", true },
{ "spideroak.com", true },
+ { "spongepowered.org", true },
{ "spreadsheets.google.com", true },
{ "spreed.me", true },
{ "sprueche-zum-valentinstag.de", true },
{ "sprueche-zur-geburt.info", true },
{ "sprueche-zur-hochzeit.de", true },
{ "sprueche-zur-konfirmation.de", true },
{ "squareup.com", false },
{ "srevilak.net", true },
{ "sro.center", true },
{ "ssl.google-analytics.com", true },
{ "sslmate.com", true },
{ "stage.wepay.com", false },
{ "standardssuck.org", true },
{ "static.wepay.com", false },
+ { "staticanime.net", false },
{ "stationary-traveller.eu", true },
{ "steventress.com", true },
{ "stocktrade.de", false },
{ "strasweb.fr", false },
{ "stretchmyan.us", true },
{ "stripe.com", true },
{ "strongest-privacy.com", true },
{ "studienportal.eu", true },
@@ -921,17 +983,19 @@ static const nsSTSPreload kSTSPreloadLis
{ "stulda.cz", true },
{ "subrosa.io", true },
{ "suite73.org", true },
{ "sunjaydhama.com", true },
{ "supplies24.at", true },
{ "supplies24.es", true },
{ "support.mayfirst.org", false },
{ "surkatty.org", true },
+ { "survivalmonkey.com", true },
{ "swehack.org", false },
+ { "sychov.pro", true },
{ "sylaps.com", true },
{ "sysctl.se", true },
{ "sysdb.io", true },
{ "syss.de", true },
{ "tadigitalstore.com", true },
{ "tageau.com", true },
{ "talk.google.com", true },
{ "talkgadget.google.com", true },
@@ -940,19 +1004,21 @@ static const nsSTSPreload kSTSPreloadLis
{ "tas2580.net", true },
{ "tatort-fanpage.de", true },
{ "tauchkater.de", true },
{ "tbspace.de", true },
{ "techhipster.net", true },
{ "tegelsensanitaironline.nl", true },
{ "tekshrek.com", true },
{ "tent.io", true },
+ { "terrty.net", true },
{ "testsuite.org", true },
{ "texte-zur-taufe.de", true },
{ "the-sky-of-valkyries.com", true },
+ { "thebimhub.com", true },
{ "thecustomizewindows.com", true },
{ "thepaymentscompany.com", true },
{ "therapynotes.com", false },
{ "theshadestore.com", true },
{ "thetomharling.com", true },
{ "thomastimepieces.com.au", true },
{ "thorncreek.net", false },
{ "thusoy.com", true },
@@ -961,16 +1027,17 @@ static const nsSTSPreload kSTSPreloadLis
{ "tid.jp", true },
{ "timtaubert.de", true },
{ "tinfoilsecurity.com", false },
{ "tinte24.de", true },
{ "tintenfix.net", true },
{ "tipps-fuer-den-haushalt.de", true },
{ "tittelbach.at", true },
{ "tls.li", true },
+ { "tmtopup.com", true },
{ "tno.io", true },
{ "tobias-kluge.de", true },
{ "todesschaf.org", true },
{ "tollmanz.com", true },
{ "tomfisher.eu", true },
{ "tomvote.com", true },
{ "toner24.at", true },
{ "toner24.co.uk", true },
@@ -1001,16 +1068,17 @@ static const nsSTSPreload kSTSPreloadLis
{ "tribut.de", true },
{ "tucuxi.org", true },
{ "tunebitfm.de", true },
{ "twentymilliseconds.com", true },
{ "twisto.cz", true },
{ "twitter.com", false },
{ "twitteroauth.com", true },
{ "typingrevolution.com", true },
+ { "uae-company-service.com", true },
{ "ub3rk1tten.com", true },
{ "ubertt.org", true },
{ "ukdefencejournal.org.uk", true },
{ "ukhas.net", true },
{ "ukrainians.ch", true },
{ "unison.com", true },
{ "unterfrankenclan.de", true },
{ "uonstaffhub.com", true },
@@ -1039,17 +1107,17 @@ static const nsSTSPreload kSTSPreloadLis
{ "webcollect.org.uk", true },
{ "webfilings-eu-mirror.appspot.com", true },
{ "webfilings-eu.appspot.com", true },
{ "webfilings-mirror-hrd.appspot.com", true },
{ "webfilings.appspot.com", true },
{ "weblogzwolle.nl", true },
{ "webmail.gigahost.dk", false },
{ "webmail.onlime.ch", false },
- { "webmail.schokokeks.org", true },
+ { "webmail.schokokeks.org", false },
{ "websenat.de", true },
{ "webtalis.nl", true },
{ "webtiles.co.uk", true },
{ "webtrh.cz", true },
{ "weggeweest.nl", true },
{ "welches-kinderfahrrad.de", true },
{ "wepay.com", false },
{ "wepay.in.th", true },
@@ -1061,22 +1129,25 @@ static const nsSTSPreload kSTSPreloadLis
{ "wf-pentest.appspot.com", true },
{ "wf-staging-hr.appspot.com", true },
{ "wf-training-hrd.appspot.com", true },
{ "wf-training-master.appspot.com", true },
{ "wf-trial-hrd.appspot.com", true },
{ "whatwg.org", true },
{ "whd-guide.de", true },
{ "when-release.ru", true },
+ { "wherephoto.com", true },
{ "whocalld.com", true },
{ "wieninternational.at", true },
{ "wiki.python.org", true },
{ "wildbee.org", true },
{ "willnorris.com", true },
+ { "wills.co.tt", true },
{ "winhistory-forum.net", true },
+ { "wondershift.biz", true },
{ "wootton95.com", true },
{ "wpletter.de", true },
{ "writeapp.me", false },
{ "wubthecaptain.eu", true },
{ "wunderlist.com", true },
{ "wundi.net", true },
{ "www.aclu.org", false },
{ "www.airbnb.com", true },
@@ -1109,33 +1180,38 @@ static const nsSTSPreload kSTSPreloadLis
{ "www.makeyourlaws.org", true },
{ "www.mydigipass.com", false },
{ "www.mylookout.com", false },
{ "www.noisebridge.net", false },
{ "www.opsmate.com", true },
{ "www.paypal.com", false },
{ "www.python.org", true },
{ "www.roddis.net", true },
- { "www.schokokeks.org", true },
+ { "www.schokokeks.org", false },
{ "www.simbolo.co.uk", false },
{ "www.simple.com", false },
{ "www.therapynotes.com", false },
{ "www.tinfoilsecurity.com", false },
{ "www.torproject.org", false },
{ "www.twitter.com", false },
{ "www.usaa.com", false },
{ "www.viasinc.com", true },
{ "www.wepay.com", false },
{ "www.zenpayroll.com", false },
+ { "wzrd.in", true },
{ "xbrlsuccess.appspot.com", true },
{ "xn--maraa-rta.org", true },
{ "xps2pdf.co.uk", true },
+ { "xuntier.ch", true },
{ "y-o-w.com", true },
{ "yahvehyireh.com", true },
+ { "yanovich.net", true },
+ { "yaporn.tv", false },
{ "yetii.net", true },
+ { "yorcom.nl", true },
{ "yoursecondphone.co", true },
{ "ypart.eu", true },
{ "z.ai", true },
{ "zenpayroll.com", false },
{ "zentralwolke.de", true },
{ "zeplin.io", false },
{ "zeropush.com", true },
{ "zhovner.com", false },
--- a/toolkit/components/reader/ReaderMode.jsm
+++ b/toolkit/components/reader/ReaderMode.jsm
@@ -12,17 +12,17 @@ Cu.import("resource://gre/modules/Servic
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.importGlobalProperties(["XMLHttpRequest"]);
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", "resource://services-common/utils.js");
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
-let ReaderMode = {
+this.ReaderMode = {
// Version of the cache schema.
CACHE_VERSION: 1,
DEBUG: 0,
// Don't try to parse the page if it has too many elements (for memory and
// performance reasons)
MAX_ELEMS_TO_PARSE: 3000,
--- a/toolkit/components/viewconfig/content/config.js
+++ b/toolkit/components/viewconfig/content/config.js
@@ -90,17 +90,17 @@ var view = {
index = gPrefView.length - index - 1;
}
else {
var pref = null;
if (index >= 0)
pref = gPrefView[index];
var old = document.getElementById(gSortedColumn);
- old.setAttribute("sortDirection", "");
+ old.removeAttribute("sortDirection");
gPrefArray.sort(gSortFunction = gSortFunctions[col.id]);
if (gPrefView != gPrefArray)
gPrefView.sort(gSortFunction);
gSortedColumn = col.id;
if (pref)
index = getViewIndexOfPref(pref);
}
col.element.setAttribute("sortDirection", gSortDirection > 0 ? "ascending" : "descending");
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/AddonWatcher.jsm
@@ -0,0 +1,83 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["AddonWatcher"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+ "resource://gre/modules/Preferences.jsm");
+
+let AddonWatcher = {
+ _lastAddonTime: {},
+ _timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
+ _callback: null,
+ _interval: 1500,
+ _ignoreList: null,
+ init: function(callback) {
+ if (!callback) {
+ return;
+ }
+
+ if (this._callback) {
+ return;
+ }
+
+ this._callback = callback;
+ try {
+ this._ignoreList = new Set(JSON.parse(Preferences.get("browser.addon-watch.ignore", null)));
+ } catch (ex) {
+ // probably some malformed JSON, ignore and carry on
+ this._ignoreList = new Set();
+ }
+ this._interval = Preferences.get("browser.addon-watch.interval", 15000);
+ this._timer.initWithCallback(this._checkAddons.bind(this), this._interval, Ci.nsITimer.TYPE_REPEATING_SLACK);
+ },
+ _checkAddons: function() {
+ let compartmentInfo = Cc["@mozilla.org/compartment-info;1"]
+ .getService(Ci.nsICompartmentInfo);
+ let compartments = compartmentInfo.getCompartments();
+ let count = compartments.length;
+ let addons = {};
+ for (let i = 0; i < count; i++) {
+ let compartment = compartments.queryElementAt(i, Ci.nsICompartment);
+ if (compartment.addonId) {
+ if (addons[compartment.addonId]) {
+ addons[compartment.addonId] += compartment.time;
+ } else {
+ addons[compartment.addonId] = compartment.time;
+ }
+ }
+ }
+ let limit = this._interval * Preferences.get("browser.addon-watch.percentage-limit", 75) * 10;
+ for (let addonId in addons) {
+ if (!this._ignoreList.has(addonId)) {
+ if (this._lastAddonTime[addonId] && (addons[addonId] - this._lastAddonTime[addonId]) > limit) {
+ this._callback(addonId);
+ }
+ this._lastAddonTime[addonId] = addons[addonId];
+ }
+ }
+ },
+ ignoreAddonForSession: function(addonid) {
+ this._ignoreList.add(addonid);
+ },
+ ignoreAddonPermanently: function(addonid) {
+ this._ignoreList.add(addonid);
+ try {
+ let ignoreList = JSON.parse(Preferences.get("browser.addon-watch.ignore", "[]"))
+ if (!ignoreList.includes(addonid)) {
+ ignoreList.push(addonid);
+ Preferences.set("browser.addon-watch.ignore", JSON.stringify(ignoreList));
+ }
+ } catch (ex) {
+ Preferences.set("browser.addon-watch.ignore", JSON.stringify([addonid]));
+ }
+ }
+};
--- a/toolkit/modules/ResetProfile.jsm
+++ b/toolkit/modules/ResetProfile.jsm
@@ -29,42 +29,16 @@ this.ResetProfile = {
("@mozilla.org/profile/migrator;1?app=" + MOZ_BUILD_APP + "&type=" + MOZ_APP_NAME in Cc);
} catch (e) {
// Catch exception when there is no selected profile.
Cu.reportError(e);
}
return false;
},
- getMigratedData: function() {
- Cu.import("resource:///modules/MigrationUtils.jsm");
-
- // From migration.properties
- const MIGRATED_TYPES = [
- 128,// Windows/Tabs
- 4, // History and Bookmarks
- 16, // Passwords
- 8, // Form History
- 2, // Cookies
- ];
-
- // Loop over possible data to migrate to give the user a list of what will be preserved.
- let dataTypes = [];
- for (let itemID of MIGRATED_TYPES) {
- try {
- let typeName = MigrationUtils.getLocalizedString(itemID + "_" + MOZ_APP_NAME);
- dataTypes.push(typeName);
- } catch (x) {
- // Catch exceptions when the string for a data type doesn't exist.
- Cu.reportError(x);
- }
- }
- return dataTypes;
- },
-
/**
* Ask the user if they wish to restart the application to reset the profile.
*/
openConfirmationDialog: function(window) {
// Prompt the user to confirm.
let params = {
reset: false,
};
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -7,16 +7,17 @@
XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']
MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
SPHINX_TREES['toolkit_modules'] = 'docs'
EXTRA_JS_MODULES += [
+ 'AddonWatcher.jsm',
'Battery.jsm',
'BinarySearch.jsm',
'BrowserUtils.jsm',
'CharsetMenu.jsm',
'debug.js',
'DeferredTask.jsm',
'Deprecated.jsm',
'Dict.jsm',
--- a/toolkit/mozapps/downloads/nsHelperAppDlg.js
+++ b/toolkit/mozapps/downloads/nsHelperAppDlg.js
@@ -1,17 +1,18 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/*
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-Components.utils.import("resource://gre/modules/Services.jsm");
+const {utils: Cu, interfaces: Ci, classes: Cc, results: Cr} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
///////////////////////////////////////////////////////////////////////////////
//// Helper Functions
/**
* Determines if a given directory is able to be used to download to.
*
* @param aDirectory
@@ -137,16 +138,24 @@ nsUnknownContentTypeDialog.prototype = {
// show: Open XUL dialog using window watcher. Since the dialog is not
// modal, it needs to be a top level window and the way to open
// one of those is via that route).
show: function(aLauncher, aContext, aReason) {
this.mLauncher = aLauncher;
this.mContext = aContext;
this.mReason = aReason;
+ // Cache some information in case this context goes away:
+ try {
+ let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+ this._mDownloadDir = new downloadModule.DownloadLastDir(parent);
+ } catch (ex) {
+ Cu.reportError("Missing window information when showing nsIHelperAppLauncherDialog: " + ex);
+ }
+
const nsITimer = Components.interfaces.nsITimer;
this._showTimer = Components.classes["@mozilla.org/timer;1"]
.createInstance(nsITimer);
this._showTimer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT);
},
// When opening from new tab, if tab closes while dialog is opening,
// (which is a race condition on the XUL file being cached and the timer
@@ -201,16 +210,32 @@ nsUnknownContentTypeDialog.prototype = {
this.mLauncher = aLauncher;
let prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
let bundle =
Services.strings
.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
+ let parent;
+ let gDownloadLastDir;
+ try {
+ parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+ } catch (ex) {}
+
+ if (parent) {
+ gDownloadLastDir = new downloadModule.DownloadLastDir(parent);
+ } else {
+ // Use the cached download info, but pick an arbitrary parent window
+ // because the original one is definitely gone (and nsIFilePicker doesn't like
+ // a null parent):
+ gDownloadLastDir = this._mDownloadDir;
+ parent = Services.wm.getMostRecentWindow("");
+ }
+
Task.spawn(function() {
if (!aForcePrompt) {
// Check to see if the user wishes to auto save to the default download
// folder without prompting. Note that preference might not be set.
let autodownload = false;
try {
autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR);
} catch (e) { }
@@ -236,22 +261,19 @@ nsUnknownContentTypeDialog.prototype = {
}
}
}
// Use file picker to show dialog.
var nsIFilePicker = Components.interfaces.nsIFilePicker;
var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
var windowTitle = bundle.GetStringFromName("saveDialogTitle");
- var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);
picker.init(parent, windowTitle, nsIFilePicker.modeSave);
picker.defaultString = aDefaultFile;
- let gDownloadLastDir = new downloadModule.DownloadLastDir(parent);
-
if (aSuggestedFileExtension) {
// aSuggestedFileExtension includes the period, so strip it
picker.defaultExtension = aSuggestedFileExtension.substring(1);
}
else {
try {
picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
}
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -200,17 +200,17 @@ xul|colorpicker[type="button"] {
}
xul|button > xul|*.button-box,
xul|menulist > xul|*.menulist-label-box {
padding-right: 10px !important;
padding-left: 10px !important;
}
-xul|menulist > xul|*.menulist-label-box > xul|*.menulist-icon {
+xul|menulist > xul|*.menulist-label-box > xul|*.menulist-icon[src] {
-moz-margin-end: 5px;
}
xul|button[type="menu"] > xul|*.button-box > xul|*.button-menu-dropmarker {
-moz-appearance: none;
margin: 1px 0;
-moz-margin-start: 10px;
padding: 0;
--- a/toolkit/themes/windows/global/in-content/common.css
+++ b/toolkit/themes/windows/global/in-content/common.css
@@ -58,14 +58,34 @@ xul|*.checkbox-icon {
}
html|a:-moz-focusring,
xul|*.text-link:-moz-focusring,
xul|*.inline-link:-moz-focusring {
border: 1px dotted -moz-DialogText;
}
+/* Don't draw a transparent border for the focusring because when page
+ colors are disabled, the border is drawn in -moz-DialogText */
+xul|*.text-link:not(:-moz-focusring),
+xul|button:not(:-moz-focusring) > xul|*.button-box,
+xul|menulist:not(:-moz-focusring) > xul|*.menulist-label-box,
xul|radio:not([focused="true"]) > xul|*.radio-label-box,
xul|checkbox:not(:-moz-focusring) > xul|*.checkbox-label-box {
border-width: 0;
margin: 1px;
+}
+
+xul|*.text-link:not(:-moz-focusring) {
+ margin-top: 2px;
+ margin-right: 1px !important;
+ margin-left: 1px !important;
+ margin-bottom: 3px;
+}
+
+xul|menulist:not(:-moz-focusring) > xul|*.menulist-label-box {
+ margin: 2px;
+}
+
+xul|radio:not([focused="true"]) > xul|*.radio-label-box,
+xul|checkbox:not(:-moz-focusring) > xul|*.checkbox-label-box {
-moz-margin-start: 0;
}