Merge m-c to inbound. a=merge

CLOSED TREE
This commit is contained in:
Ryan VanderMeulen 2015-06-12 14:58:41 -04:00
commit 6fc4fa1738
68 changed files with 895 additions and 363 deletions

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="9f36b711af7597a6a32471c3305cf1e2d6947d39"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b387bcad9bf8389580a8ae133b071053cfc9c30f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="41efd945c841175f733ef764fe5f6a85c592970b"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
@ -122,7 +122,7 @@
<project name="platform/system/media" path="system/media" revision="c1332c21c608f4932a6d7e83450411cde53315ef"/>
<default remote="caf" revision="LNX.LA.3.5.2.1.1" sync-j="4"/>
<!-- Platform common things -->
<project name="device-shinano-common" path="device/sony/shinano-common" remote="b2g" revision="9934000e6d842b4c4d76fb56f52584e1a3819c9c"/>
<project name="device-shinano-common" path="device/sony/shinano-common" remote="b2g" revision="ac45522bb3a14aff9facd74e2cd03430e1e0793f"/>
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="1bb28abbc215f45220620af5cd60a8ac1be93722"/>
<project name="device/qcom/common" path="device/qcom/common" revision="2501e5940ba69ece7654ff85611c76ae5bda299c"/>
<project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="5ada05ac150f643ef19e87015df7e106b88effe7"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="9f36b711af7597a6a32471c3305cf1e2d6947d39"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b387bcad9bf8389580a8ae133b071053cfc9c30f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="41efd945c841175f733ef764fe5f6a85c592970b"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="9f36b711af7597a6a32471c3305cf1e2d6947d39"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b387bcad9bf8389580a8ae133b071053cfc9c30f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="87a2d8ab9248540910e56921654367b78a587095"/>

View File

@ -17,10 +17,10 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="9f36b711af7597a6a32471c3305cf1e2d6947d39"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b387bcad9bf8389580a8ae133b071053cfc9c30f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="41efd945c841175f733ef764fe5f6a85c592970b"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="9f36b711af7597a6a32471c3305cf1e2d6947d39"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b387bcad9bf8389580a8ae133b071053cfc9c30f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="41efd945c841175f733ef764fe5f6a85c592970b"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="9f36b711af7597a6a32471c3305cf1e2d6947d39"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b387bcad9bf8389580a8ae133b071053cfc9c30f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="41efd945c841175f733ef764fe5f6a85c592970b"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
<!-- Stock Android things -->
<project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="9f36b711af7597a6a32471c3305cf1e2d6947d39"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b387bcad9bf8389580a8ae133b071053cfc9c30f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="87a2d8ab9248540910e56921654367b78a587095"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="9f36b711af7597a6a32471c3305cf1e2d6947d39"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b387bcad9bf8389580a8ae133b071053cfc9c30f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="41efd945c841175f733ef764fe5f6a85c592970b"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

View File

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "9f36b711af7597a6a32471c3305cf1e2d6947d39",
"git_revision": "b387bcad9bf8389580a8ae133b071053cfc9c30f",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "8583025b84cda74c63e83bbbdfd7a4f33b917f20",
"revision": "7dbfef98a254a3d56b8d38e003c988f9d19771a4",
"repo_path": "integration/gaia-central"
}

View File

@ -17,10 +17,10 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="9f36b711af7597a6a32471c3305cf1e2d6947d39"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b387bcad9bf8389580a8ae133b071053cfc9c30f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="41efd945c841175f733ef764fe5f6a85c592970b"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="9f36b711af7597a6a32471c3305cf1e2d6947d39"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b387bcad9bf8389580a8ae133b071053cfc9c30f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="41efd945c841175f733ef764fe5f6a85c592970b"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
<!-- Stock Android things -->
<project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>

View File

@ -522,38 +522,38 @@ let LoopUI;
* has been fetched.
*/
getFavicon: function(callback) {
let favicon = gBrowser.getIcon(gBrowser.selectedTab);
// If the tab image's url starts with http(s), fetch icon from favicon
// service via the moz-anno protocol.
if (/^https?:/.test(favicon)) {
let faviconURI = makeURI(favicon);
favicon = this.favIconService.getFaviconLinkForIcon(faviconURI).spec;
}
if (!favicon) {
callback(new Error("No favicon found"));
let pageURI = gBrowser.selectedTab.linkedBrowser.currentURI.spec;
// If the tab pages url starts with http(s), fetch icon.
if (!/^https?:/.test(pageURI)) {
callback();
return;
}
favicon = this.PlacesUtils.getImageURLForResolution(window, favicon);
// We XHR the favicon to get a File object, which we can pass to the FileReader
// object. The FileReader turns the File object into a data-uri.
let xhr = new XMLHttpRequest();
xhr.open("get", favicon, true);
xhr.responseType = "blob";
xhr.overrideMimeType("image/x-icon");
xhr.onload = () => {
if (xhr.status != 200) {
callback(new Error("Invalid status code received for favicon XHR: " + xhr.status));
return;
}
this.PlacesUtils.promiseFaviconLinkUrl(pageURI).then(uri => {
uri = this.PlacesUtils.getImageURLForResolution(window, uri.spec);
let reader = new FileReader();
reader.onload = () => callback(null, reader.result);
reader.onerror = callback;
reader.readAsDataURL(xhr.response);
};
xhr.onerror = callback;
xhr.send();
// We XHR the favicon to get a File object, which we can pass to the FileReader
// object. The FileReader turns the File object into a data-uri.
let xhr = new XMLHttpRequest();
xhr.open("get", uri, true);
xhr.responseType = "blob";
xhr.overrideMimeType("image/x-icon");
xhr.onload = () => {
if (xhr.status != 200) {
callback(new Error("Invalid status code received for favicon XHR: " + xhr.status));
return;
}
let reader = new FileReader();
reader.onload = reader.onload = () => callback(null, reader.result);
reader.onerror = callback;
reader.readAsDataURL(xhr.response);
};
xhr.onerror = callback;
xhr.send();
}).catch(err => {
callback(err || new Error("No favicon found"));
});
}
};
})();
@ -563,5 +563,3 @@ XPCOMUtils.defineLazyModuleGetter(LoopUI, "LoopRooms", "resource:///modules/loop
XPCOMUtils.defineLazyModuleGetter(LoopUI, "MozLoopService", "resource:///modules/loop/MozLoopService.jsm");
XPCOMUtils.defineLazyModuleGetter(LoopUI, "PanelFrame", "resource:///modules/PanelFrame.jsm");
XPCOMUtils.defineLazyModuleGetter(LoopUI, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(LoopUI, "favIconService",
"@mozilla.org/browser/favicon-service;1", "nsIFaviconService");

View File

@ -34,8 +34,8 @@
* along any dimension beyond the point at which an overflow event would
* occur". But none of -moz-{fit,max,min}-content do what we want here. So..
*/
min-width: 320px;
min-height: 280px;
min-width: 260px;
min-height: 315px;
}
#main-window[customize-entered] {
@ -907,8 +907,32 @@ chatbox {
width: 260px; /* CHAT_WIDTH_OPEN in socialchat.xml */
}
chatbox[large="true"] {
width: 300px;
chatbox[customSize] {
width: 300px; /* CHAT_WIDTH_OPEN_ALT in socialchat.xml */
}
#chat-window[customSize] {
min-width: 300px;
}
chatbox[customSize="loopChatEnabled"] {
/* 325px as defined per UX */
height: 325px;
}
#chat-window[customSize="loopChatEnabled"] {
/* 325px + 30px top bar height. */
min-height: calc(325px + 30px);
}
chatbox[customSize="loopChatMessageAppended"] {
/* 445px as defined per UX */
height: 445px;
}
#chat-window[customSize="loopChatMessageAppended"] {
/* 445px + 30px top bar height. */
min-height: calc(445px + 30px);
}
chatbox[minimized="true"] {
@ -922,6 +946,15 @@ chatbar {
max-height: 0;
}
.chatbar-innerbox {
margin: -285px 0 0;
}
chatbar[customSize] > .chatbar-innerbox {
/* 425px to make room for the maximum custom-size chatbox; currently 'loopChatMessageAppended'. */
margin-top: -425px;
}
/* Apply crisp rendering for favicons at exactly 2dppx resolution */
@media (resolution: 2dppx) {
#social-sidebar-favico,

View File

@ -160,8 +160,7 @@
<parameter name="aTarget"/>
<body><![CDATA[
aTarget.setAttribute("label", this.contentDocument.title);
if (this.getAttribute("dark") == "true")
aTarget.setAttribute("dark", "true");
aTarget.src = this.src;
aTarget.content.setAttribute("origin", this.content.getAttribute("origin"));
aTarget.content.popupnotificationanchor.className = this.content.popupnotificationanchor.className;
@ -169,6 +168,16 @@
]]></body>
</method>
<method name="setDecorationAttributes">
<parameter name="aTarget"/>
<body><![CDATA[
for (let attr of ["dark", "customSize"]) {
if (this.hasAttribute(attr))
aTarget.setAttribute(attr, this.getAttribute(attr));
}
]]></body>
</method>
<method name="onTitlebarClick">
<parameter name="aEvent"/>
<body><![CDATA[
@ -211,6 +220,8 @@
let chatbar = win.document.getElementById("pinnedchats");
let origin = this.content.getAttribute("origin");
let cb = chatbar.openChat(origin, title, "about:blank");
this.setDecorationAttributes(cb);
cb.promiseChatLoaded.then(
() => {
this.swapDocShells(cb);
@ -225,6 +236,12 @@
chatbar.chatboxForURL.delete("about:blank");
chatbar.chatboxForURL.set(this.src, Cu.getWeakReference(cb));
let attachEvent = new cb.contentWindow.CustomEvent("socialFrameAttached", {
bubbles: true,
cancelable: true,
});
cb.contentDocument.dispatchEvent(attachEvent);
deferred.resolve(cb);
}
);
@ -457,8 +474,10 @@
// These are from the CSS for the chatbox and must be kept in sync.
// We can't use calcTotalWidthOf due to the transitions...
const CHAT_WIDTH_OPEN = 260;
const CHAT_WIDTH_OPEN_ALT = 300;
const CHAT_WIDTH_MINIMIZED = 160;
let openWidth = aChatbox.hasAttribute("large") ? 300 : CHAT_WIDTH_OPEN;
let openWidth = aChatbox.hasAttribute("customSize") ?
CHAT_WIDTH_OPEN_ALT : CHAT_WIDTH_OPEN;
return aChatbox.minimized ? CHAT_WIDTH_MINIMIZED : openWidth;
]]></body>
@ -684,18 +703,27 @@
if (event.target != otherWin.document)
return;
if (aChatbox.hasAttribute("customSize")) {
otherWin.document.getElementById("chat-window").
setAttribute("customSize", aChatbox.getAttribute("customSize"));
}
let document = aChatbox.contentDocument;
let detachEvent = new aChatbox.contentWindow.CustomEvent("socialFrameDetached", {
bubbles: true,
cancelable: true,
});
aChatbox.contentDocument.dispatchEvent(detachEvent);
otherWin.removeEventListener("load", _chatLoad, true);
let otherChatbox = otherWin.document.getElementById("chatter");
aChatbox.setDecorationAttributes(otherChatbox);
aChatbox.swapDocShells(otherChatbox);
aChatbox.close();
chatbar.chatboxForURL.set(aChatbox.src, Cu.getWeakReference(otherChatbox));
// All processing is done, now we can fire the event.
document.dispatchEvent(detachEvent);
deferred.resolve(otherChatbox);
}, true);
return deferred.promise;

View File

@ -269,10 +269,6 @@ html[dir="rtl"] .new-room-view > .context > .context-content > .context-preview
float: left;
}
.new-room-view > .context > .context-content > .context-preview[src=""] {
display: none;
}
.new-room-view > .context > .context-content > .context-description {
flex: 0 1 auto;
display: block;

View File

@ -29,7 +29,7 @@ loop.conversationViews = (function(mozL10n) {
if (!contact.email || contact.email.length === 0) {
return { value: "" };
}
return contact.email.find(e => e.pref) || contact.email[0];
return contact.email.find(function find(e) { return e.pref; }) || contact.email[0];
}
function _getContactDisplayName(contact) {

View File

@ -29,7 +29,7 @@ loop.conversationViews = (function(mozL10n) {
if (!contact.email || contact.email.length === 0) {
return { value: "" };
}
return contact.email.find(e => e.pref) || contact.email[0];
return contact.email.find(function find(e) { return e.pref; }) || contact.email[0];
}
function _getContactDisplayName(contact) {

View File

@ -500,9 +500,8 @@ loop.panel = (function(_, mozL10n) {
return (
React.createElement("div", {className: "room-entry-context-item"},
React.createElement("a", {href: roomUrl.location, onClick: this.handleClick},
React.createElement("img", {title: roomUrl.description,
src: roomUrl.thumbnail})
React.createElement("a", {href: roomUrl.location, title: roomUrl.description, onClick: this.handleClick},
React.createElement("img", {src: roomUrl.thumbnail || "loop/shared/img/icons-16x16.svg#globe"})
)
)
);
@ -766,6 +765,7 @@ loop.panel = (function(_, mozL10n) {
hide: !hostname ||
!this.props.mozLoop.getLoopPref("contextInConversations.enabled")
});
var thumbnail = this.state.previewImage || "loop/shared/img/icons-16x16.svg#globe";
return (
React.createElement("div", {className: "new-room-view"},
@ -773,7 +773,7 @@ loop.panel = (function(_, mozL10n) {
React.createElement(Checkbox, {label: mozL10n.get("context_inroom_label"),
onChange: this.onCheckboxChange}),
React.createElement("div", {className: "context-content"},
React.createElement("img", {className: "context-preview", src: this.state.previewImage}),
React.createElement("img", {className: "context-preview", src: thumbnail}),
React.createElement("span", {className: "context-description"},
this.state.description,
React.createElement("span", {className: "context-url"}, hostname)

View File

@ -500,9 +500,8 @@ loop.panel = (function(_, mozL10n) {
return (
<div className="room-entry-context-item">
<a href={roomUrl.location} onClick={this.handleClick}>
<img title={roomUrl.description}
src={roomUrl.thumbnail} />
<a href={roomUrl.location} title={roomUrl.description} onClick={this.handleClick}>
<img src={roomUrl.thumbnail || "loop/shared/img/icons-16x16.svg#globe"} />
</a>
</div>
);
@ -766,6 +765,7 @@ loop.panel = (function(_, mozL10n) {
hide: !hostname ||
!this.props.mozLoop.getLoopPref("contextInConversations.enabled")
});
var thumbnail = this.state.previewImage || "loop/shared/img/icons-16x16.svg#globe";
return (
<div className="new-room-view">
@ -773,7 +773,7 @@ loop.panel = (function(_, mozL10n) {
<Checkbox label={mozL10n.get("context_inroom_label")}
onChange={this.onCheckboxChange} />
<div className="context-content">
<img className="context-preview" src={this.state.previewImage}/>
<img className="context-preview" src={thumbnail} />
<span className="context-description">
{this.state.description}
<span className="context-url">{hostname}</span>

View File

@ -480,7 +480,7 @@ loop.roomViews = (function(mozL10n) {
}
var url = this._getURL();
var thumbnail = url && url.thumbnail || "";
var thumbnail = url && url.thumbnail || "loop/shared/img/icons-16x16.svg#globe";
var urlDescription = url && url.description || "";
var location = url && url.location || "";
var locationData = null;
@ -718,7 +718,6 @@ loop.roomViews = (function(mozL10n) {
return (
React.createElement("div", {className: "room-conversation-wrapper"},
React.createElement(sharedViews.TextChatView, {dispatcher: this.props.dispatcher}),
React.createElement(DesktopRoomInvitationView, {
dispatcher: this.props.dispatcher,
error: this.state.error,
@ -761,7 +760,8 @@ loop.roomViews = (function(mozL10n) {
savingContext: this.state.savingContext,
mozLoop: this.props.mozLoop,
roomData: roomData,
show: !shouldRenderInvitationOverlay && shouldRenderContextView})
show: !shouldRenderInvitationOverlay && shouldRenderContextView}),
React.createElement(sharedViews.TextChatView, {dispatcher: this.props.dispatcher})
)
);
}

View File

@ -480,7 +480,7 @@ loop.roomViews = (function(mozL10n) {
}
var url = this._getURL();
var thumbnail = url && url.thumbnail || "";
var thumbnail = url && url.thumbnail || "loop/shared/img/icons-16x16.svg#globe";
var urlDescription = url && url.description || "";
var location = url && url.location || "";
var locationData = null;
@ -553,7 +553,7 @@ loop.roomViews = (function(mozL10n) {
<div className="room-context-label">{mozL10n.get("context_inroom_label")}</div>
<div className="room-context-content"
onClick={this.handleContextClick}>
<img className="room-context-thumbnail" src={thumbnail}/>
<img className="room-context-thumbnail" src={thumbnail} />
<div className="room-context-description"
title={urlDescription}>
{this._truncate(urlDescription)}
@ -718,7 +718,6 @@ loop.roomViews = (function(mozL10n) {
return (
<div className="room-conversation-wrapper">
<sharedViews.TextChatView dispatcher={this.props.dispatcher} />
<DesktopRoomInvitationView
dispatcher={this.props.dispatcher}
error={this.state.error}
@ -762,6 +761,7 @@ loop.roomViews = (function(mozL10n) {
mozLoop={this.props.mozLoop}
roomData={roomData}
show={!shouldRenderInvitationOverlay && shouldRenderContextView} />
<sharedViews.TextChatView dispatcher={this.props.dispatcher} />
</div>
);
}

View File

@ -651,27 +651,6 @@ html, .fx-embedded, #main,
height: 100%;
}
/**
* The .fx-embbeded .text-chat-* styles are very temporarily whilst we work on
* text chat (bug 1108892 and dependencies).
*/
.fx-embedded .text-chat-view {
height: 60px;
color: white;
background-color: black;
}
.fx-embedded .text-chat-entries {
/* XXX Should use flex, this is just for the initial implementation. */
height: calc(100% - 2em);
width: 100%;
}
.fx-embedded .text-chat-box {
width: 100%;
margin: auto;
}
/* We use 641px rather than 640, as min-width and max-width are inclusive */
@media screen and (min-width:641px) {
.standalone .conversation-toolbar {
@ -681,10 +660,6 @@ html, .fx-embedded, #main,
right: 0;
}
.fx-embedded .local-stream {
position: fixed;
}
.standalone .local-stream,
.standalone .remote-inset-stream {
position: absolute;
@ -697,7 +672,7 @@ html, .fx-embedded, #main,
}
/* Nested video elements */
.conversation .media.nested {
.standalone .conversation .media.nested {
position: relative;
height: 100%;
}
@ -744,7 +719,7 @@ html, .fx-embedded, #main,
}
/* Nested video elements */
.conversation .media.nested {
.standalone .conversation .media.nested {
display: flex;
flex-direction: column;
align-items: center;
@ -969,10 +944,6 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
flex: 0 1 auto;
}
.room-context-thumbnail[src=""] {
display: none;
}
.room-context > .error-display-area.error {
display: block;
background-color: rgba(215,67,69,.8);
@ -1211,9 +1182,8 @@ html[dir="rtl"] .room-context-btn-edit {
.standalone-context-url > img {
margin: 1em auto;
max-width: 50%;
/* allows 20% for the description wrapper plus the margins */
max-height: calc(80% - 2em);
width: 16px;
height: 16px;
}
.standalone-context-url-description-wrapper {
@ -1244,18 +1214,64 @@ html[dir="rtl"] .room-context-btn-edit {
height: auto;
}
/* Text chat in rooms styles */
.fx-embedded .room-conversation-wrapper {
display: flex;
flex-flow: column nowrap;
}
.fx-embedded .video-layout-wrapper {
flex: 1 1 auto;
}
.text-chat-view {
background: #fff;
}
.fx-embedded .text-chat-view {
flex: 1 0 auto;
display: flex;
flex-flow: column nowrap;
}
.fx-embedded .text-chat-entries {
flex: 1 1 auto;
max-height: 120px;
min-height: 60px;
padding: .7em .5em 0;
}
.fx-embedded .text-chat-box {
flex: 0 0 auto;
max-height: 40px;
min-height: 40px;
width: 100%;
}
.text-chat-entries {
margin: auto;
overflow: scroll;
border: 1px solid red;
}
.text-chat-entry {
text-align: left;
text-align: end;
margin-bottom: 1.5em;
}
.text-chat-entry > span {
border-width: 1px;
border-style: solid;
border-color: #0095dd;
border-radius: 10000px;
padding: .5em 1em;
}
.text-chat-entry.received {
text-align: right;
text-align: start;
}
.text-chat-entry.received > span {
border-color: #d8d8d8;
}
.text-chat-box {
@ -1264,6 +1280,14 @@ html[dir="rtl"] .room-context-btn-edit {
.text-chat-box > form > input {
width: 100%;
height: 40px;
padding: 0 .5em .5em;
font-size: 1.1em;
}
.fx-embedded .text-chat-box > form > input {
border: 0;
border-top: 1px solid #999;
}
@media screen and (max-width:640px) {

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 73 KiB

View File

@ -255,6 +255,10 @@ loop.shared.mixins = (function() {
*/
var DocumentVisibilityMixin = {
_onDocumentVisibilityChanged: function(event) {
if (!this.isMounted()) {
return;
}
var hidden = event.target.hidden;
if (hidden && typeof this.onDocumentHidden === "function") {
this.onDocumentHidden();
@ -267,6 +271,9 @@ loop.shared.mixins = (function() {
componentDidMount: function() {
rootObject.document.addEventListener(
"visibilitychange", this._onDocumentVisibilityChanged);
// Assume that the consumer components is only mounted when the document
// has become visible.
this._onDocumentVisibilityChanged({ target: rootObject.document });
},
componentWillUnmount: function() {

View File

@ -68,6 +68,27 @@ loop.store.TextChatStore = (function() {
*/
dataChannelsAvailable: function() {
this.setStoreState({ textChatEnabled: true });
window.dispatchEvent(new CustomEvent("LoopChatEnabled"));
},
/**
* Appends a message to the store, which may be of type 'sent' or 'received'.
*
* @param {String} type
* @param {sharedActions.ReceivedTextChatMessage|sharedActions.SendTextChatMessage} actionData
*/
_appendTextChatMessage: function(type, actionData) {
// We create a new list to avoid updating the store's state directly,
// which confuses the views.
var message = {
type: type,
contentType: actionData.contentType,
message: actionData.message
};
var newList = this._storeState.messageList.concat(message);
this.setStoreState({ messageList: newList });
window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
},
/**
@ -81,14 +102,8 @@ loop.store.TextChatStore = (function() {
if (actionData.contentType != CHAT_CONTENT_TYPES.TEXT) {
return;
}
// We create a new list to avoid updating the store's state directly,
// which confuses the views.
var newList = this._storeState.messageList.concat({
type: CHAT_MESSAGE_TYPES.RECEIVED,
contentType: actionData.contentType,
message: actionData.message
});
this.setStoreState({ messageList: newList });
this._appendTextChatMessage(CHAT_MESSAGE_TYPES.RECEIVED, actionData);
},
/**
@ -97,15 +112,8 @@ loop.store.TextChatStore = (function() {
* @param {sharedActions.SendTextChatMessage} actionData
*/
sendTextChatMessage: function(actionData) {
// We create a new list to avoid updating the store's state directly,
// which confuses the views.
var newList = this._storeState.messageList.concat({
type: CHAT_MESSAGE_TYPES.SENT,
contentType: actionData.contentType,
message: actionData.message
});
this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SENT, actionData);
this._sdkDriver.sendTextChatMessage(actionData);
this.setStoreState({ messageList: newList });
}
});

View File

@ -29,7 +29,7 @@ loop.shared.views.TextChatView = (function(mozl10n) {
return (
React.createElement("div", {className: classes},
this.props.message
React.createElement("span", null, this.props.message)
)
);
}
@ -49,6 +49,9 @@ loop.shared.views.TextChatView = (function(mozl10n) {
componentWillUpdate: function() {
var node = this.getDOMNode();
if (!node) {
return;
}
// Scroll only if we're right at the bottom of the display.
this.shouldScroll = node.scrollHeight === node.scrollTop + node.clientHeight;
},
@ -64,17 +67,23 @@ loop.shared.views.TextChatView = (function(mozl10n) {
},
render: function() {
if (!this.props.messageList.length) {
return null;
}
return (
React.createElement("div", {className: "text-chat-entries"},
this.props.messageList.map(function(entry, i) {
return (
React.createElement(TextChatEntry, {key: i,
message: entry.message,
type: entry.type})
);
}, this)
React.createElement("div", {className: "text-chat-scroller"},
this.props.messageList.map(function(entry, i) {
return (
React.createElement(TextChatEntry, {key: i,
message: entry.message,
type: entry.type})
);
}, this)
)
)
);
}
@ -134,12 +143,15 @@ loop.shared.views.TextChatView = (function(mozl10n) {
return null;
}
var messageList = this.state.messageList;
return (
React.createElement("div", {className: "text-chat-view"},
React.createElement(TextChatEntriesView, {messageList: this.state.messageList}),
React.createElement(TextChatEntriesView, {messageList: messageList}),
React.createElement("div", {className: "text-chat-box"},
React.createElement("form", {onSubmit: this.handleFormSubmit},
React.createElement("input", {type: "text",
placeholder: messageList.length ? "" : mozl10n.get("chat_textbox_placeholder"),
onKeyDown: this.handleKeyDown,
valueLink: this.linkState("messageDetail")})
)

View File

@ -29,7 +29,7 @@ loop.shared.views.TextChatView = (function(mozl10n) {
return (
<div className={classes}>
{this.props.message}
<span>{this.props.message}</span>
</div>
);
}
@ -49,6 +49,9 @@ loop.shared.views.TextChatView = (function(mozl10n) {
componentWillUpdate: function() {
var node = this.getDOMNode();
if (!node) {
return;
}
// Scroll only if we're right at the bottom of the display.
this.shouldScroll = node.scrollHeight === node.scrollTop + node.clientHeight;
},
@ -64,17 +67,23 @@ loop.shared.views.TextChatView = (function(mozl10n) {
},
render: function() {
if (!this.props.messageList.length) {
return null;
}
return (
<div className="text-chat-entries">
{
this.props.messageList.map(function(entry, i) {
return (
<TextChatEntry key={i}
message={entry.message}
type={entry.type} />
);
}, this)
}
<div className="text-chat-scroller">
{
this.props.messageList.map(function(entry, i) {
return (
<TextChatEntry key={i}
message={entry.message}
type={entry.type} />
);
}, this)
}
</div>
</div>
);
}
@ -134,12 +143,15 @@ loop.shared.views.TextChatView = (function(mozl10n) {
return null;
}
var messageList = this.state.messageList;
return (
<div className="text-chat-view">
<TextChatEntriesView messageList={this.state.messageList} />
<TextChatEntriesView messageList={messageList} />
<div className="text-chat-box">
<form onSubmit={this.handleFormSubmit}>
<input type="text"
placeholder={messageList.length ? "" : mozl10n.get("chat_textbox_placeholder")}
onKeyDown={this.handleKeyDown}
valueLink={this.linkState("messageDetail")} />
</form>

View File

@ -853,27 +853,51 @@ let MozLoopServiceInternal = {
return;
}
chatbox.setAttribute("dark", true);
chatbox.setAttribute("large", true);
chatbox.addEventListener("DOMContentLoaded", function loaded(event) {
if (event.target != chatbox.contentDocument) {
return;
}
chatbox.removeEventListener("DOMContentLoaded", loaded, true);
let chatbar = chatbox.parentNode;
let window = chatbox.contentWindow;
function socialFrameChanged(eventName) {
UITour.availableTargetsCache.clear();
UITour.notify(eventName);
if (eventName == "Loop:ChatWindowDetached" || eventName == "Loop:ChatWindowAttached") {
// After detach, re-attach of the chatbox, refresh its reference so
// we can keep using it here.
let ref = chatbar.chatboxForURL.get(chatbox.src);
chatbox = ref && ref.get() || chatbox;
}
}
window.addEventListener("socialFrameHide", socialFrameChanged.bind(null, "Loop:ChatWindowHidden"));
window.addEventListener("socialFrameShow", socialFrameChanged.bind(null, "Loop:ChatWindowShown"));
window.addEventListener("socialFrameDetached", socialFrameChanged.bind(null, "Loop:ChatWindowDetached"));
window.addEventListener("socialFrameAttached", socialFrameChanged.bind(null, "Loop:ChatWindowAttached"));
window.addEventListener("unload", socialFrameChanged.bind(null, "Loop:ChatWindowClosed"));
const kSizeMap = {
LoopChatEnabled: "loopChatEnabled",
LoopChatMessageAppended: "loopChatMessageAppended"
};
function onChatEvent(event) {
// When the chat box or messages are shown, resize the panel or window
// to be slightly higher to accomodate them.
let customSize = kSizeMap[event.type];
if (customSize) {
chatbox.setAttribute("customSize", customSize);
chatbox.parentNode.setAttribute("customSize", customSize);
}
}
window.addEventListener("LoopChatEnabled", onChatEvent);
window.addEventListener("LoopChatMessageAppended", onChatEvent);
injectLoopAPI(window);
let ourID = window.QueryInterface(Ci.nsIInterfaceRequestor)
@ -918,8 +942,16 @@ let MozLoopServiceInternal = {
}.bind(this), true);
};
if (!Chat.open(null, origin, "", url, undefined, undefined, callback)) {
let chatbox = Chat.open(null, origin, "", url, undefined, undefined, callback);
if (!chatbox) {
return null;
// It's common for unit tests to overload Chat.open.
} else if (chatbox.setAttribute) {
// Set properties that influence visual appeara nce of the chatbox right
// away to circumvent glitches.
chatbox.setAttribute("dark", true);
chatbox.setAttribute("customSize", "loopDefault");
chatbox.parentNode.setAttribute("customSize", "loopDefault");
}
return windowId;
},

View File

@ -262,7 +262,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
return (
React.createElement("div", {className: classes},
React.createElement("img", {src: this.props.roomContextUrl.thumbnail}),
React.createElement("img", {src: this.props.roomContextUrl.thumbnail || "shared/img/icons-16x16.svg#globe"}),
React.createElement("div", {className: "standalone-context-url-description-wrapper"},
this.props.roomContextUrl.description,
React.createElement("br", null), React.createElement("a", {href: locationInfo.location,

View File

@ -262,7 +262,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
return (
<div className={classes}>
<img src={this.props.roomContextUrl.thumbnail} />
<img src={this.props.roomContextUrl.thumbnail || "shared/img/icons-16x16.svg#globe"} />
<div className="standalone-context-url-description-wrapper">
{this.props.roomContextUrl.description}
<br /><a href={locationInfo.location}

View File

@ -137,3 +137,7 @@ status_in_conversation=In conversation
status_conversation_ended=Conversation ended
status_error=Something went wrong
support_link=Get Help
# Text chat strings
chat_textbox_placeholder=Type here…

View File

@ -850,6 +850,24 @@ describe("loop.panel", function() {
expect(contextContent).to.not.equal(null);
});
it("should show a default favicon when none is available", function() {
fakeMozLoop.getSelectedTabMetadata = function (callback) {
callback({
url: "https://www.example.com",
description: "fake description",
previews: [""]
});
};
var view = createTestComponent();
// Simulate being visible
view.onDocumentVisible();
var previewImage = view.getDOMNode().querySelector(".context-preview");
expect(previewImage.src).to.match(/loop\/shared\/img\/icons-16x16.svg#globe$/);
});
it("should not show context information when a URL is unavailable", function() {
fakeMozLoop.getSelectedTabMetadata = function (callback) {
callback({

View File

@ -305,6 +305,19 @@ describe("loop.roomViews", function () {
expect(view.getDOMNode().querySelector(".room-context-url").textContent)
.eql("hostname");
});
it("should show a default favicon when none is available", function() {
fakeContextURL.thumbnail = null;
view = mountTestComponent({
showContext: true,
roomData: {
roomContextUrls: [fakeContextURL]
}
});
expect(view.getDOMNode().querySelector(".room-context-thumbnail").src)
.to.match(/loop\/shared\/img\/icons-16x16.svg#globe$/);
});
});
});

View File

@ -28,10 +28,22 @@ add_task(function* test_mozLoop_getSelectedTabMetadata() {
metadata = yield promiseGetMetadata();
Assert.strictEqual(metadata.url, null, "URL should be empty for about:home");
Assert.ok(metadata.favicon.startsWith("data:image/x-icon;base64,"),
"Favicon should be set for about:home");
Assert.strictEqual(metadata.favicon, null, "Favicon should be empty for about:home");
Assert.ok(metadata.title, "Title should be set for about:home");
Assert.deepEqual(metadata.previews, [], "No previews available for about:home");
gBrowser.removeTab(tab);
});
add_task(function* test_mozLoop_getSelectedTabMetadata_defaultIcon() {
let tab = gBrowser.selectedTab = gBrowser.addTab();
yield promiseTabLoadEvent(tab, "http://example.com/");
let metadata = yield promiseGetMetadata();
Assert.strictEqual(metadata.url, "http://example.com/", "URL should match");
Assert.strictEqual(metadata.favicon, null, "Favicon should be empty");
Assert.ok(metadata.title, "Title should be set");
Assert.deepEqual(metadata.previews, [], "No previews available");
gBrowser.removeTab(tab);
});

View File

@ -177,7 +177,8 @@ describe("loop.shared.mixins", function() {
comp = TestUtils.renderIntoDocument(React.createElement(TestComp));
sinon.assert.calledOnce(onDocumentVisibleStub);
// Twice, because it's also called when the component was mounted.
sinon.assert.calledTwice(onDocumentVisibleStub);
});
it("should call onDocumentVisible when document visibility changes to hidden",

View File

@ -25,6 +25,11 @@ describe("loop.store.TextChatStore", function () {
store = new loop.store.TextChatStore(dispatcher, {
sdkDriver: fakeSdkDriver
});
sandbox.stub(window, "dispatchEvent");
sandbox.stub(window, "CustomEvent", function(name) {
this.name = name;
});
});
afterEach(function() {
@ -37,6 +42,14 @@ describe("loop.store.TextChatStore", function () {
expect(store.getStoreState("textChatEnabled")).eql(true);
});
it("should dispatch a LoopChatEnabled event", function() {
store.dataChannelsAvailable();
sinon.assert.calledOnce(window.dispatchEvent);
sinon.assert.calledWithExactly(window.dispatchEvent,
new CustomEvent("LoopChatEnabled"));
});
});
describe("#receivedTextChatMessage", function() {
@ -63,6 +76,17 @@ describe("loop.store.TextChatStore", function () {
expect(store.getStoreState("messageList").length).eql(0);
});
it("should dispatch a LoopChatMessageAppended event", function() {
store.receivedTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!"
});
sinon.assert.calledOnce(window.dispatchEvent);
sinon.assert.calledWithExactly(window.dispatchEvent,
new CustomEvent("LoopChatMessageAppended"));
});
});
describe("#sendTextChatMessage", function() {
@ -92,5 +116,16 @@ describe("loop.store.TextChatStore", function () {
message: messageData.message
}]);
});
it("should dipatch a LoopChatMessageAppended event", function() {
store.sendTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!"
});
sinon.assert.calledOnce(window.dispatchEvent);
sinon.assert.calledWithExactly(window.dispatchEvent,
new CustomEvent("LoopChatMessageAppended"));
});
});
});

View File

@ -80,5 +80,32 @@ describe("loop.shared.views.TextChatView", function () {
message: "Hello!"
}));
});
it("should not render message entries when none are sent/ received yet", function() {
view = mountTestComponent();
expect(view.getDOMNode().querySelector(".text-chat-entries")).to.eql(null);
});
it("should render message entries when message were sent/ received", function() {
view = mountTestComponent();
store.receivedTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!"
});
store.sendTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Is it me you're looking for?"
});
var node = view.getDOMNode();
expect(node.querySelector(".text-chat-entries")).to.not.eql(null);
var entries = node.querySelectorAll(".text-chat-entry");
expect(entries.length).to.eql(2);
expect(entries[0].classList.contains("received")).to.eql(true);
expect(entries[1].classList.contains("received")).to.not.eql(true);
});
});
});

View File

@ -149,6 +149,20 @@ describe("loop.standaloneRoomViews", function() {
linkInfo: "Shared URL"
}));
});
it("should display the default favicon when no thumbnail is available", function() {
var view = mountTestComponent({
roomName: "Mike's room",
roomContextUrls: [{
description: "Mark's super page",
location: "http://invalid.com",
thumbnail: ""
}]
});
expect(view.getDOMNode().querySelector(".standalone-context-url > img").src)
.to.match(/shared\/img\/icons-16x16.svg#globe$/);
});
});
describe("StandaloneRoomHeader", function() {

View File

@ -148,10 +148,6 @@ body {
.svg-icon {
display: inline-block;
width: 16px;
height: 16px;
margin-left: .5rem;
background-repeat: no-repeat;
background-size: 16px 16px;
background-position: center;
border: 0;
}

View File

@ -300,13 +300,12 @@
var SVGIcon = React.createClass({displayName: "SVGIcon",
render: function() {
var sizeUnit = this.props.size.split("x")[0] + "px";
var sizeUnit = this.props.size.split("x");
return (
React.createElement("span", {className: "svg-icon", style: {
"backgroundImage": "url(../content/shared/img/icons-" + this.props.size +
".svg#" + this.props.shapeId + ")",
"backgroundSize": sizeUnit + " " + sizeUnit
}})
React.createElement("img", {className: "svg-icon",
src: "../content/shared/img/icons-" + this.props.size + ".svg#" + this.props.shapeId,
width: sizeUnit[0],
height: sizeUnit[1]})
);
}
});
@ -328,7 +327,7 @@
],
"16x16": ["add", "add-hover", "add-active", "audio", "audio-hover", "audio-active",
"block", "block-red", "block-hover", "block-active", "contacts", "contacts-hover",
"contacts-active", "copy", "checkmark", "delete", "google", "google-hover",
"contacts-active", "copy", "checkmark", "delete", "globe", "google", "google-hover",
"google-active", "history", "history-hover", "history-active", "leave",
"precall", "precall-hover", "precall-active", "screen-white", "screenmute-white",
"settings", "settings-hover", "settings-active", "share-darkgrey", "tag",

View File

@ -300,13 +300,12 @@
var SVGIcon = React.createClass({
render: function() {
var sizeUnit = this.props.size.split("x")[0] + "px";
var sizeUnit = this.props.size.split("x");
return (
<span className="svg-icon" style={{
"backgroundImage": "url(../content/shared/img/icons-" + this.props.size +
".svg#" + this.props.shapeId + ")",
"backgroundSize": sizeUnit + " " + sizeUnit
}} />
<img className="svg-icon"
src={"../content/shared/img/icons-" + this.props.size + ".svg#" + this.props.shapeId}
width={sizeUnit[0]}
height={sizeUnit[1]} />
);
}
});
@ -328,7 +327,7 @@
],
"16x16": ["add", "add-hover", "add-active", "audio", "audio-hover", "audio-active",
"block", "block-red", "block-hover", "block-active", "contacts", "contacts-hover",
"contacts-active", "copy", "checkmark", "delete", "google", "google-hover",
"contacts-active", "copy", "checkmark", "delete", "globe", "google", "google-hover",
"google-active", "history", "history-hover", "history-active", "leave",
"precall", "precall-hover", "precall-active", "screen-white", "screenmute-white",
"settings", "settings-hover", "settings-active", "share-darkgrey", "tag",

View File

@ -193,6 +193,10 @@ let NetMonitorView = {
* @return string (e.g, "network-inspector-view" or "network-statistics-view")
*/
get currentFrontendMode() {
// The getter may be called from a timeout after the panel is destroyed.
if (!this._body.selectedPanel) {
return null;
}
return this._body.selectedPanel.id;
},

View File

@ -21,6 +21,7 @@ support-files =
html_post-raw-test-page.html
html_post-raw-with-headers-test-page.html
html_simple-test-page.html
html_send-beacon.html
html_sorting-test-page.html
html_statistics-test-page.html
html_status-codes-test-page.html
@ -99,6 +100,8 @@ skip-if = e10s # Bug 1091612
[browser_net_security-tab-deselect.js]
[browser_net_security-tab-visibility.js]
[browser_net_security-warnings.js]
[browser_net_send-beacon.js]
[browser_net_send-beacon-other-tab.js]
[browser_net_simple-init.js]
[browser_net_simple-request-data.js]
[browser_net_simple-request-details.js]

View File

@ -0,0 +1,31 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if beacons from other tabs are properly ignored.
*/
let test = Task.async(function*() {
let [, debuggee, monitor] = yield initNetMonitor(SIMPLE_URL);
let { RequestsMenu } = monitor.panelWin.NetMonitorView;
RequestsMenu.lazyUpdate = false;
let tab = yield addTab(SEND_BEACON_URL);
let beaconDebuggee = tab.linkedBrowser.contentWindow.wrappedJSObject;
info("Beacon tab added successfully.");
is(RequestsMenu.itemCount, 0, "The requests menu should be empty.");
beaconDebuggee.performRequest();
debuggee.location.reload();
yield waitForNetworkEvents(monitor, 1);
is(RequestsMenu.itemCount, 1, "Only the reload should be recorded.");
let request = RequestsMenu.getItemAtIndex(0);
is(request.attachment.method, "GET", "The method is correct.");
is(request.attachment.status, "200", "The status is correct.");
yield teardown(monitor);
removeTab(tab);
finish();
});

View File

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if beacons are handled correctly.
*/
let test = Task.async(function*() {
let [, debuggee, monitor] = yield initNetMonitor(SEND_BEACON_URL);
let { RequestsMenu } = monitor.panelWin.NetMonitorView;
RequestsMenu.lazyUpdate = false;
is(RequestsMenu.itemCount, 0, "The requests menu should be empty.");
debuggee.performRequest();
yield waitForNetworkEvents(monitor, 1);
is(RequestsMenu.itemCount, 1, "The beacon should be recorded.");
let request = RequestsMenu.getItemAtIndex(0);
is(request.attachment.method, "POST", "The method is correct.");
ok(request.attachment.url.endsWith("beacon_request"), "The URL is correct.");
is(request.attachment.status, "404", "The status is correct.");
yield teardown(monitor);
finish();
});

View File

@ -40,6 +40,7 @@ const SINGLE_GET_URL = EXAMPLE_URL + "html_single-get-page.html";
const STATISTICS_URL = EXAMPLE_URL + "html_statistics-test-page.html";
const CURL_URL = EXAMPLE_URL + "html_copy-as-curl.html";
const CURL_UTILS_URL = EXAMPLE_URL + "html_curl-utils.html";
const SEND_BEACON_URL = EXAMPLE_URL + "html_send-beacon.html";
const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";

View File

@ -0,0 +1,23 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>
<body>
<p>Send beacon test</p>
<script type="text/javascript">
function performRequest() {
navigator.sendBeacon("beacon_request");
}
</script>
</body>
</html>

View File

@ -116,6 +116,7 @@ support-files =
[browser_perf_recordings-io-02.js]
[browser_perf_recordings-io-03.js]
[browser_perf_recordings-io-04.js]
[browser_perf_recordings-io-05.js]
[browser_perf-range-changed-render.js]
[browser_perf-recording-selected-01.js]
[browser_perf-recording-selected-02.js]

View File

@ -29,7 +29,6 @@ let test = Task.async(function*() {
yield DetailsView.selectView("memory-calltree");
yield DetailsView.selectView("memory-flamegraph");
// Verify original recording.
let originalData = PerformanceController.getCurrentRecording().getAllData();
@ -81,25 +80,6 @@ let test = Task.async(function*() {
is(importedData.configuration.withMemory, originalData.configuration.withMemory,
"The imported data is identical to the original data (9).");
yield teardown(panel);
// Test that when importing and no graphs rendered yet,
// we do not get a getMappedSelection error
// bug 1160828
var { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
var { EVENTS, PerformanceController, DetailsView, DetailsSubview, OverviewView, WaterfallView } = panel.panelWin;
yield PerformanceController.clearRecordings();
rerendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
imported = once(PerformanceController, EVENTS.RECORDING_IMPORTED);
yield PerformanceController.importRecording("", file);
yield imported;
ok(true, "The recording data appears to have been successfully imported.");
yield rerendered;
ok(true, "The imported data was re-rendered.");
yield teardown(panel);
finish();
});

View File

@ -0,0 +1,41 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that when importing and no graphs rendered yet, we do not get a
* `getMappedSelection` error.
*/
let test = Task.async(function*() {
var { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
var { EVENTS, PerformanceController, WaterfallView } = panel.panelWin;
yield startRecording(panel);
yield stopRecording(panel);
// Save recording.
let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
let exported = once(PerformanceController, EVENTS.RECORDING_EXPORTED);
yield PerformanceController.exportRecording("", PerformanceController.getCurrentRecording(), file);
yield exported;
ok(true, "The recording data appears to have been successfully saved.");
// Clear and re-import.
yield PerformanceController.clearRecordings();
let rendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
let imported = once(PerformanceController, EVENTS.RECORDING_IMPORTED);
yield PerformanceController.importRecording("", file);
yield imported;
yield rendered;
ok(true, "No error was thrown.");
yield teardown(panel);
finish();
});

View File

@ -65,10 +65,8 @@ function* spawnTest() {
is(afterResizeBarsCount, beforeResizeBarsCount,
"The same subset of the total markers remained visible.");
// Temporarily disable the following assertion; intermittent failures.
// Bug 1169352.
// is(Array.indexOf($$(".waterfall-tree-item"), $(".waterfall-tree-item:focus")), 2,
// "The correct item is still focused in the tree.");
is(Array.indexOf($$(".waterfall-tree-item"), $(".waterfall-tree-item:focus")), 2,
"The correct item is still focused in the tree.");
ok(!$("#waterfall-details").hidden,
"The waterfall sidebar is still visible.");

View File

@ -11,6 +11,7 @@ skip-if = buildapp == 'mulet'
[browser_projecteditor_confirm_unsaved.js]
[browser_projecteditor_contextmenu_01.js]
[browser_projecteditor_contextmenu_02.js]
skip-if = true # Bug 1173950
[browser_projecteditor_delete_file.js]
skip-if = e10s # Frequent failures in e10s - Bug 1020027
[browser_projecteditor_rename_file.js]
@ -23,6 +24,7 @@ skip-if = buildapp == 'mulet'
[browser_projecteditor_init.js]
[browser_projecteditor_menubar_01.js]
[browser_projecteditor_menubar_02.js]
skip-if = true # Bug 1173950
[browser_projecteditor_new_file.js]
[browser_projecteditor_stores.js]
[browser_projecteditor_tree_selection_01.js]

View File

@ -63,10 +63,10 @@ share_email_subject5={{clientShortname2}} — Join the conversation
share_email_subject_context={{clientShortname2}} conversation: {{title}}
## LOCALIZATION NOTE (share_email_body4): In this item, don't translate the
## part between {{..}} and leave the \n\n part alone
share_email_body5=Hello!\n\nJoin me for a video conversation on {{clientShortname2}}.\n\nIt's the easiest way to connect by video with anyone anywhere. With {{clientSuperShortname}}, you don't have to download or install anything. Just click or paste this link into your {{brandShortname}}, Opera, or Chrome browser to join the conversation:\n\n{{callUrl}}\n\nIf you'd like to learn more about {{clientSuperShortname}} and how you can start your own free video conversations, visit {{learnMoreUrl}}\n\nTalk to you soon!
share_email_body5=Hello!\n\nJoin me for a video conversation on {{clientShortname2}}.\n\nIt's the easiest way to connect by video with anyone anywhere. With {{clientSuperShortname}}, you don't have to download or install anything. Just click or paste this link into your {{brandShortname}}, Opera or Chrome browser to join the conversation:\n\n{{callUrl}}\n\nIf you'd like to learn more about {{clientSuperShortname}} and how you can start your own free video conversations, visit {{learnMoreUrl}}\n\nTalk to you soon!
## LOCALIZATION NOTE (share_email_body_context): In this item, don't translate
## the part between {{..}} and leave the \n\n part alone.
share_email_body_context=Hello!\n\nJoin me for a video conversation on {{clientShortname2}} about:\n{{title}}.\n\nIt's the easiest way to connect by video with anyone anywhere. With {{clientSuperShortname}}, you don't have to download or install anything. Just click or paste this link into your {{brandShortname}}, Opera, or Chrome browser to join the conversation:\n\n{{callUrl}}\n\nIf you'd like to learn more about {{clientSuperShortname}} and how you can start your own free video conversations, visit {{learnMoreUrl}}\n\nTalk to you soon!
share_email_body_context=Hello!\n\nJoin me for a video conversation on {{clientShortname2}} about:\n{{title}}.\n\nIt's the easiest way to connect by video with anyone anywhere. With {{clientSuperShortname}}, you don't have to download or install anything. Just click or paste this link into your {{brandShortname}}, Opera or Chrome browser to join the conversation:\n\n{{callUrl}}\n\nIf you'd like to learn more about {{clientSuperShortname}} and how you can start your own free video conversations, visit {{learnMoreUrl}}\n\nTalk to you soon!
## LOCALIZATION NOTE (share_tweeet): In this item, don't translate the part
## between {{..}}. Please keep the text below 117 characters to make sure it fits
## in a tweet.
@ -354,3 +354,7 @@ context_show_tooltip=Show Context
context_save_label2=Save
context_link_modified=This link was modified.
context_learn_more_link_label=Learn more.
# Text chat strings
chat_textbox_placeholder=Type here…

View File

@ -195,7 +195,6 @@ chatbox[dark=true] > .chat-titlebar > hbox > .chat-title {
.chatbar-innerbox {
background: transparent;
margin: -285px 0 0;
overflow: hidden;
}

View File

@ -1075,7 +1075,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
list-style-image: url(chrome://browser/skin/undoCloseTab.png);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#alltabs_undoCloseTab {
list-style-image: url(chrome://browser/skin/undoCloseTab@2x.png);
}
@ -1508,16 +1508,19 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
padding: 0 3px;
}
@media (-moz-os-version: windows-xp),
@media not all and (-moz-os-version: windows-vista),
not all and (-moz-windows-default-theme) {
richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-box > .ac-action-icon {
-moz-image-region: rect(11px, 16px, 22px, 0);
}
@media not all and (-moz-os-version: windows-win7),
not all and (-moz-windows-default-theme) {
richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-box > .ac-action-icon {
-moz-image-region: rect(11px, 16px, 22px, 0);
}
.ac-comment[selected="true"],
.ac-url-text[selected="true"],
.ac-action-text[selected="true"] {
color: inherit !important;
.ac-comment[selected="true"],
.ac-url-text[selected="true"],
.ac-action-text[selected="true"] {
color: inherit !important;
}
}
}
@ -1892,7 +1895,7 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
%include ../shared/tabs.inc.css
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
/* image preloading hack from shared/tabs.inc.css */
#tabbrowser-tabs::before {
background-image:
@ -1934,8 +1937,17 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
}
}
#TabsToolbar[brighttext] .tab-close-button:not(:hover):not([visuallyselected="true"]) {
-moz-image-region: rect(0, 64px, 16px, 48px) !important;
/* Invert the unhovered close tab icons on bright-text tabs */
@media not all and (min-resolution: 1.1dppx) {
#TabsToolbar[brighttext] .tab-close-button:not(:hover):not([visuallyselected="true"]) {
-moz-image-region: rect(0, 64px, 16px, 48px) !important;
}
}
@media (min-resolution: 1.1dppx) {
#TabsToolbar[brighttext] .tab-close-button:not(:hover):not([visuallyselected="true"]) {
-moz-image-region: rect(0, 128px, 32px, 96px) !important;
}
}
/* tabbrowser-tab focus ring */

View File

@ -1642,13 +1642,15 @@ BluetoothGattManager::GetCharacteristicNotification(
aCharId,
new DiscoverResultHandler(client));
} else { // all characteristics of this service are discovered
// Notify BluetoothGattService to create characteristics then proceed
nsString path;
GeneratePathFromGattId(aServiceId.mId, path);
// Notify BluetoothGatt to make BluetoothGattService create characteristics
// then proceed
nsTArray<BluetoothNamedValue> values;
BT_APPEND_NAMED_VALUE(values, "serviceId", aServiceId);
BT_APPEND_NAMED_VALUE(values, "characteristics", client->mCharacteristics);
bs->DistributeSignal(NS_LITERAL_STRING("CharacteristicsDiscovered"),
path,
BluetoothValue(client->mCharacteristics));
client->mAppUuid,
BluetoothValue(values));
ProceedDiscoverProcess(client, aServiceId);
}
@ -1686,13 +1688,16 @@ BluetoothGattManager::GetDescriptorNotification(
aDescriptorId,
new DiscoverResultHandler(client));
} else { // all descriptors of this characteristic are discovered
// Notify BluetoothGattCharacteristic to create descriptors then proceed
nsString path;
GeneratePathFromGattId(aCharId, path);
// Notify BluetoothGatt to make BluetoothGattCharacteristic create
// descriptors then proceed
nsTArray<BluetoothNamedValue> values;
BT_APPEND_NAMED_VALUE(values, "serviceId", aServiceId);
BT_APPEND_NAMED_VALUE(values, "characteristicId", aCharId);
BT_APPEND_NAMED_VALUE(values, "descriptors", client->mDescriptors);
bs->DistributeSignal(NS_LITERAL_STRING("DescriptorsDiscovered"),
path,
BluetoothValue(client->mDescriptors));
client->mAppUuid,
BluetoothValue(values));
client->mDescriptors.Clear();
ProceedDiscoverProcess(client, aServiceId);
@ -1729,13 +1734,16 @@ BluetoothGattManager::GetIncludedServiceNotification(
aIncludedServId,
new DiscoverResultHandler(client));
} else { // all included services of this service are discovered
// Notify BluetoothGattService to create included services
nsString path;
GeneratePathFromGattId(aServiceId.mId, path);
// Notify BluetoothGatt to make BluetoothGattService create included
// services
nsTArray<BluetoothNamedValue> values;
BT_APPEND_NAMED_VALUE(values, "serviceId", aServiceId);
BT_APPEND_NAMED_VALUE(values, "includedServices",
client->mIncludedServices);
bs->DistributeSignal(NS_LITERAL_STRING("IncludedServicesDiscovered"),
path,
BluetoothValue(client->mIncludedServices));
client->mAppUuid,
BluetoothValue(values));
client->mIncludedServices.Clear();
// Start to discover characteristics of this service

View File

@ -281,6 +281,77 @@ BluetoothGatt::HandleServicesDiscovered(const BluetoothValue& aValue)
BluetoothGattBinding::ClearCachedServicesValue(this);
}
void
BluetoothGatt::HandleIncludedServicesDiscovered(const BluetoothValue& aValue)
{
MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
const InfallibleTArray<BluetoothNamedValue>& values =
aValue.get_ArrayOfBluetoothNamedValue();
MOZ_ASSERT(values.Length() == 2); // ServiceId, IncludedServices
MOZ_ASSERT(values[0].name().EqualsLiteral("serviceId"));
MOZ_ASSERT(values[0].value().type() ==
BluetoothValue::TBluetoothGattServiceId);
MOZ_ASSERT(values[1].name().EqualsLiteral("includedServices"));
MOZ_ASSERT(values[1].value().type() ==
BluetoothValue::TArrayOfBluetoothGattServiceId);
size_t index = mServices.IndexOf(
values[0].value().get_BluetoothGattServiceId());
NS_ENSURE_TRUE_VOID(index != mServices.NoIndex);
nsRefPtr<BluetoothGattService> service = mServices.ElementAt(index);
service->AssignIncludedServices(
values[1].value().get_ArrayOfBluetoothGattServiceId());
}
void
BluetoothGatt::HandleCharacteristicsDiscovered(const BluetoothValue& aValue)
{
MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
const InfallibleTArray<BluetoothNamedValue>& values =
aValue.get_ArrayOfBluetoothNamedValue();
MOZ_ASSERT(values.Length() == 2); // ServiceId, Characteristics
MOZ_ASSERT(values[0].name().EqualsLiteral("serviceId"));
MOZ_ASSERT(values[0].value().type() == BluetoothValue::TBluetoothGattServiceId);
MOZ_ASSERT(values[1].name().EqualsLiteral("characteristics"));
MOZ_ASSERT(values[1].value().type() ==
BluetoothValue::TArrayOfBluetoothGattCharAttribute);
size_t index = mServices.IndexOf(
values[0].value().get_BluetoothGattServiceId());
NS_ENSURE_TRUE_VOID(index != mServices.NoIndex);
nsRefPtr<BluetoothGattService> service = mServices.ElementAt(index);
service->AssignCharacteristics(
values[1].value().get_ArrayOfBluetoothGattCharAttribute());
}
void
BluetoothGatt::HandleDescriptorsDiscovered(const BluetoothValue& aValue)
{
MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
const InfallibleTArray<BluetoothNamedValue>& values =
aValue.get_ArrayOfBluetoothNamedValue();
MOZ_ASSERT(values.Length() == 3); // ServiceId, CharacteristicId, Descriptors
MOZ_ASSERT(values[0].name().EqualsLiteral("serviceId"));
MOZ_ASSERT(values[0].value().type() == BluetoothValue::TBluetoothGattServiceId);
MOZ_ASSERT(values[1].name().EqualsLiteral("characteristicId"));
MOZ_ASSERT(values[1].value().type() == BluetoothValue::TBluetoothGattId);
MOZ_ASSERT(values[2].name().EqualsLiteral("descriptors"));
MOZ_ASSERT(values[2].value().type() == BluetoothValue::TArrayOfBluetoothGattId);
size_t index = mServices.IndexOf(
values[0].value().get_BluetoothGattServiceId());
NS_ENSURE_TRUE_VOID(index != mServices.NoIndex);
nsRefPtr<BluetoothGattService> service = mServices.ElementAt(index);
service->AssignDescriptors(values[1].value().get_BluetoothGattId(),
values[2].value().get_ArrayOfBluetoothGattId());
}
void
BluetoothGatt::HandleCharacteristicChanged(const BluetoothValue& aValue)
{
@ -348,6 +419,12 @@ BluetoothGatt::Notify(const BluetoothSignal& aData)
}
mDiscoveringServices = false;
} else if (aData.name().EqualsLiteral("IncludedServicesDiscovered")) {
HandleIncludedServicesDiscovered(v);
} else if (aData.name().EqualsLiteral("CharacteristicsDiscovered")) {
HandleCharacteristicsDiscovered(v);
} else if (aData.name().EqualsLiteral("DescriptorsDiscovered")) {
HandleDescriptorsDiscovered(v);
} else if (aData.name().EqualsLiteral(GATT_CHARACTERISTIC_CHANGED_ID)) {
HandleCharacteristicChanged(v);
} else {

View File

@ -96,6 +96,50 @@ private:
*/
void HandleServicesDiscovered(const BluetoothValue& aValue);
/**
* Add newly discovered GATT included services into mIncludedServices of
* BluetoothGattService and update the cache value of mIncludedServices.
*
* @param aValue [in] BluetoothValue which contains an array of
* BluetoothNamedValue. There are exact two elements in
* the array. The first element uses 'serviceId' as the
* name and uses BluetoothGattServiceId as the value. The
* second element uses 'includedServices' as the name and
* uses an array of BluetoothGattServiceId of all
* discovered included services as the value.
*/
void HandleIncludedServicesDiscovered(const BluetoothValue& aValue);
/**
* Add newly discovered GATT characteristics into mCharacteristics of
* BluetoothGattService and update the cache value of mCharacteristics.
*
* @param aValue [in] BluetoothValue which contains an array of
* BluetoothNamedValue. There are exact two elements in
* the array. The first element uses 'serviceId' as the
* name and uses BluetoothGattServiceId as the value. The
* second element uses 'characteristics' as the name and
* uses an array of BluetoothGattCharAttribute of all
* discovered characteristics as the value.
*/
void HandleCharacteristicsDiscovered(const BluetoothValue& aValue);
/**
* Add newly discovered GATT descriptors into mDescriptors of
* BluetoothGattCharacteristic and update the cache value of mDescriptors.
*
* @param aValue [in] BluetoothValue which contains an array of
* BluetoothNamedValue. There are exact three elements in
* the array. The first element uses 'serviceId' as the
* name and uses BluetoothGattServiceId as the value. The
* second element uses 'characteristicId' as the name and
* uses BluetoothGattId as the value. The third element
* uses 'descriptors' as the name and uses an array of
* BluetoothGattId of all discovered descriptors as the
* value.
*/
void HandleDescriptorsDiscovered(const BluetoothValue& aValue);
/**
* The value of a GATT characteristic has changed. In the mean time, the
* cached value of this GATT characteristic has already been updated. An

View File

@ -142,18 +142,13 @@ BluetoothGattCharacteristic::StopNotifications(ErrorResult& aRv)
}
void
BluetoothGattCharacteristic::HandleDescriptorsDiscovered(
const BluetoothValue& aValue)
BluetoothGattCharacteristic::AssignDescriptors(
const nsTArray<BluetoothGattId>& aDescriptorIds)
{
MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothGattId);
const InfallibleTArray<BluetoothGattId>& descriptorIds =
aValue.get_ArrayOfBluetoothGattId();
mDescriptors.Clear();
for (uint32_t i = 0; i < descriptorIds.Length(); i++) {
for (uint32_t i = 0; i < aDescriptorIds.Length(); i++) {
mDescriptors.AppendElement(new BluetoothGattDescriptor(
GetParentObject(), this, descriptorIds[i]));
GetParentObject(), this, aDescriptorIds[i]));
}
BluetoothGattCharacteristicBinding::ClearCachedDescriptorsValue(this);
@ -175,9 +170,7 @@ BluetoothGattCharacteristic::Notify(const BluetoothSignal& aData)
NS_ENSURE_TRUE_VOID(mSignalRegistered);
BluetoothValue v = aData.value();
if (aData.name().EqualsLiteral("DescriptorsDiscovered")) {
HandleDescriptorsDiscovered(v);
} else if (aData.name().EqualsLiteral("CharacteristicValueUpdated")) {
if (aData.name().EqualsLiteral("CharacteristicValueUpdated")) {
HandleCharacteristicValueUpdated(v);
} else {
BT_WARNING("Not handling GATT Characteristic signal: %s",

View File

@ -32,6 +32,7 @@ class BluetoothGattCharacteristic final : public nsISupports
, public nsWrapperCache
, public BluetoothSignalObserver
{
friend class BluetoothGattService;
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BluetoothGattCharacteristic)
@ -106,10 +107,10 @@ private:
* Add newly discovered GATT descriptors into mDescriptors and update the
* cache value of mDescriptors.
*
* @param aValue [in] BluetoothValue which contains an array of
* BluetoothGattId of all discovered descriptors.
* @param aDescriptorIds [in] An array of BluetoothGattId for each descriptor
* that belongs to this characteristic.
*/
void HandleDescriptorsDiscovered(const BluetoothValue& aValue);
void AssignDescriptors(const nsTArray<BluetoothGattId>& aDescriptorIds);
/**
* Update the value of this characteristic.

View File

@ -17,33 +17,10 @@ using namespace mozilla::dom;
USING_BLUETOOTH_NAMESPACE
NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothGattService)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(BluetoothGattService)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncludedServices)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCharacteristics)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
/**
* Unregister the bluetooth signal handler after unlinked.
*
* This is needed to avoid ending up with exposing a deleted object to JS or
* accessing deleted objects while receiving signals from parent process
* after unlinked. Please see Bug 1138267 for detail informations.
*/
nsString path;
GeneratePathFromGattId(tmp->mServiceId.mId, path);
UnregisterBluetoothSignalHandler(path, tmp);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(BluetoothGattService)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncludedServices)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCharacteristics)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(BluetoothGattService)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(BluetoothGattService,
mOwner,
mIncludedServices,
mCharacteristics)
NS_IMPL_CYCLE_COLLECTING_ADDREF(BluetoothGattService)
NS_IMPL_CYCLE_COLLECTING_RELEASE(BluetoothGattService)
@ -63,72 +40,49 @@ BluetoothGattService::BluetoothGattService(
MOZ_ASSERT(!mAppUuid.IsEmpty());
UuidToString(mServiceId.mId.mUuid, mUuidStr);
// Generate bluetooth signal path of this service to applications
nsString path;
GeneratePathFromGattId(mServiceId.mId, path);
RegisterBluetoothSignalHandler(path, this);
}
BluetoothGattService::~BluetoothGattService()
{
nsString path;
GeneratePathFromGattId(mServiceId.mId, path);
UnregisterBluetoothSignalHandler(path, this);
}
void
BluetoothGattService::HandleIncludedServicesDiscovered(
const BluetoothValue& aValue)
BluetoothGattService::AssignIncludedServices(
const nsTArray<BluetoothGattServiceId>& aServiceIds)
{
MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothGattServiceId);
const InfallibleTArray<BluetoothGattServiceId>& includedServIds =
aValue.get_ArrayOfBluetoothGattServiceId();
mIncludedServices.Clear();
for (uint32_t i = 0; i < includedServIds.Length(); i++) {
for (uint32_t i = 0; i < aServiceIds.Length(); i++) {
mIncludedServices.AppendElement(new BluetoothGattService(
GetParentObject(), mAppUuid, includedServIds[i]));
GetParentObject(), mAppUuid, aServiceIds[i]));
}
BluetoothGattServiceBinding::ClearCachedIncludedServicesValue(this);
}
void
BluetoothGattService::HandleCharacteristicsDiscovered(
const BluetoothValue& aValue)
BluetoothGattService::AssignCharacteristics(
const nsTArray<BluetoothGattCharAttribute>& aCharacteristics)
{
MOZ_ASSERT(aValue.type() ==
BluetoothValue::TArrayOfBluetoothGattCharAttribute);
const InfallibleTArray<BluetoothGattCharAttribute>& characteristics =
aValue.get_ArrayOfBluetoothGattCharAttribute();
mCharacteristics.Clear();
for (uint32_t i = 0; i < characteristics.Length(); i++) {
for (uint32_t i = 0; i < aCharacteristics.Length(); i++) {
mCharacteristics.AppendElement(new BluetoothGattCharacteristic(
GetParentObject(), this, characteristics[i]));
GetParentObject(), this, aCharacteristics[i]));
}
BluetoothGattServiceBinding::ClearCachedCharacteristicsValue(this);
}
void
BluetoothGattService::Notify(const BluetoothSignal& aData)
BluetoothGattService::AssignDescriptors(
const BluetoothGattId& aCharacteristicId,
const nsTArray<BluetoothGattId>& aDescriptorIds)
{
BT_LOGD("[D] %s", NS_ConvertUTF16toUTF8(aData.name()).get());
NS_ENSURE_TRUE_VOID(mSignalRegistered);
size_t index = mCharacteristics.IndexOf(aCharacteristicId);
NS_ENSURE_TRUE_VOID(index != mCharacteristics.NoIndex);
BluetoothValue v = aData.value();
if (aData.name().EqualsLiteral("IncludedServicesDiscovered")) {
HandleIncludedServicesDiscovered(v);
} else if (aData.name().EqualsLiteral("CharacteristicsDiscovered")) {
HandleCharacteristicsDiscovered(v);
} else {
BT_WARNING("Not handling GATT Service signal: %s",
NS_ConvertUTF16toUTF8(aData.name()).get());
}
nsRefPtr<BluetoothGattCharacteristic> characteristic =
mCharacteristics.ElementAt(index);
characteristic->AssignDescriptors(aDescriptorIds);
}
JSObject*

View File

@ -17,13 +17,14 @@
BEGIN_BLUETOOTH_NAMESPACE
class BluetoothGatt;
class BluetoothSignal;
class BluetoothValue;
class BluetoothGattService final : public nsISupports
, public nsWrapperCache
, public BluetoothSignalObserver
{
friend class BluetoothGatt;
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BluetoothGattService)
@ -71,8 +72,6 @@ public:
return mServiceId;
}
void Notify(const BluetoothSignal& aData); // BluetoothSignalObserver
nsPIDOMWindow* GetParentObject() const
{
return mOwner;
@ -92,20 +91,36 @@ private:
* Add newly discovered GATT included services into mIncludedServices and
* update the cache value of mIncludedServices.
*
* @param aValue [in] BluetoothValue which contains an array of
* BluetoothGattServiceId of all discovered included
* services.
* @param aServiceIds [in] An array of BluetoothGattServiceId for each
* included service that belongs to this service.
*/
void HandleIncludedServicesDiscovered(const BluetoothValue& aValue);
void AssignIncludedServices(
const nsTArray<BluetoothGattServiceId>& aServiceIds);
/**
* Add newly discovered GATT characteristics into mCharacteristics and
* update the cache value of mCharacteristics.
*
* @param aValue [in] BluetoothValue which contains an array of
* BluetoothGattId of all discovered characteristics.
* @param aCharacteristics [in] An array of BluetoothGattCharAttribute for
* each characteristic that belongs to this
* service.
*/
void HandleCharacteristicsDiscovered(const BluetoothValue& aValue);
void AssignCharacteristics(
const nsTArray<BluetoothGattCharAttribute>& aCharacteristics);
/**
* Add newly discovered GATT descriptors into mDescriptors of
* BluetoothGattCharacteristic and update the cache value of mDescriptors.
*
* @param aCharacteristicId [in] BluetoothGattId of a characteristic that
* belongs to this service.
* @param aDescriptorIds [in] An array of BluetoothGattId for each descriptor
* that belongs to the characteristic referred by
* aCharacteristicId.
*/
void AssignDescriptors(
const BluetoothGattId& aCharacteristicId,
const nsTArray<BluetoothGattId>& aDescriptorIds);
/****************************************************************************
* Variables

View File

@ -94,6 +94,7 @@ this.Keyboard = {
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
Services.obs.addObserver(this, 'remote-browser-shown', false);
Services.obs.addObserver(this, 'oop-frameloader-crashed', false);
Services.obs.addObserver(this, 'message-manager-close', false);
for (let name of this._messageNames) {
ppmm.addMessageListener('Keyboard:' + name, this);
@ -107,10 +108,18 @@ this.Keyboard = {
},
observe: function keyboardObserve(subject, topic, data) {
let frameLoader = subject.QueryInterface(Ci.nsIFrameLoader);
let mm = frameLoader.messageManager;
let frameLoader = null;
let mm = null;
if (topic == 'oop-frameloader-crashed') {
if (topic == 'message-manager-close') {
mm = subject;
} else {
frameLoader = subject.QueryInterface(Ci.nsIFrameLoader);
mm = frameLoader.messageManager;
}
if (topic == 'oop-frameloader-crashed' ||
topic == 'message-manager-close') {
if (this.formMM == mm) {
// The application has been closed unexpectingly. Let's tell the
// keyboard app that the focus has been lost.
@ -290,6 +299,8 @@ this.Keyboard = {
if (mm !== this.formMM) {
return false;
}
this.formMM = null;
}
}

View File

@ -8,6 +8,8 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
// -----------------------------------------------------------------------
// Web Install Prompt service
// -----------------------------------------------------------------------
@ -27,7 +29,15 @@ WebInstallPrompt.prototype = {
let button = bundle.GetStringFromName("addonsConfirmInstall.install");
aInstalls.forEach(function(install) {
let result = (prompt.confirmEx(aBrowser.contentWindow, title, install.name, flags, button, null, null, null, {value: false}) == 0);
let message;
if (install.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
title = bundle.GetStringFromName("addonsConfirmInstallUnsigned.title")
message = bundle.GetStringFromName("addonsConfirmInstallUnsigned.message") + "\n\n" + install.name;
} else {
message = install.name;
}
let result = (prompt.confirmEx(aBrowser.contentWindow, title, message, flags, button, null, null, null, {value: false}) == 0);
if (result)
install.install();
else

View File

@ -5,6 +5,9 @@
addonsConfirmInstall.title=Installing Add-on
addonsConfirmInstall.install=Install
addonsConfirmInstallUnsigned.title=Unverified add-on
addonsConfirmInstallUnsigned.message=This site would like to install an unverified add-on. Proceed at your own risk.
# Alerts
alertAddonsDownloading=Downloading add-on
alertAddonsInstalledNoRestart.message=Installation complete

View File

@ -13,7 +13,7 @@ task:
phone:
type: 'flame'
memory: '319'
sims: '1'
sims: '2'
build: '{{{img_url}}}'
features:
testdroidProxy: true

View File

@ -15,7 +15,18 @@ loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
loader.lazyServiceGetter(this, "gActivityDistributor",
"@mozilla.org/network/http-activity-distributor;1",
"nsIHttpActivityDistributor");
loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
let _testing = false;
Object.defineProperty(this, "gTesting", {
get: function() {
try {
const { gDevTools } = require("resource:///modules/devtools/gDevTools.jsm");
_testing = gDevTools.testing;
} catch (e) {
// gDevTools is not present on B2G.
}
return _testing;
}
});
///////////////////////////////////////////////////////////////////////////////
// Network logging
@ -747,7 +758,7 @@ NetworkMonitor.prototype = {
// TODO: one particular test (browser_styleeditor_fetch-from-cache.js) needs
// the gDevTools.testing check. We will move to a better way to serve its
// needs in bug 1167188, where this check should be removed.
if (!gDevTools.testing && aChannel.loadInfo &&
if (!gTesting && aChannel.loadInfo &&
aChannel.loadInfo.loadingDocument === null &&
aChannel.loadInfo.loadingPrincipal === Services.scriptSecurityManager.getSystemPrincipal()) {
return false;
@ -768,12 +779,6 @@ NetworkMonitor.prototype = {
}
}
if (aChannel.loadInfo) {
if (aChannel.loadInfo.contentPolicyType == Ci.nsIContentPolicy.TYPE_BEACON) {
return true;
}
}
if (this.topFrame) {
let topFrame = NetworkHelper.getTopFrameForRequest(aChannel);
if (topFrame && topFrame === this.topFrame) {
@ -788,6 +793,24 @@ NetworkMonitor.prototype = {
}
}
// The following check is necessary because beacon channels don't come
// associated with a load group. Bug 1160837 will hopefully introduce a
// platform fix that will render the following code entirely useless.
if (aChannel.loadInfo &&
aChannel.loadInfo.contentPolicyType == Ci.nsIContentPolicy.TYPE_BEACON) {
let nonE10sMatch = this.window &&
aChannel.loadInfo.loadingDocument === this.window.document;
let e10sMatch = this.topFrame &&
this.topFrame.contentPrincipal &&
this.topFrame.contentPrincipal.equals(aChannel.loadInfo.loadingPrincipal) &&
this.topFrame.contentPrincipal.URI.spec == aChannel.referrer.spec;
let b2gMatch = this.appId &&
aChannel.loadInfo.loadingPrincipal.appId === this.appId;
if (nonE10sMatch || e10sMatch || b2gMatch) {
return true;
}
}
return false;
},

View File

@ -106,28 +106,23 @@ treechildren.autocomplete-treebody::-moz-tree-cell-text(selected) {
}
%ifdef XP_WIN
@media not all and (-moz-os-version: windows-xp) {
@media (-moz-windows-default-theme) {
/*
-moz-appearance: menuitem is almost right, but the hover effect is not
transparent and is lighter than desired.
*/
.autocomplete-richlistitem[selected="true"] {
color: inherit;
background-color: transparent;
/* four gradients for the bevel highlights on each edge, one for blue background */
background-image:
linear-gradient(to bottom, rgba(255,255,255,0.9) 3px, transparent 3px),
linear-gradient(to right, rgba(255,255,255,0.5) 3px, transparent 3px),
linear-gradient(to left, rgba(255,255,255,0.5) 3px, transparent 3px),
linear-gradient(to top, rgba(255,255,255,0.4) 3px, transparent 3px),
linear-gradient(to bottom, rgba(163,196,247,0.3), rgba(122,180,246,0.3));
background-clip: content-box;
border-radius: 6px;
outline: 1px solid rgb(124,163,206);
-moz-outline-radius: 3px;
outline-offset: -2px;
}
@media (-moz-os-version: windows-vista) and (-moz-windows-default-theme),
(-moz-os-version: windows-win7) and (-moz-windows-default-theme) {
.autocomplete-richlistitem[selected="true"] {
color: inherit;
background-color: transparent;
/* four gradients for the bevel highlights on each edge, one for blue background */
background-image:
linear-gradient(to bottom, rgba(255,255,255,0.9) 3px, transparent 3px),
linear-gradient(to right, rgba(255,255,255,0.5) 3px, transparent 3px),
linear-gradient(to left, rgba(255,255,255,0.5) 3px, transparent 3px),
linear-gradient(to top, rgba(255,255,255,0.4) 3px, transparent 3px),
linear-gradient(to bottom, rgba(163,196,247,0.3), rgba(122,180,246,0.3));
background-clip: content-box;
border-radius: 6px;
outline: 1px solid rgb(124,163,206);
-moz-outline-radius: 3px;
outline-offset: -2px;
}
}
%endif