mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge m-c to inbound. a=merge
This commit is contained in:
commit
094299de37
@ -15,6 +15,7 @@ support-files =
|
||||
[test_controls.xul]
|
||||
[test_doc.html]
|
||||
[test_doc_busy.html]
|
||||
skip-if = os == 'mac' && os_version == '10.6'
|
||||
[test_docarticle.html]
|
||||
[test_editablebody.html]
|
||||
[test_expandable.xul]
|
||||
|
@ -27,6 +27,6 @@ exports.getTabForWindow = getTabForWindow;
|
||||
exports.getTabForRawTab = modelFor;
|
||||
|
||||
function getTabForBrowser(browser) {
|
||||
return modelFor(getRawTabForBrowser(browser));
|
||||
return modelFor(getRawTabForBrowser(browser)) || null;
|
||||
}
|
||||
exports.getTabForBrowser = getTabForBrowser;
|
||||
|
@ -9,7 +9,7 @@ const { tabNS, rawTabNS } = require('./namespace');
|
||||
const { EventTarget } = require('../event/target');
|
||||
const { activateTab, getTabTitle, setTabTitle, closeTab, getTabURL,
|
||||
getTabContentWindow, getTabForBrowser, setTabURL, getOwnerWindow,
|
||||
getTabContentDocument, getTabContentType, getTabId } = require('./utils');
|
||||
getTabContentDocument, getTabContentType, getTabId, isTab } = require('./utils');
|
||||
const { emit } = require('../event/core');
|
||||
const { isPrivate } = require('../private-browsing/utils');
|
||||
const { isWindowPrivate } = require('../window/utils');
|
||||
@ -17,6 +17,7 @@ const { when: unload } = require('../system/unload');
|
||||
const { BLANK } = require('../content/thumbnail');
|
||||
const { viewFor } = require('../view/core');
|
||||
const { EVENTS } = require('./events');
|
||||
const { modelFor } = require('../model/core');
|
||||
|
||||
const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec';
|
||||
|
||||
|
@ -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="05380df3158fa39e1dde1687c0bf11a71f8c6868"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="e7d268074ee3c9eeb191c2205c0e35992fb3915d"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
|
||||
<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="d31f6f08740a46e5315c9279b8f987c9d1fce486"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="04c307a8a8439adf38c0f14d065275f571a7b486"/>
|
||||
<!-- 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"/>
|
||||
|
@ -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="05380df3158fa39e1dde1687c0bf11a71f8c6868"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="e7d268074ee3c9eeb191c2205c0e35992fb3915d"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
|
||||
<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="d31f6f08740a46e5315c9279b8f987c9d1fce486"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="04c307a8a8439adf38c0f14d065275f571a7b486"/>
|
||||
<!-- 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"/>
|
||||
|
@ -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="05380df3158fa39e1dde1687c0bf11a71f8c6868"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e7d268074ee3c9eeb191c2205c0e35992fb3915d"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="22664edc4c73e5fe8f5095ff1d5549db78a2bc10"/>
|
||||
|
@ -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="05380df3158fa39e1dde1687c0bf11a71f8c6868"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="e7d268074ee3c9eeb191c2205c0e35992fb3915d"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="d31f6f08740a46e5315c9279b8f987c9d1fce486"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="04c307a8a8439adf38c0f14d065275f571a7b486"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
|
||||
<!-- Stock Android things -->
|
||||
|
@ -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="05380df3158fa39e1dde1687c0bf11a71f8c6868"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="e7d268074ee3c9eeb191c2205c0e35992fb3915d"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
|
||||
<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="d31f6f08740a46e5315c9279b8f987c9d1fce486"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="04c307a8a8439adf38c0f14d065275f571a7b486"/>
|
||||
<!-- 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"/>
|
||||
|
@ -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="05380df3158fa39e1dde1687c0bf11a71f8c6868"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="e7d268074ee3c9eeb191c2205c0e35992fb3915d"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
|
||||
<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="d31f6f08740a46e5315c9279b8f987c9d1fce486"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="04c307a8a8439adf38c0f14d065275f571a7b486"/>
|
||||
<!-- 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"/>
|
||||
|
@ -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="05380df3158fa39e1dde1687c0bf11a71f8c6868"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e7d268074ee3c9eeb191c2205c0e35992fb3915d"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="22664edc4c73e5fe8f5095ff1d5549db78a2bc10"/>
|
||||
|
@ -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="05380df3158fa39e1dde1687c0bf11a71f8c6868"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="e7d268074ee3c9eeb191c2205c0e35992fb3915d"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
|
||||
<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="d31f6f08740a46e5315c9279b8f987c9d1fce486"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="04c307a8a8439adf38c0f14d065275f571a7b486"/>
|
||||
<!-- Stock Android things -->
|
||||
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
|
||||
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"git": {
|
||||
"git_revision": "05380df3158fa39e1dde1687c0bf11a71f8c6868",
|
||||
"git_revision": "e7d268074ee3c9eeb191c2205c0e35992fb3915d",
|
||||
"remote": "https://git.mozilla.org/releases/gaia.git",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "fc4f5dfe591a45cb03ae221489f41b56f38c883a",
|
||||
"revision": "f7c817ea239637d874d7099f2ccaafc6c3766389",
|
||||
"repo_path": "integration/gaia-central"
|
||||
}
|
||||
|
@ -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="05380df3158fa39e1dde1687c0bf11a71f8c6868"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="e7d268074ee3c9eeb191c2205c0e35992fb3915d"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="d31f6f08740a46e5315c9279b8f987c9d1fce486"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="04c307a8a8439adf38c0f14d065275f571a7b486"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
|
||||
<!-- Stock Android things -->
|
||||
|
@ -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="05380df3158fa39e1dde1687c0bf11a71f8c6868"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="e7d268074ee3c9eeb191c2205c0e35992fb3915d"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
|
||||
<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="d31f6f08740a46e5315c9279b8f987c9d1fce486"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="04c307a8a8439adf38c0f14d065275f571a7b486"/>
|
||||
<!-- 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"/>
|
||||
|
@ -178,6 +178,9 @@ pref("app.update.badge", true);
|
||||
#else
|
||||
pref("app.update.badge", false);
|
||||
#endif
|
||||
// Give the user x seconds to reboot before showing a badge on the hamburger
|
||||
// button. default=4 days
|
||||
pref("app.update.badgeWaitTime", 345600);
|
||||
|
||||
// If set to true, the Update Service will apply updates in the background
|
||||
// when it finishes downloading them.
|
||||
|
@ -2363,11 +2363,19 @@ function BrowserViewSourceOfDocument(aArgsOrDocument) {
|
||||
let inTab = Services.prefs.getBoolPref("view_source.tab");
|
||||
if (inTab) {
|
||||
let viewSourceURL = `view-source:${args.URL}`;
|
||||
let tab = gBrowser.loadOneTab(viewSourceURL, {
|
||||
let tabBrowser = gBrowser;
|
||||
// In the case of sidebars and chat windows, gBrowser is defined but null,
|
||||
// because no #content element exists. For these cases, we need to find
|
||||
// the most recent browser window.
|
||||
if (!tabBrowser) {
|
||||
let browserWindow = RecentWindow.getMostRecentBrowserWindow();
|
||||
tabBrowser = browserWindow.gBrowser;
|
||||
}
|
||||
let tab = tabBrowser.loadOneTab(viewSourceURL, {
|
||||
relatedToCurrent: true,
|
||||
inBackground: false
|
||||
});
|
||||
args.viewSourceBrowser = gBrowser.getBrowserForTab(tab);
|
||||
args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
|
||||
top.gViewSourceUtils.viewSourceInBrowser(args);
|
||||
} else {
|
||||
top.gViewSourceUtils.viewSource(args);
|
||||
@ -2561,18 +2569,27 @@ function PageProxyClickHandler(aEvent)
|
||||
// Setup the hamburger button badges for updates, if enabled.
|
||||
let gMenuButtonUpdateBadge = {
|
||||
enabled: false,
|
||||
badgeWaitTime: 0,
|
||||
timer: null,
|
||||
|
||||
init: function () {
|
||||
try {
|
||||
this.enabled = Services.prefs.getBoolPref("app.update.badge");
|
||||
} catch (e) {}
|
||||
if (this.enabled) {
|
||||
try {
|
||||
this.badgeWaitTime = Services.prefs.getIntPref("app.update.badgeWaitTime");
|
||||
} catch (e) {
|
||||
this.badgeWaitTime = 345600; // 4 days
|
||||
}
|
||||
PanelUI.menuButton.classList.add("badged-button");
|
||||
Services.obs.addObserver(this, "update-staged", false);
|
||||
}
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
if (this.timer)
|
||||
this.timer.cancel();
|
||||
if (this.enabled) {
|
||||
Services.obs.removeObserver(this, "update-staged");
|
||||
PanelUI.panel.removeEventListener("popupshowing", this, true);
|
||||
@ -2616,23 +2633,14 @@ let gMenuButtonUpdateBadge = {
|
||||
case STATE_APPLIED_SVC:
|
||||
case STATE_PENDING:
|
||||
case STATE_PENDING_SVC:
|
||||
// If the update is successfully applied, or if the updater has fallen back
|
||||
// to non-staged updates, add a badge to the hamburger menu to indicate an
|
||||
// update will be applied once the browser restarts.
|
||||
PanelUI.menuButton.setAttribute("update-status", "succeeded");
|
||||
|
||||
let brandBundle = document.getElementById("bundle_brand");
|
||||
let brandShortName = brandBundle.getString("brandShortName");
|
||||
stringId = "appmenu.restartNeeded.description";
|
||||
updateButtonText = gNavigatorBundle.getFormattedString(stringId,
|
||||
[brandShortName]);
|
||||
|
||||
updateButton.setAttribute("label", updateButtonText);
|
||||
updateButton.setAttribute("update-status", "succeeded");
|
||||
updateButton.hidden = false;
|
||||
|
||||
PanelUI.panel.addEventListener("popupshowing", this, true);
|
||||
|
||||
if (this.timer) {
|
||||
return;
|
||||
}
|
||||
// Give the user badgeWaitTime seconds to react before prompting.
|
||||
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this.timer.initWithCallback(this, this.badgeWaitTime * 1000,
|
||||
this.timer.TYPE_ONE_SHOT);
|
||||
// The timer callback will call uninit() when it completes.
|
||||
break;
|
||||
case STATE_FAILED:
|
||||
// Background update has failed, let's show the UI responsible for
|
||||
@ -2649,13 +2657,29 @@ let gMenuButtonUpdateBadge = {
|
||||
|
||||
PanelUI.panel.addEventListener("popupshowing", this, true);
|
||||
|
||||
this.uninit();
|
||||
break;
|
||||
case STATE_DOWNLOADING:
|
||||
// We've fallen back to downloading the full update because the partial
|
||||
// update failed to get staged in the background. Therefore we need to keep
|
||||
// our observer.
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
notify: function () {
|
||||
// If the update is successfully applied, or if the updater has fallen back
|
||||
// to non-staged updates, add a badge to the hamburger menu to indicate an
|
||||
// update will be applied once the browser restarts.
|
||||
PanelUI.menuButton.setAttribute("update-status", "succeeded");
|
||||
|
||||
let brandBundle = document.getElementById("bundle_brand");
|
||||
let brandShortName = brandBundle.getString("brandShortName");
|
||||
stringId = "appmenu.restartNeeded.description";
|
||||
updateButtonText = gNavigatorBundle.getFormattedString(stringId,
|
||||
[brandShortName]);
|
||||
|
||||
let updateButton = document.getElementById("PanelUI-update-status");
|
||||
updateButton.setAttribute("label", updateButtonText);
|
||||
updateButton.setAttribute("update-status", "succeeded");
|
||||
updateButton.hidden = false;
|
||||
|
||||
PanelUI.panel.addEventListener("popupshowing", this, true);
|
||||
this.uninit();
|
||||
},
|
||||
|
||||
|
@ -1007,11 +1007,19 @@ nsContextMenu.prototype = {
|
||||
|
||||
let inTab = Services.prefs.getBoolPref("view_source.tab");
|
||||
if (inTab) {
|
||||
let tab = gBrowser.loadOneTab("about:blank", {
|
||||
let tabBrowser = gBrowser;
|
||||
// In the case of sidebars and chat windows, gBrowser is defined but null,
|
||||
// because no #content element exists. For these cases, we need to find
|
||||
// the most recent browser window.
|
||||
if (!tabBrowser) {
|
||||
let browserWindow = RecentWindow.getMostRecentBrowserWindow();
|
||||
tabBrowser = browserWindow.gBrowser;
|
||||
}
|
||||
let tab = tabBrowser.loadOneTab("about:blank", {
|
||||
relatedToCurrent: true,
|
||||
inBackground: false
|
||||
});
|
||||
let viewSourceBrowser = gBrowser.getBrowserForTab(tab);
|
||||
let viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
|
||||
if (aContext == "selection") {
|
||||
top.gViewSourceUtils
|
||||
.viewSourceFromSelectionInBrowser(reference, viewSourceBrowser);
|
||||
|
@ -163,7 +163,7 @@ loop.conversation = (function(mozL10n) {
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: navigator.mozLoop}), document.querySelector("#main"));
|
||||
|
||||
document.body.setAttribute("dir", mozL10n.getDirection());
|
||||
document.body.setAttribute("dir", "rtl");//mozL10n.getDirection());
|
||||
document.body.setAttribute("platform", loop.shared.utils.getPlatform());
|
||||
|
||||
dispatcher.dispatch(new sharedActions.GetWindowData({
|
||||
|
@ -163,7 +163,7 @@ loop.conversation = (function(mozL10n) {
|
||||
dispatcher={dispatcher}
|
||||
mozLoop={navigator.mozLoop} />, document.querySelector("#main"));
|
||||
|
||||
document.body.setAttribute("dir", mozL10n.getDirection());
|
||||
document.body.setAttribute("dir", "rtl");//mozL10n.getDirection());
|
||||
document.body.setAttribute("platform", loop.shared.utils.getPlatform());
|
||||
|
||||
dispatcher.dispatch(new sharedActions.GetWindowData({
|
||||
|
@ -91,6 +91,7 @@ loop.store = loop.store || {};
|
||||
"openRoom",
|
||||
"shareRoomUrl",
|
||||
"updateRoomContext",
|
||||
"updateRoomContextDone",
|
||||
"updateRoomContextError",
|
||||
"updateRoomList"
|
||||
],
|
||||
@ -115,7 +116,8 @@ loop.store = loop.store || {};
|
||||
error: null,
|
||||
pendingCreation: false,
|
||||
pendingInitialRetrieval: false,
|
||||
rooms: []
|
||||
rooms: [],
|
||||
savingContext: false
|
||||
};
|
||||
},
|
||||
|
||||
@ -473,6 +475,7 @@ loop.store = loop.store || {};
|
||||
* @param {sharedActions.UpdateRoomContext} actionData
|
||||
*/
|
||||
updateRoomContext: function(actionData) {
|
||||
this.setStoreState({ savingContext: true });
|
||||
this._mozLoop.rooms.get(actionData.roomToken, function(err, room) {
|
||||
if (err) {
|
||||
this.dispatchAction(new sharedActions.UpdateRoomContextError({
|
||||
@ -520,28 +523,38 @@ loop.store = loop.store || {};
|
||||
// When no properties have been set on the roomData object, there's nothing
|
||||
// to save.
|
||||
if (!Object.getOwnPropertyNames(roomData).length) {
|
||||
this.dispatchAction(new sharedActions.UpdateRoomContextDone());
|
||||
return;
|
||||
}
|
||||
|
||||
this.setStoreState({error: null});
|
||||
this._mozLoop.rooms.update(actionData.roomToken, roomData,
|
||||
function(err, data) {
|
||||
if (err) {
|
||||
this.dispatchAction(new sharedActions.UpdateRoomContextError({
|
||||
error: err
|
||||
}));
|
||||
}
|
||||
var action = err ?
|
||||
new sharedActions.UpdateRoomContextError({ error: err }) :
|
||||
new sharedActions.UpdateRoomContextDone();
|
||||
this.dispatchAction(action);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the updateRoomContextDone action.
|
||||
*/
|
||||
updateRoomContextDone: function() {
|
||||
this.setStoreState({ savingContext: false });
|
||||
},
|
||||
|
||||
/**
|
||||
* Updating the context data attached to a room error.
|
||||
*
|
||||
* @param {sharedActions.UpdateRoomContextError} actionData
|
||||
*/
|
||||
updateRoomContextError: function(actionData) {
|
||||
this.setStoreState({error: actionData.error});
|
||||
this.setStoreState({
|
||||
error: actionData.error,
|
||||
savingContext: false
|
||||
});
|
||||
}
|
||||
});
|
||||
})(document.mozL10n || navigator.mozL10n);
|
||||
|
@ -29,6 +29,8 @@ loop.roomViews = (function(mozL10n) {
|
||||
this._onActiveRoomStateChanged);
|
||||
this.listenTo(this.props.roomStore, "change:error",
|
||||
this._onRoomError);
|
||||
this.listenTo(this.props.roomStore, "change:savingContext",
|
||||
this._onRoomSavingContext);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
@ -53,11 +55,21 @@ loop.roomViews = (function(mozL10n) {
|
||||
}
|
||||
},
|
||||
|
||||
_onRoomSavingContext: function() {
|
||||
// Only update the state if we're mounted, to avoid the problem where
|
||||
// stopListening doesn't nuke the active listeners during a event
|
||||
// processing.
|
||||
if (this.isMounted()) {
|
||||
this.setState({savingContext: this.props.roomStore.getStoreState("savingContext")});
|
||||
}
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
var storeState = this.props.roomStore.getStoreState("activeRoom");
|
||||
return _.extend({
|
||||
// Used by the UI showcase.
|
||||
roomState: this.props.roomState || storeState.roomState
|
||||
roomState: this.props.roomState || storeState.roomState,
|
||||
savingContext: false
|
||||
}, storeState);
|
||||
}
|
||||
};
|
||||
@ -174,6 +186,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
mozLoop: React.PropTypes.object.isRequired,
|
||||
// This data is supplied by the activeRoomStore.
|
||||
roomData: React.PropTypes.object.isRequired,
|
||||
savingContext: React.PropTypes.bool,
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
showContext: React.PropTypes.bool.isRequired
|
||||
},
|
||||
@ -243,7 +256,11 @@ loop.roomViews = (function(mozL10n) {
|
||||
mozL10n.get("context_add_some_label")
|
||||
)
|
||||
),
|
||||
React.createElement("div", {className: "btn-group call-action-group"},
|
||||
React.createElement("div", {className: cx({
|
||||
"btn-group": true,
|
||||
"call-action-group": true,
|
||||
hide: this.state.editMode
|
||||
})},
|
||||
React.createElement("button", {className: "btn btn-info btn-email",
|
||||
onClick: this.handleEmailButtonClick},
|
||||
mozL10n.get("email_link_button")
|
||||
@ -270,6 +287,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
dispatcher: this.props.dispatcher,
|
||||
editMode: this.state.editMode,
|
||||
error: this.props.error,
|
||||
savingContext: this.props.savingContext,
|
||||
mozLoop: this.props.mozLoop,
|
||||
onEditModeChange: this.handleEditModeChange,
|
||||
roomData: this.props.roomData,
|
||||
@ -290,6 +308,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
onEditModeChange: React.PropTypes.func,
|
||||
// This data is supplied by the activeRoomStore.
|
||||
roomData: React.PropTypes.object.isRequired,
|
||||
savingContext: React.PropTypes.bool.isRequired,
|
||||
show: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
@ -379,6 +398,18 @@ loop.roomViews = (function(mozL10n) {
|
||||
this.setState({ show: false });
|
||||
},
|
||||
|
||||
handleContextClick: function(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
var url = this._getURL();
|
||||
if (!url || !url.location) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.mozLoop.openURL(url.location);
|
||||
},
|
||||
|
||||
handleEditClick: function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
@ -397,13 +428,13 @@ loop.roomViews = (function(mozL10n) {
|
||||
newRoomURL: context.url,
|
||||
newRoomDescription: context.description,
|
||||
newRoomThumbnail: context.previewImage
|
||||
}, this.handleFormSubmit);
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
newRoomURL: "",
|
||||
newRoomDescription: "",
|
||||
newRoomThumbnail: ""
|
||||
}, this.handleFormSubmit);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -469,7 +500,6 @@ loop.roomViews = (function(mozL10n) {
|
||||
var thumbnail = url && url.thumbnail || "";
|
||||
var urlDescription = url && url.description || "";
|
||||
var location = url && url.location || "";
|
||||
var checkboxLabel = null;
|
||||
var locationData = null;
|
||||
if (location) {
|
||||
locationData = checkboxLabel = sharedUtils.formatURL(location);
|
||||
@ -483,43 +513,50 @@ loop.roomViews = (function(mozL10n) {
|
||||
|
||||
var cx = React.addons.classSet;
|
||||
if (this.state.editMode) {
|
||||
var availableContext = this.state.availableContext;
|
||||
// The checkbox shows as checked when there's already context data
|
||||
// attached to this room.
|
||||
var checked = !!urlDescription;
|
||||
var checkboxLabel = urlDescription || (availableContext && availableContext.url ?
|
||||
availableContext.description : "");
|
||||
|
||||
return (
|
||||
React.createElement("div", {className: "room-context"},
|
||||
React.createElement("div", {className: "room-context-content"},
|
||||
React.createElement("p", {className: cx({"error": !!this.props.error,
|
||||
"error-display-area": true})},
|
||||
mozL10n.get("rooms_change_failed_label")
|
||||
),
|
||||
React.createElement("div", {className: "room-context-label"}, mozL10n.get("context_inroom_label")),
|
||||
React.createElement(sharedViews.Checkbox, {
|
||||
checked: !!url,
|
||||
disabled: !!url || !checkboxLabel,
|
||||
label: mozL10n.get("context_edit_activate_label", {
|
||||
title: checkboxLabel ? checkboxLabel.hostname : ""
|
||||
}),
|
||||
onChange: this.handleCheckboxChange,
|
||||
value: location}),
|
||||
React.createElement("form", {onSubmit: this.handleFormSubmit},
|
||||
React.createElement("textarea", {rows: "2", type: "text", className: "room-context-name",
|
||||
onBlur: this.handleFormSubmit,
|
||||
onKeyDown: this.handleTextareaKeyDown,
|
||||
placeholder: mozL10n.get("context_edit_name_placeholder"),
|
||||
valueLink: this.linkState("newRoomName")}),
|
||||
React.createElement("input", {type: "text", className: "room-context-url",
|
||||
onBlur: this.handleFormSubmit,
|
||||
onKeyDown: this.handleTextareaKeyDown,
|
||||
placeholder: "https://",
|
||||
valueLink: this.linkState("newRoomURL")}),
|
||||
React.createElement("textarea", {rows: "4", type: "text", className: "room-context-comments",
|
||||
onBlur: this.handleFormSubmit,
|
||||
onKeyDown: this.handleTextareaKeyDown,
|
||||
placeholder: mozL10n.get("context_edit_comments_placeholder"),
|
||||
valueLink: this.linkState("newRoomDescription")})
|
||||
),
|
||||
React.createElement("button", {className: "room-context-btn-close",
|
||||
onClick: this.handleCloseClick,
|
||||
title: mozL10n.get("cancel_button")})
|
||||
)
|
||||
React.createElement("div", {className: "room-context editMode"},
|
||||
React.createElement("p", {className: cx({"error": !!this.props.error,
|
||||
"error-display-area": true})},
|
||||
mozL10n.get("rooms_change_failed_label")
|
||||
),
|
||||
React.createElement("div", {className: "room-context-label"}, mozL10n.get("context_inroom_label")),
|
||||
React.createElement(sharedViews.Checkbox, {
|
||||
additionalClass: cx({ hide: !checkboxLabel }),
|
||||
checked: checked,
|
||||
disabled: checked,
|
||||
label: checkboxLabel,
|
||||
onChange: this.handleCheckboxChange,
|
||||
value: location}),
|
||||
React.createElement("form", {onSubmit: this.handleFormSubmit},
|
||||
React.createElement("input", {type: "text", className: "room-context-name",
|
||||
onKeyDown: this.handleTextareaKeyDown,
|
||||
placeholder: mozL10n.get("context_edit_name_placeholder"),
|
||||
valueLink: this.linkState("newRoomName")}),
|
||||
React.createElement("input", {type: "text", className: "room-context-url",
|
||||
onKeyDown: this.handleTextareaKeyDown,
|
||||
placeholder: "https://",
|
||||
disabled: availableContext && availableContext.url === this.state.newRoomURL,
|
||||
valueLink: this.linkState("newRoomURL")}),
|
||||
React.createElement("textarea", {rows: "3", type: "text", className: "room-context-comments",
|
||||
onKeyDown: this.handleTextareaKeyDown,
|
||||
placeholder: mozL10n.get("context_edit_comments_placeholder"),
|
||||
valueLink: this.linkState("newRoomDescription")})
|
||||
),
|
||||
React.createElement("button", {className: "btn btn-info",
|
||||
disabled: this.props.savingContext,
|
||||
onClick: this.handleFormSubmit},
|
||||
mozL10n.get("context_save_label")
|
||||
),
|
||||
React.createElement("button", {className: "room-context-btn-close",
|
||||
onClick: this.handleCloseClick,
|
||||
title: mozL10n.get("cancel_button")})
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -530,25 +567,23 @@ loop.roomViews = (function(mozL10n) {
|
||||
|
||||
return (
|
||||
React.createElement("div", {className: "room-context"},
|
||||
React.createElement("img", {className: "room-context-thumbnail", src: thumbnail}),
|
||||
React.createElement("div", {className: "room-context-content"},
|
||||
React.createElement("div", {className: "room-context-label"}, mozL10n.get("context_inroom_label")),
|
||||
React.createElement("div", {className: "room-context-label"}, mozL10n.get("context_inroom_label")),
|
||||
React.createElement("div", {className: "room-context-content",
|
||||
onClick: this.handleContextClick},
|
||||
React.createElement("img", {className: "room-context-thumbnail", src: thumbnail}),
|
||||
React.createElement("div", {className: "room-context-description",
|
||||
title: urlDescription}, this._truncate(urlDescription)),
|
||||
React.createElement("a", {className: "room-context-url",
|
||||
href: location,
|
||||
target: "_blank",
|
||||
title: locationData.location}, locationData.hostname),
|
||||
this.props.roomData.roomDescription ?
|
||||
React.createElement("div", {className: "room-context-comment"}, this.props.roomData.roomDescription) :
|
||||
null,
|
||||
React.createElement("button", {className: "room-context-btn-close",
|
||||
onClick: this.handleCloseClick,
|
||||
title: mozL10n.get("context_hide_tooltip")}),
|
||||
React.createElement("button", {className: "room-context-btn-edit",
|
||||
onClick: this.handleEditClick,
|
||||
title: mozL10n.get("context_edit_tooltip")})
|
||||
)
|
||||
title: urlDescription},
|
||||
this._truncate(urlDescription),
|
||||
React.createElement("a", {className: "room-context-url",
|
||||
title: locationData.location}, locationData.hostname)
|
||||
)
|
||||
),
|
||||
React.createElement("button", {className: "room-context-btn-close",
|
||||
onClick: this.handleCloseClick,
|
||||
title: mozL10n.get("context_hide_tooltip")}),
|
||||
React.createElement("button", {className: "room-context-btn-edit",
|
||||
onClick: this.handleEditClick,
|
||||
title: mozL10n.get("context_edit_tooltip")})
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -671,6 +706,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
error: this.state.error,
|
||||
mozLoop: this.props.mozLoop,
|
||||
roomData: roomData,
|
||||
savingContext: this.state.savingContext,
|
||||
show: shouldRenderInvitationOverlay,
|
||||
showContext: shouldRenderContextView,
|
||||
socialShareButtonAvailable: this.state.socialShareButtonAvailable,
|
||||
@ -696,6 +732,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
React.createElement(DesktopRoomContextView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
error: this.state.error,
|
||||
savingContext: this.state.savingContext,
|
||||
mozLoop: this.props.mozLoop,
|
||||
roomData: roomData,
|
||||
show: !shouldRenderInvitationOverlay && shouldRenderContextView})
|
||||
|
@ -29,6 +29,8 @@ loop.roomViews = (function(mozL10n) {
|
||||
this._onActiveRoomStateChanged);
|
||||
this.listenTo(this.props.roomStore, "change:error",
|
||||
this._onRoomError);
|
||||
this.listenTo(this.props.roomStore, "change:savingContext",
|
||||
this._onRoomSavingContext);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
@ -53,11 +55,21 @@ loop.roomViews = (function(mozL10n) {
|
||||
}
|
||||
},
|
||||
|
||||
_onRoomSavingContext: function() {
|
||||
// Only update the state if we're mounted, to avoid the problem where
|
||||
// stopListening doesn't nuke the active listeners during a event
|
||||
// processing.
|
||||
if (this.isMounted()) {
|
||||
this.setState({savingContext: this.props.roomStore.getStoreState("savingContext")});
|
||||
}
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
var storeState = this.props.roomStore.getStoreState("activeRoom");
|
||||
return _.extend({
|
||||
// Used by the UI showcase.
|
||||
roomState: this.props.roomState || storeState.roomState
|
||||
roomState: this.props.roomState || storeState.roomState,
|
||||
savingContext: false
|
||||
}, storeState);
|
||||
}
|
||||
};
|
||||
@ -174,6 +186,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
mozLoop: React.PropTypes.object.isRequired,
|
||||
// This data is supplied by the activeRoomStore.
|
||||
roomData: React.PropTypes.object.isRequired,
|
||||
savingContext: React.PropTypes.bool,
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
showContext: React.PropTypes.bool.isRequired
|
||||
},
|
||||
@ -243,7 +256,11 @@ loop.roomViews = (function(mozL10n) {
|
||||
{mozL10n.get("context_add_some_label")}
|
||||
</a>
|
||||
</div>
|
||||
<div className="btn-group call-action-group">
|
||||
<div className={cx({
|
||||
"btn-group": true,
|
||||
"call-action-group": true,
|
||||
hide: this.state.editMode
|
||||
})}>
|
||||
<button className="btn btn-info btn-email"
|
||||
onClick={this.handleEmailButtonClick}>
|
||||
{mozL10n.get("email_link_button")}
|
||||
@ -270,6 +287,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
dispatcher={this.props.dispatcher}
|
||||
editMode={this.state.editMode}
|
||||
error={this.props.error}
|
||||
savingContext={this.props.savingContext}
|
||||
mozLoop={this.props.mozLoop}
|
||||
onEditModeChange={this.handleEditModeChange}
|
||||
roomData={this.props.roomData}
|
||||
@ -290,6 +308,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
onEditModeChange: React.PropTypes.func,
|
||||
// This data is supplied by the activeRoomStore.
|
||||
roomData: React.PropTypes.object.isRequired,
|
||||
savingContext: React.PropTypes.bool.isRequired,
|
||||
show: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
@ -379,6 +398,18 @@ loop.roomViews = (function(mozL10n) {
|
||||
this.setState({ show: false });
|
||||
},
|
||||
|
||||
handleContextClick: function(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
var url = this._getURL();
|
||||
if (!url || !url.location) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.mozLoop.openURL(url.location);
|
||||
},
|
||||
|
||||
handleEditClick: function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
@ -397,13 +428,13 @@ loop.roomViews = (function(mozL10n) {
|
||||
newRoomURL: context.url,
|
||||
newRoomDescription: context.description,
|
||||
newRoomThumbnail: context.previewImage
|
||||
}, this.handleFormSubmit);
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
newRoomURL: "",
|
||||
newRoomDescription: "",
|
||||
newRoomThumbnail: ""
|
||||
}, this.handleFormSubmit);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -469,7 +500,6 @@ loop.roomViews = (function(mozL10n) {
|
||||
var thumbnail = url && url.thumbnail || "";
|
||||
var urlDescription = url && url.description || "";
|
||||
var location = url && url.location || "";
|
||||
var checkboxLabel = null;
|
||||
var locationData = null;
|
||||
if (location) {
|
||||
locationData = checkboxLabel = sharedUtils.formatURL(location);
|
||||
@ -483,43 +513,50 @@ loop.roomViews = (function(mozL10n) {
|
||||
|
||||
var cx = React.addons.classSet;
|
||||
if (this.state.editMode) {
|
||||
var availableContext = this.state.availableContext;
|
||||
// The checkbox shows as checked when there's already context data
|
||||
// attached to this room.
|
||||
var checked = !!urlDescription;
|
||||
var checkboxLabel = urlDescription || (availableContext && availableContext.url ?
|
||||
availableContext.description : "");
|
||||
|
||||
return (
|
||||
<div className="room-context">
|
||||
<div className="room-context-content">
|
||||
<p className={cx({"error": !!this.props.error,
|
||||
"error-display-area": true})}>
|
||||
{mozL10n.get("rooms_change_failed_label")}
|
||||
</p>
|
||||
<div className="room-context-label">{mozL10n.get("context_inroom_label")}</div>
|
||||
<sharedViews.Checkbox
|
||||
checked={!!url}
|
||||
disabled={!!url || !checkboxLabel}
|
||||
label={mozL10n.get("context_edit_activate_label", {
|
||||
title: checkboxLabel ? checkboxLabel.hostname : ""
|
||||
})}
|
||||
onChange={this.handleCheckboxChange}
|
||||
value={location} />
|
||||
<form onSubmit={this.handleFormSubmit}>
|
||||
<textarea rows="2" type="text" className="room-context-name"
|
||||
onBlur={this.handleFormSubmit}
|
||||
onKeyDown={this.handleTextareaKeyDown}
|
||||
placeholder={mozL10n.get("context_edit_name_placeholder")}
|
||||
valueLink={this.linkState("newRoomName")} />
|
||||
<input type="text" className="room-context-url"
|
||||
onBlur={this.handleFormSubmit}
|
||||
onKeyDown={this.handleTextareaKeyDown}
|
||||
placeholder="https://"
|
||||
valueLink={this.linkState("newRoomURL")} />
|
||||
<textarea rows="4" type="text" className="room-context-comments"
|
||||
onBlur={this.handleFormSubmit}
|
||||
onKeyDown={this.handleTextareaKeyDown}
|
||||
placeholder={mozL10n.get("context_edit_comments_placeholder")}
|
||||
valueLink={this.linkState("newRoomDescription")} />
|
||||
</form>
|
||||
<button className="room-context-btn-close"
|
||||
onClick={this.handleCloseClick}
|
||||
title={mozL10n.get("cancel_button")}/>
|
||||
</div>
|
||||
<div className="room-context editMode">
|
||||
<p className={cx({"error": !!this.props.error,
|
||||
"error-display-area": true})}>
|
||||
{mozL10n.get("rooms_change_failed_label")}
|
||||
</p>
|
||||
<div className="room-context-label">{mozL10n.get("context_inroom_label")}</div>
|
||||
<sharedViews.Checkbox
|
||||
additionalClass={cx({ hide: !checkboxLabel })}
|
||||
checked={checked}
|
||||
disabled={checked}
|
||||
label={checkboxLabel}
|
||||
onChange={this.handleCheckboxChange}
|
||||
value={location} />
|
||||
<form onSubmit={this.handleFormSubmit}>
|
||||
<input type="text" className="room-context-name"
|
||||
onKeyDown={this.handleTextareaKeyDown}
|
||||
placeholder={mozL10n.get("context_edit_name_placeholder")}
|
||||
valueLink={this.linkState("newRoomName")} />
|
||||
<input type="text" className="room-context-url"
|
||||
onKeyDown={this.handleTextareaKeyDown}
|
||||
placeholder="https://"
|
||||
disabled={availableContext && availableContext.url === this.state.newRoomURL}
|
||||
valueLink={this.linkState("newRoomURL")} />
|
||||
<textarea rows="3" type="text" className="room-context-comments"
|
||||
onKeyDown={this.handleTextareaKeyDown}
|
||||
placeholder={mozL10n.get("context_edit_comments_placeholder")}
|
||||
valueLink={this.linkState("newRoomDescription")} />
|
||||
</form>
|
||||
<button className="btn btn-info"
|
||||
disabled={this.props.savingContext}
|
||||
onClick={this.handleFormSubmit}>
|
||||
{mozL10n.get("context_save_label")}
|
||||
</button>
|
||||
<button className="room-context-btn-close"
|
||||
onClick={this.handleCloseClick}
|
||||
title={mozL10n.get("cancel_button")}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -530,25 +567,23 @@ loop.roomViews = (function(mozL10n) {
|
||||
|
||||
return (
|
||||
<div className="room-context">
|
||||
<img className="room-context-thumbnail" src={thumbnail}/>
|
||||
<div className="room-context-content">
|
||||
<div className="room-context-label">{mozL10n.get("context_inroom_label")}</div>
|
||||
<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}/>
|
||||
<div className="room-context-description"
|
||||
title={urlDescription}>{this._truncate(urlDescription)}</div>
|
||||
<a className="room-context-url"
|
||||
href={location}
|
||||
target="_blank"
|
||||
title={locationData.location}>{locationData.hostname}</a>
|
||||
{this.props.roomData.roomDescription ?
|
||||
<div className="room-context-comment">{this.props.roomData.roomDescription}</div> :
|
||||
null}
|
||||
<button className="room-context-btn-close"
|
||||
onClick={this.handleCloseClick}
|
||||
title={mozL10n.get("context_hide_tooltip")}/>
|
||||
<button className="room-context-btn-edit"
|
||||
onClick={this.handleEditClick}
|
||||
title={mozL10n.get("context_edit_tooltip")}/>
|
||||
title={urlDescription}>
|
||||
{this._truncate(urlDescription)}
|
||||
<a className="room-context-url"
|
||||
title={locationData.location}>{locationData.hostname}</a>
|
||||
</div>
|
||||
</div>
|
||||
<button className="room-context-btn-close"
|
||||
onClick={this.handleCloseClick}
|
||||
title={mozL10n.get("context_hide_tooltip")}/>
|
||||
<button className="room-context-btn-edit"
|
||||
onClick={this.handleEditClick}
|
||||
title={mozL10n.get("context_edit_tooltip")}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -671,6 +706,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
error={this.state.error}
|
||||
mozLoop={this.props.mozLoop}
|
||||
roomData={roomData}
|
||||
savingContext={this.state.savingContext}
|
||||
show={shouldRenderInvitationOverlay}
|
||||
showContext={shouldRenderContextView}
|
||||
socialShareButtonAvailable={this.state.socialShareButtonAvailable}
|
||||
@ -696,6 +732,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
<DesktopRoomContextView
|
||||
dispatcher={this.props.dispatcher}
|
||||
error={this.state.error}
|
||||
savingContext={this.state.savingContext}
|
||||
mozLoop={this.props.mozLoop}
|
||||
roomData={roomData}
|
||||
show={!shouldRenderInvitationOverlay && shouldRenderContextView} />
|
||||
|
@ -313,7 +313,8 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.call-action-group > .btn {
|
||||
.call-action-group > .btn,
|
||||
.room-context > .btn {
|
||||
min-height: 26px;
|
||||
border-radius: 2px;
|
||||
margin: 0 4px;
|
||||
@ -1005,11 +1006,19 @@ body[dir=rtl] .share-service-dropdown .share-panel-header {
|
||||
width: 100%;
|
||||
font-size: .9em;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
flex-flow: column nowrap;
|
||||
align-content: flex-start;
|
||||
align-items: flex-start;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
/* Make the context view float atop the video elements. */
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.room-context.editMode {
|
||||
/* Stretch to the maximum available space whilst not covering the conversation
|
||||
toolbar (26px). */
|
||||
height: calc(100% - 26px);
|
||||
}
|
||||
|
||||
.room-invitation-overlay .room-context {
|
||||
@ -1019,11 +1028,26 @@ body[dir=rtl] .share-service-dropdown .share-panel-header {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.room-invitation-overlay .room-context.editMode {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.room-context-content {
|
||||
flex: 1 1 auto;
|
||||
text-align: start;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
.room-context-thumbnail {
|
||||
width: 16px;
|
||||
/* 16px icon size + 3px border width. */
|
||||
width: 19px;
|
||||
max-height: 19px;
|
||||
border: 3px solid #fff;
|
||||
border-radius: 3px;
|
||||
background-color: #fff;
|
||||
-moz-margin-end: 1ch;
|
||||
margin-bottom: 1em;
|
||||
order: 1;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
@ -1031,30 +1055,24 @@ body[dir=rtl] .share-service-dropdown .share-panel-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.room-context-content {
|
||||
order: 2;
|
||||
flex: 1 1 auto;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.room-context-content > .error-display-area.error {
|
||||
.room-context > .error-display-area.error {
|
||||
display: block;
|
||||
background-color: rgba(215,67,69,.8);
|
||||
border-radius: 3px;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
.room-context-content > .error-display-area {
|
||||
.room-context > .error-display-area {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.room-context-content > .error-display-area.error {
|
||||
.room-context > .error-display-area.error {
|
||||
margin: 1em 0 .5em 0;
|
||||
text-align: center;
|
||||
text-shadow: 1px 1px 0 rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
.room-context-content > .checkbox-wrapper {
|
||||
.room-context > .checkbox-wrapper {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
@ -1062,9 +1080,8 @@ body[dir=rtl] .share-service-dropdown .share-panel-header {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.room-context-label,
|
||||
.room-context-description,
|
||||
.room-context-content > .checkbox-wrapper > label {
|
||||
.room-context > .checkbox-wrapper > label {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@ -1077,45 +1094,70 @@ body[dir=rtl] .share-service-dropdown .share-panel-header {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.room-context-url {
|
||||
color: #59A1D7;
|
||||
:not(input).room-context-url {
|
||||
color: #0095dd;
|
||||
font-style: italic;
|
||||
text-decoration: none;
|
||||
margin-bottom: 1em;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.room-context-url:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.room-context-content > form > textarea,
|
||||
.room-context-content > form > input[type="text"] {
|
||||
display: block;
|
||||
background: rgba(0,0,0,.5);
|
||||
color: #fff;
|
||||
font-family: "Helvetica Neue", Arial, sans;
|
||||
font-size: 1em;
|
||||
border: 1px solid #999;
|
||||
.room-context > form {
|
||||
width: 100%;
|
||||
padding: .2em .4em;
|
||||
border-radius: 3px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.room-context-content > form > textarea:not(:last-of-type),
|
||||
.room-context-content > form > input[type="text"] {
|
||||
.room-context > form > textarea,
|
||||
.room-context > form > input[type="text"] {
|
||||
display: block;
|
||||
background: rgba(0,0,0,.5);
|
||||
font-family: "Helvetica Neue", Arial, sans;
|
||||
border: 1px solid rgba(255,255,255,.2);
|
||||
width: 100%;
|
||||
padding: .5em;
|
||||
border-radius: 3px;
|
||||
resize: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.room-context > form > textarea {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.room-context > form > input:not([disabled]).room-context-url {
|
||||
color: #0095dd;
|
||||
}
|
||||
|
||||
.room-context > form > input[disabled] {
|
||||
background-color: rgba(255,255,255,.2);
|
||||
color: rgba(255,255,255,.4);
|
||||
}
|
||||
|
||||
.room-context > form > textarea:not(:last-of-type),
|
||||
.room-context > form > input[type="text"] {
|
||||
margin: 0 0 .5em 0;
|
||||
}
|
||||
|
||||
.room-context > .btn {
|
||||
margin: .5em 0 0;
|
||||
font-size: 1.1em;
|
||||
padding: 0 .5em;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.room-context-btn-close,
|
||||
.room-context-btn-edit {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
right: 8px;
|
||||
/* 8px offset + 2px border-top */
|
||||
top: 10px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: transparent;
|
||||
background-image: url("../img/icons-10x10.svg#close");
|
||||
background-image: url("../img/icons-10x10.svg#close-darkergrey");
|
||||
background-size: 8px 8px;
|
||||
background-repeat: no-repeat;
|
||||
border: 0;
|
||||
@ -1124,8 +1166,8 @@ body[dir=rtl] .share-service-dropdown .share-panel-header {
|
||||
}
|
||||
|
||||
.room-context-btn-edit {
|
||||
right: 18px;
|
||||
background-image: url("../img/icons-10x10.svg#edit");
|
||||
right: 20px;
|
||||
background-image: url("../img/icons-10x10.svg#edit-darkergrey");
|
||||
}
|
||||
|
||||
.room-context-btn-edit:hover,
|
||||
@ -1141,11 +1183,11 @@ body[dir=rtl] .share-service-dropdown .share-panel-header {
|
||||
body[dir=rtl] .room-context-btn-close,
|
||||
body[dir=rtl] .room-context-btn-edit {
|
||||
right: auto;
|
||||
left: 5px;
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
body[dir=rtl] .room-context-btn-edit {
|
||||
left: 18px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
/* Standalone rooms */
|
||||
|
@ -22,6 +22,9 @@
|
||||
use[id$="-disabled"] {
|
||||
fill: rgba(255,255,255,0.4);
|
||||
}
|
||||
use[id$="-darkergrey"] {
|
||||
fill: #999;
|
||||
}
|
||||
</style>
|
||||
<defs>
|
||||
<polygon id="close-shape" points="10,1.717 8.336,0.049 5.024,3.369 1.663,0 0,1.668 3.36,5.037 0.098,8.307 1.762,9.975 5.025,6.705 8.311,10 9.975,8.332 6.688,5.037"/>
|
||||
@ -33,6 +36,7 @@
|
||||
<use id="close" xlink:href="#close-shape"/>
|
||||
<use id="close-active" xlink:href="#close-shape"/>
|
||||
<use id="close-disabled" xlink:href="#close-shape"/>
|
||||
<use id="close-darkergrey" xlink:href="#close-shape"/>
|
||||
<use id="dropdown" xlink:href="#dropdown-shape"/>
|
||||
<use id="dropdown-white" xlink:href="#dropdown-shape"/>
|
||||
<use id="dropdown-active" xlink:href="#dropdown-shape"/>
|
||||
@ -40,6 +44,7 @@
|
||||
<use id="edit" xlink:href="#edit-shape"/>
|
||||
<use id="edit-active" xlink:href="#edit-shape"/>
|
||||
<use id="edit-disabled" xlink:href="#edit-shape"/>
|
||||
<use id="edit-darkergrey" xlink:href="#edit-shape"/>
|
||||
<use id="expand" xlink:href="#expand-shape"/>
|
||||
<use id="expand-active" xlink:href="#expand-shape"/>
|
||||
<use id="expand-disabled" xlink:href="#expand-shape"/>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.7 KiB |
@ -368,6 +368,12 @@ loop.shared.actions = (function() {
|
||||
error: [Error, Object]
|
||||
}),
|
||||
|
||||
/**
|
||||
* Updating the context data attached to a room finished successfully.
|
||||
*/
|
||||
UpdateRoomContextDone: Action.define("updateRoomContextDone", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Copy a room url into the user's clipboard.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
|
@ -435,7 +435,7 @@ loop.store.ActiveRoomStore = (function() {
|
||||
// XXX Ideally we'd do this check before joining a room, but we're waiting
|
||||
// for the UX for that. See bug 1166824. In the meantime this gives us
|
||||
// additional information for analysis.
|
||||
loop.shared.utils.hasAudioDevices(function(hasAudio) {
|
||||
loop.shared.utils.hasAudioOrVideoDevices(function(hasAudio) {
|
||||
if (hasAudio) {
|
||||
// MEDIA_WAIT causes the views to dispatch sharedActions.SetupStreamElements,
|
||||
// which in turn starts the sdk obtaining the device permission.
|
||||
|
@ -312,12 +312,12 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
||||
* @param {Function} callback Called with a boolean which is true if there
|
||||
* are audio devices present.
|
||||
*/
|
||||
function hasAudioDevices(callback) {
|
||||
function hasAudioOrVideoDevices(callback) {
|
||||
// mediaDevices is the official API for the spec.
|
||||
if ("mediaDevices" in rootNavigator) {
|
||||
rootNavigator.mediaDevices.enumerateDevices().then(function(result) {
|
||||
function checkForInput(device) {
|
||||
return device.kind === "audioinput";
|
||||
return device.kind === "audioinput" || device.kind === "videoinput";
|
||||
}
|
||||
|
||||
callback(result.some(checkForInput));
|
||||
@ -329,7 +329,7 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
||||
} else if ("MediaStreamTrack" in rootObject) {
|
||||
rootObject.MediaStreamTrack.getSources(function(result) {
|
||||
function checkForInput(device) {
|
||||
return device.kind === "audio";
|
||||
return device.kind === "audio" || device.kind === "video";
|
||||
}
|
||||
|
||||
callback(result.some(checkForInput));
|
||||
@ -745,7 +745,7 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
||||
isFirefoxOS: isFirefoxOS,
|
||||
isOpera: isOpera,
|
||||
getUnsupportedPlatform: getUnsupportedPlatform,
|
||||
hasAudioDevices: hasAudioDevices,
|
||||
hasAudioOrVideoDevices: hasAudioOrVideoDevices,
|
||||
locationData: locationData,
|
||||
atob: atob,
|
||||
btoa: btoa,
|
||||
|
@ -663,7 +663,7 @@ loop.shared.views = (function(_, l10n) {
|
||||
disabled: this.props.disabled
|
||||
};
|
||||
if (this.props.additionalClass) {
|
||||
checkClasses[this.props.additionalClass] = true;
|
||||
wrapperClasses[this.props.additionalClass] = true;
|
||||
}
|
||||
return (
|
||||
React.createElement("div", {className: cx(wrapperClasses),
|
||||
|
@ -663,7 +663,7 @@ loop.shared.views = (function(_, l10n) {
|
||||
disabled: this.props.disabled
|
||||
};
|
||||
if (this.props.additionalClass) {
|
||||
checkClasses[this.props.additionalClass] = true;
|
||||
wrapperClasses[this.props.additionalClass] = true;
|
||||
}
|
||||
return (
|
||||
<div className={cx(wrapperClasses)}
|
||||
|
@ -644,9 +644,26 @@ describe("loop.store.RoomStore", function () {
|
||||
});
|
||||
});
|
||||
|
||||
it("should flag the the store as saving context", function() {
|
||||
expect(store.getStoreState().savingContext).to.eql(false);
|
||||
|
||||
sandbox.stub(fakeMozLoop.rooms, "update", function(roomToken, roomData, cb) {
|
||||
expect(store.getStoreState().savingContext).to.eql(true);
|
||||
cb();
|
||||
});
|
||||
|
||||
dispatcher.dispatch(new sharedActions.UpdateRoomContext({
|
||||
roomToken: "42abc",
|
||||
newRoomName: "silly name"
|
||||
}));
|
||||
|
||||
expect(store.getStoreState().savingContext).to.eql(false);
|
||||
});
|
||||
|
||||
it("should store any update-encountered error", function() {
|
||||
var err = new Error("fake");
|
||||
sandbox.stub(fakeMozLoop.rooms, "update", function(roomToken, roomData, cb) {
|
||||
expect(store.getStoreState().savingContext).to.eql(true);
|
||||
cb(err);
|
||||
});
|
||||
|
||||
@ -655,7 +672,9 @@ describe("loop.store.RoomStore", function () {
|
||||
newRoomName: "silly name"
|
||||
}));
|
||||
|
||||
expect(store.getStoreState().error).eql(err);
|
||||
var state = store.getStoreState();
|
||||
expect(state.error).eql(err);
|
||||
expect(state.savingContext).to.eql(false);
|
||||
});
|
||||
|
||||
it("should ensure only submitting a non-empty room name", function() {
|
||||
@ -667,6 +686,7 @@ describe("loop.store.RoomStore", function () {
|
||||
}));
|
||||
|
||||
sinon.assert.notCalled(fakeMozLoop.rooms.update);
|
||||
expect(store.getStoreState().savingContext).to.eql(false);
|
||||
});
|
||||
|
||||
it("should save updated context information", function() {
|
||||
@ -723,6 +743,7 @@ describe("loop.store.RoomStore", function () {
|
||||
}));
|
||||
|
||||
sinon.assert.notCalled(fakeMozLoop.rooms.update);
|
||||
expect(store.getStoreState().savingContext).to.eql(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -96,7 +96,7 @@ describe("loop.roomViews", function () {
|
||||
roomStore: roomStore
|
||||
}));
|
||||
|
||||
var expectedState = _.extend({foo: "bar"},
|
||||
var expectedState = _.extend({foo: "bar", savingContext: false},
|
||||
activeRoomStore.getInitialStoreState());
|
||||
|
||||
expect(testView.state).eql(expectedState);
|
||||
@ -676,10 +676,8 @@ describe("loop.roomViews", function () {
|
||||
expect(node).to.not.eql(null);
|
||||
expect(node.querySelector(".room-context-thumbnail").src).to.
|
||||
eql(fakeContextURL.thumbnail);
|
||||
expect(node.querySelector(".room-context-description").textContent).to.
|
||||
eql(fakeContextURL.description);
|
||||
expect(node.querySelector(".room-context-comment").textContent).to.
|
||||
eql(view.props.roomData.roomDescription);
|
||||
expect(node.querySelector(".room-context-description").firstChild.textContent).
|
||||
to.eql(fakeContextURL.description);
|
||||
});
|
||||
|
||||
it("should not render optional data", function() {
|
||||
@ -727,12 +725,13 @@ describe("loop.roomViews", function () {
|
||||
expect(node.querySelector(".room-context-comments").value).to.eql(fakeContextURL.description);
|
||||
});
|
||||
|
||||
it("should show the checkbox as disabled when no context is available", function() {
|
||||
it("should show the checkbox as disabled when context is already set", function() {
|
||||
view = mountTestComponent({
|
||||
editMode: true,
|
||||
roomData: {
|
||||
roomToken: "fakeToken",
|
||||
roomName: "fakeName"
|
||||
roomName: "fakeName",
|
||||
roomContextUrls: [fakeContextURL]
|
||||
}
|
||||
});
|
||||
|
||||
@ -758,6 +757,7 @@ describe("loop.roomViews", function () {
|
||||
expect(view.state.availableContext.previewImage).to.eql(favicon);
|
||||
|
||||
var node = view.getDOMNode();
|
||||
expect(node.querySelector(".checkbox-wrapper").classList.contains("disabled")).to.eql(true);
|
||||
expect(node.querySelector(".room-context-name").value).to.eql(roomName);
|
||||
expect(node.querySelector(".room-context-url").value).to.eql(fakeContextURL.location);
|
||||
expect(node.querySelector(".room-context-comments").value).to.eql(fakeContextURL.description);
|
||||
@ -765,6 +765,28 @@ describe("loop.roomViews", function () {
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
it("should hide the checkbox when no context data is stored or available", function(next) {
|
||||
view = mountTestComponent({
|
||||
roomData: {
|
||||
roomToken: "fakeToken",
|
||||
roomName: "Hello, is it me you're looking for?"
|
||||
}
|
||||
});
|
||||
|
||||
// Switch to editMode via setting the prop, since we can control that
|
||||
// better.
|
||||
view.setProps({ editMode: true }, function() {
|
||||
// First check if availableContext is set correctly.
|
||||
expect(view.state.availableContext).to.not.eql(null);
|
||||
expect(view.state.availableContext.previewImage).to.eql(favicon);
|
||||
|
||||
var node = view.getDOMNode();
|
||||
expect(node.querySelector(".checkbox-wrapper").classList.contains("hide")).to.eql(true);
|
||||
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Update Room", function() {
|
||||
@ -785,13 +807,13 @@ describe("loop.roomViews", function () {
|
||||
roomNameBox = view.getDOMNode().querySelector(".room-context-name");
|
||||
});
|
||||
|
||||
it("should dispatch a UpdateRoomContext action when the focus is lost",
|
||||
it("should dispatch a UpdateRoomContext action when the save button is clicked",
|
||||
function() {
|
||||
React.addons.TestUtils.Simulate.change(roomNameBox, { target: {
|
||||
value: "reallyFake"
|
||||
}});
|
||||
|
||||
React.addons.TestUtils.Simulate.blur(roomNameBox);
|
||||
React.addons.TestUtils.Simulate.click(view.getDOMNode().querySelector(".btn-info"));
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
|
@ -640,7 +640,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
});
|
||||
|
||||
it("should set the state to MEDIA_WAIT if media devices are present", function() {
|
||||
sandbox.stub(loop.shared.utils, "hasAudioDevices").callsArgWith(0, true);
|
||||
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, true);
|
||||
|
||||
store.joinRoom();
|
||||
|
||||
@ -648,7 +648,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
});
|
||||
|
||||
it("should not set the state to MEDIA_WAIT if no media devices are present", function() {
|
||||
sandbox.stub(loop.shared.utils, "hasAudioDevices").callsArgWith(0, false);
|
||||
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, false);
|
||||
|
||||
store.joinRoom();
|
||||
|
||||
@ -656,7 +656,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
});
|
||||
|
||||
it("should dispatch `ConnectionFailure` if no media devices are present", function() {
|
||||
sandbox.stub(loop.shared.utils, "hasAudioDevices").callsArgWith(0, false);
|
||||
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, false);
|
||||
|
||||
store.joinRoom();
|
||||
|
||||
|
@ -141,7 +141,7 @@ describe("loop.shared.utils", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasAudioDevices", function() {
|
||||
describe("#hasAudioOrVideoDevices", function() {
|
||||
var fakeNavigatorObject, fakeWindowObject;
|
||||
|
||||
beforeEach(function() {
|
||||
@ -168,17 +168,18 @@ describe("loop.shared.utils", function() {
|
||||
delete fakeNavigatorObject.mediaDevices;
|
||||
delete fakeWindowObject.MediaStreamTrack;
|
||||
|
||||
sharedUtils.hasAudioDevices(function(result) {
|
||||
sharedUtils.hasAudioOrVideoDevices(function(result) {
|
||||
expect(result).eql(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should return false if no audio devices exist according to navigator.mediaDevices", function(done) {
|
||||
it("should return false if no audio nor video devices exist according to navigator.mediaDevices", function(done) {
|
||||
delete fakeWindowObject.MediaStreamTrack;
|
||||
|
||||
fakeNavigatorObject.mediaDevices.enumerateDevices.returns(Promise.resolve([]));
|
||||
sharedUtils.hasAudioDevices(function(result) {
|
||||
|
||||
sharedUtils.hasAudioOrVideoDevices(function(result) {
|
||||
try {
|
||||
expect(result).eql(false);
|
||||
done();
|
||||
@ -193,11 +194,6 @@ describe("loop.shared.utils", function() {
|
||||
|
||||
fakeNavigatorObject.mediaDevices.enumerateDevices.returns(
|
||||
Promise.resolve([{
|
||||
deviceId: "15234",
|
||||
groupId: "",
|
||||
kind: "videoinput",
|
||||
label: ""
|
||||
}, {
|
||||
deviceId: "54321",
|
||||
groupId: "",
|
||||
kind: "audioinput",
|
||||
@ -205,7 +201,7 @@ describe("loop.shared.utils", function() {
|
||||
}])
|
||||
);
|
||||
|
||||
sharedUtils.hasAudioDevices(function(result) {
|
||||
sharedUtils.hasAudioOrVideoDevices(function(result) {
|
||||
try {
|
||||
expect(result).eql(true);
|
||||
done();
|
||||
@ -215,11 +211,33 @@ describe("loop.shared.utils", function() {
|
||||
});
|
||||
});
|
||||
|
||||
it("should return false if no audio devices exist according to window.MediaStreamTrack", function(done) {
|
||||
it("should return true if video devices exist according to navigator.mediaDevices", function(done) {
|
||||
delete fakeWindowObject.MediaStreamTrack;
|
||||
|
||||
fakeNavigatorObject.mediaDevices.enumerateDevices.returns(
|
||||
Promise.resolve([{
|
||||
deviceId: "15234",
|
||||
groupId: "",
|
||||
kind: "videoinput",
|
||||
label: ""
|
||||
}])
|
||||
);
|
||||
|
||||
sharedUtils.hasAudioOrVideoDevices(function(result) {
|
||||
try {
|
||||
expect(result).eql(true);
|
||||
done();
|
||||
} catch (ex) {
|
||||
done(ex);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should return false if no audio nor video devices exist according to window.MediaStreamTrack", function(done) {
|
||||
delete fakeNavigatorObject.mediaDevices;
|
||||
|
||||
fakeWindowObject.MediaStreamTrack.getSources.callsArgWith(0, []);
|
||||
sharedUtils.hasAudioDevices(function(result) {
|
||||
sharedUtils.hasAudioOrVideoDevices(function(result) {
|
||||
try {
|
||||
expect(result).eql(false);
|
||||
done();
|
||||
@ -233,18 +251,33 @@ describe("loop.shared.utils", function() {
|
||||
delete fakeNavigatorObject.mediaDevices;
|
||||
|
||||
fakeWindowObject.MediaStreamTrack.getSources.callsArgWith(0, [{
|
||||
facing: "",
|
||||
id: "15234",
|
||||
kind: "video",
|
||||
label: ""
|
||||
}, {
|
||||
facing: "",
|
||||
id: "54321",
|
||||
kind: "audio",
|
||||
label: ""
|
||||
}]);
|
||||
|
||||
sharedUtils.hasAudioDevices(function(result) {
|
||||
sharedUtils.hasAudioOrVideoDevices(function(result) {
|
||||
try {
|
||||
expect(result).eql(true);
|
||||
done();
|
||||
} catch (ex) {
|
||||
done(ex);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should return true if video devices exist according to window.MediaStreamTrack", function(done) {
|
||||
delete fakeNavigatorObject.mediaDevices;
|
||||
|
||||
fakeWindowObject.MediaStreamTrack.getSources.callsArgWith(0, [{
|
||||
facing: "",
|
||||
id: "15234",
|
||||
kind: "video",
|
||||
label: ""
|
||||
}]);
|
||||
|
||||
sharedUtils.hasAudioOrVideoDevices(function(result) {
|
||||
try {
|
||||
expect(result).eql(true);
|
||||
done();
|
||||
|
15
browser/devtools/.eslintignore
Normal file
15
browser/devtools/.eslintignore
Normal file
@ -0,0 +1,15 @@
|
||||
# Ignore d3
|
||||
browser/devtools/shared/d3.js
|
||||
browser/devtools/webaudioeditor/lib/dagre-d3.js
|
||||
|
||||
# Ignore codemirror
|
||||
browser/devtools/sourceeditor/codemirror/*.js
|
||||
browser/devtools/sourceeditor/codemirror/**/*.js
|
||||
|
||||
# Ignore jquery test libs
|
||||
browser/devtools/markupview/test/lib_*
|
||||
|
||||
# Ignore pre-processed files
|
||||
browser/devtools/framework/toolbox-process-window.js
|
||||
browser/devtools/performance/system.js
|
||||
browser/devtools/webide/webide-prefs.js
|
406
browser/devtools/.eslintrc
Normal file
406
browser/devtools/.eslintrc
Normal file
@ -0,0 +1,406 @@
|
||||
{
|
||||
"env": {
|
||||
"es6": true
|
||||
},
|
||||
"globals": {
|
||||
"Cc": true,
|
||||
"Ci": true,
|
||||
"Components": true,
|
||||
"console": true,
|
||||
"Cr": true,
|
||||
"Cu": true,
|
||||
"devtools": true,
|
||||
"dump": true,
|
||||
"EventEmitter": true,
|
||||
"exports": true,
|
||||
"loader": true,
|
||||
"module": true,
|
||||
"require": true,
|
||||
"Services": true,
|
||||
"Task": true,
|
||||
"XPCOMUtils": true,
|
||||
},
|
||||
"rules": {
|
||||
// These are the rules that have been configured so far to match the
|
||||
// devtools coding style.
|
||||
|
||||
// Disallow using variables outside the blocks they are defined (especially
|
||||
// since only let and const are used, see "no-var").
|
||||
"block-scoped-var": 2,
|
||||
// Enforce one true brace style (opening brace on the same line) and avoid
|
||||
// start and end braces on the same line.
|
||||
"brace-style": [2, "1tbs", {"allowSingleLine": false}],
|
||||
// Require camel case names
|
||||
"camelcase": 2,
|
||||
// Disallow trailing commas. Not valid JSON notation.
|
||||
"comma-dangle": 1,
|
||||
// Enforce spacing before and after comma
|
||||
"comma-spacing": [2, {"before": false, "after": true}],
|
||||
// Enforce one true comma style.
|
||||
"comma-style": [2, "last"],
|
||||
// Warn about cyclomatic complexity in functions.
|
||||
"complexity": 1,
|
||||
// Require return statements to either always or never specify values.
|
||||
"consistent-return": 2,
|
||||
// Don't warn for inconsistent naming when capturing this (not so important
|
||||
// with auto-binding fat arrow functions).
|
||||
"consistent-this": 0,
|
||||
// Enforce curly brace conventions for all control statements.
|
||||
"curly": 2,
|
||||
// Don't require a default case in switch statements. Avoid being forced to
|
||||
// add a bogus default when you know all possible cases are handled.
|
||||
"default-case": 0,
|
||||
// Don't enforce consistent newlines before or after dots. This depends on
|
||||
// what gives the most readable code.
|
||||
"dot-location": [1, "object"],
|
||||
// Encourage the use of dot notation whenever possible.
|
||||
"dot-notation": 2,
|
||||
// Enforce newline at the end of file, with no multiple empty lines.
|
||||
"eol-last": 2,
|
||||
// Allow using == instead of ===, in the interest of landing something since
|
||||
// the devtools codebase is split on convention here.
|
||||
"eqeqeq": 0,
|
||||
// Don't require function expressions to have a name.
|
||||
// This makes the code more verbose and hard to read. Our engine already
|
||||
// does a fantastic job assigning a name to the function, which includes
|
||||
// the enclosing function name, and worst case you have a line number that
|
||||
// you can just look up.
|
||||
"func-names": 0,
|
||||
// Allow use of function declarations and expressions.
|
||||
"func-style": 0,
|
||||
// Deprecated, will be removed in 1.0.
|
||||
"generator-star": 0,
|
||||
// Enforce the spacing around the * in generator functions.
|
||||
"generator-star-spacing": [1, "after"],
|
||||
// Deprecated, will be removed in 1.0.
|
||||
"global-strict": 0,
|
||||
// Only useful in a node environment.
|
||||
"handle-callback-err": 0,
|
||||
// Tab width.
|
||||
"indent": [2, 2],
|
||||
// Enforces spacing between keys and values in object literal properties.
|
||||
"key-spacing": [1, {"beforeColon": false, "afterColon": true}],
|
||||
// Allow mixed 'LF' and 'CRLF' as linebreaks.
|
||||
"linebreak-style": 0,
|
||||
// Don't enforce the maximum depth that blocks can be nested. The complexity
|
||||
// rule is a better rule to check this.
|
||||
"max-depth": 0,
|
||||
// Maximum length of a line.
|
||||
"max-len": [1, 80],
|
||||
// Maximum depth callbacks can be nested.
|
||||
"max-nested-callbacks": [2, 3],
|
||||
// Don't limit the number of parameters that can be used in a function.
|
||||
"max-params": 0,
|
||||
// Don't limit the maximum number of statement allowed in a function. We
|
||||
// already have the complexity rule that's a better measurement.
|
||||
"max-statements": 0,
|
||||
// Require a capital letter for constructors, only check if all new
|
||||
// operators are followed by a capital letter. Don't warn when capitalized
|
||||
// functions are used without the new operator.
|
||||
"new-cap": [2, {"capIsNew": false}],
|
||||
// Disallow the omission of parentheses when invoking a constructor with no
|
||||
// arguments.
|
||||
"new-parens": 2,
|
||||
// Disallow use of the Array constructor.
|
||||
"no-array-constructor": 2,
|
||||
// Allow use of bitwise operators.
|
||||
"no-bitwise": 0,
|
||||
// Disallow use of arguments.caller or arguments.callee.
|
||||
"no-caller": 2,
|
||||
// Disallow the catch clause parameter name being the same as a variable in
|
||||
// the outer scope, to avoid confusion.
|
||||
"no-catch-shadow": 1,
|
||||
// Deprecated, will be removed in 1.0.
|
||||
"no-comma-dangle": 0,
|
||||
// Disallow assignment in conditional expressions.
|
||||
"no-cond-assign": 2,
|
||||
// Allow using the console API.
|
||||
"no-console": 0,
|
||||
// Allow using constant expressions in conditions like while (true)
|
||||
"no-constant-condition": 0,
|
||||
// Allow use of the continue statement.
|
||||
"no-continue": 0,
|
||||
// Disallow control characters in regular expressions.
|
||||
"no-control-regex": 2,
|
||||
// Disallow use of debugger.
|
||||
"no-debugger": 2,
|
||||
// Disallow deletion of variables (deleting properties is fine).
|
||||
"no-delete-var": 2,
|
||||
// Allow division operators explicitly at beginning of regular expression.
|
||||
"no-div-regex": 0,
|
||||
// Disallow duplicate arguments in functions.
|
||||
"no-dupe-args": 2,
|
||||
// Disallow duplicate keys when creating object literals.
|
||||
"no-dupe-keys": 2,
|
||||
// Disallow a duplicate case label.
|
||||
"no-duplicate-case": 2,
|
||||
// Disallow else after a return in an if. The else around the second return
|
||||
// here is useless:
|
||||
// if (something) { return false; } else { return true; }
|
||||
"no-else-return": 2,
|
||||
// Disallow empty statements. This will report an error for:
|
||||
// try { something(); } catch (e) {}
|
||||
// but will not report it for:
|
||||
// try { something(); } catch (e) { /* Silencing the error because ...*/ }
|
||||
// which is a valid use case.
|
||||
"no-empty": 2,
|
||||
// Disallow the use of empty character classes in regular expressions.
|
||||
"no-empty-class": 2,
|
||||
// Disallow use of labels for anything other then loops and switches.
|
||||
"no-empty-label": 2,
|
||||
// Disallow use of eval(). We have other APIs to evaluate code in content.
|
||||
"no-eval": 2,
|
||||
// Disallow assigning to the exception in a catch block.
|
||||
"no-ex-assign": 2,
|
||||
// Disallow adding to native types
|
||||
"no-extend-native": 2,
|
||||
// Disallow unnecessary function binding.
|
||||
"no-extra-bind": 2,
|
||||
// Disallow double-negation boolean casts in a boolean context.
|
||||
"no-extra-boolean-cast": 2,
|
||||
// Allow unnecessary parentheses, as they may make the code more readable.
|
||||
"no-extra-parens": 0,
|
||||
// Disallow unnecessary semicolons.
|
||||
"no-extra-semi": 2,
|
||||
// Deprecated, will be removed in 1.0.
|
||||
"no-extra-strict": 0,
|
||||
// Disallow fallthrough of case statements, except if there is a comment.
|
||||
"no-fallthrough": 2,
|
||||
// Allow the use of leading or trailing decimal points in numeric literals.
|
||||
"no-floating-decimal": 0,
|
||||
// Disallow comments inline after code.
|
||||
"no-inline-comments": 1,
|
||||
// Disallow if as the only statement in an else block.
|
||||
"no-lonely-if": 2,
|
||||
// Allow mixing regular variable and require declarations (not a node env).
|
||||
"no-mixed-requires": 0,
|
||||
// Disallow mixed spaces and tabs for indentation.
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
// Disallow use of multiple spaces (sometimes used to align const values,
|
||||
// array or object items, etc.). It's hard to maintain and doesn't add that
|
||||
// much benefit.
|
||||
"no-multi-spaces": 1,
|
||||
// Disallow use of multiline strings (use template strings instead).
|
||||
"no-multi-str": 1,
|
||||
// Disallow multiple empty lines.
|
||||
"no-multiple-empty-lines": [1, {"max": 1}],
|
||||
// Disallow reassignments of native objects.
|
||||
"no-native-reassign": 2,
|
||||
// Disallow nested ternary expressions, they make the code hard to read.
|
||||
"no-nested-ternary": 2,
|
||||
// Allow use of new operator with the require function.
|
||||
"no-new-require": 0,
|
||||
// Disallow use of octal literals.
|
||||
"no-octal": 1,
|
||||
// Allow reassignment of function parameters.
|
||||
"no-param-reassign": 0,
|
||||
// Allow string concatenation with __dirname and __filename (not a node env).
|
||||
"no-path-concat": 0,
|
||||
// Allow use of unary operators, ++ and --.
|
||||
"no-plusplus": 0,
|
||||
// Allow using process.env (not a node environment).
|
||||
"no-process-env": 0,
|
||||
// Allow using process.exit (not a node environment).
|
||||
"no-process-exit": 0,
|
||||
// Disallow usage of __proto__ property.
|
||||
"no-proto": 2,
|
||||
// Disallow declaring the same variable more than once (we use let anyway).
|
||||
"no-redeclare": 2,
|
||||
// Disallow multiple spaces in a regular expression literal.
|
||||
"no-regex-spaces": 2,
|
||||
// Allow reserved words being used as object literal keys.
|
||||
"no-reserved-keys": 0,
|
||||
// Don't restrict usage of specified node modules (not a node environment).
|
||||
"no-restricted-modules": 0,
|
||||
// Disallow use of assignment in return statement. It is preferable for a
|
||||
// single line of code to have only one easily predictable effect.
|
||||
"no-return-assign": 2,
|
||||
// Allow use of javascript: urls.
|
||||
"no-script-url": 0,
|
||||
// Disallow comparisons where both sides are exactly the same.
|
||||
"no-self-compare": 2,
|
||||
// Disallow use of comma operator.
|
||||
"no-sequences": 2,
|
||||
// Warn about declaration of variables already declared in the outer scope.
|
||||
// This isn't an error because it sometimes is useful to use the same name
|
||||
// in a small helper function rather than having to come up with another
|
||||
// random name.
|
||||
// Still, making this a warning can help people avoid being confused.
|
||||
"no-shadow": 1,
|
||||
// Disallow shadowing of names such as arguments.
|
||||
"no-shadow-restricted-names": 2,
|
||||
// Deprecated, will be removed in 1.0.
|
||||
"no-space-before-semi": 0,
|
||||
// Disallow space between function identifier and application.
|
||||
"no-spaced-func": 1,
|
||||
// Disallow sparse arrays, eg. let arr = [,,2].
|
||||
// Array destructuring is fine though:
|
||||
// for (let [, breakpointPromise] of aPromises)
|
||||
"no-sparse-arrays": 2,
|
||||
// Allow use of synchronous methods (not a node environment).
|
||||
"no-sync": 0,
|
||||
// Allow the use of ternary operators.
|
||||
"no-ternary": 0,
|
||||
// Disallow throwing literals (eg. throw "error" instead of
|
||||
// throw new Error("error")).
|
||||
"no-throw-literal": 2,
|
||||
// Disallow trailing whitespace at the end of lines.
|
||||
"no-trailing-spaces": 2,
|
||||
// Disallow use of undeclared variables unless mentioned in a /*global */
|
||||
// block.
|
||||
// This should really be a 2, but until we define all globals in comments
|
||||
// and .eslintrc, keeping this as a 1.
|
||||
"no-undef": 2,
|
||||
// Allow dangling underscores in identifiers (for privates).
|
||||
"no-underscore-dangle": 0,
|
||||
// Allow use of undefined variable.
|
||||
"no-undefined": 0,
|
||||
// Disallow the use of Boolean literals in conditional expressions.
|
||||
"no-unneeded-ternary": 2,
|
||||
// Disallow unreachable statements after a return, throw, continue, or break
|
||||
// statement.
|
||||
"no-unreachable": 2,
|
||||
// Disallow declaration of variables that are not used in the code
|
||||
"no-unused-vars": 2,
|
||||
// Allow using variables before they are defined.
|
||||
"no-use-before-define": 0,
|
||||
// Require let or const instead of var.
|
||||
"no-var": 2,
|
||||
// Allow using TODO/FIXME comments.
|
||||
"no-warning-comments": 0,
|
||||
// Disallow use of the with statement.
|
||||
"no-with": 2,
|
||||
// Don't require method and property shorthand syntax for object literals.
|
||||
// We use this in the code a lot, but not consistently, and this seems more
|
||||
// like something to check at code review time.
|
||||
"object-shorthand": 0,
|
||||
// Allow more than one variable declaration per function.
|
||||
"one-var": 0,
|
||||
// Disallow padding within blocks.
|
||||
"padded-blocks": [1, "never"],
|
||||
// Don't require quotes around object literal property names.
|
||||
"quote-props": 0,
|
||||
// Double quotes should be used.
|
||||
"quotes": [1, "double"],
|
||||
// Require use of the second argument for parseInt().
|
||||
"radix": 2,
|
||||
// Always require use of semicolons wherever they are valid.
|
||||
"semi": [1, "always"],
|
||||
// Enforce spacing after semicolons.
|
||||
"semi-spacing": [1, {"before": false, "after": true}],
|
||||
// Don't require to sort variables within the same declaration block.
|
||||
// Anyway, one-var is disabled.
|
||||
"sort-vars": 0,
|
||||
// Deprecated, will be removed in 1.0.
|
||||
"space-after-function-name": 0,
|
||||
// Require a space after keywords.
|
||||
"space-after-keywords": [1, "always"],
|
||||
// Deprecated, will be removed in 1.0.
|
||||
"space-after-function-name": 0,
|
||||
// Require a space before the start brace of a block.
|
||||
"space-before-blocks": [1, "always"],
|
||||
// Deprecated, will be removed in 1.0.
|
||||
"space-before-function-parentheses": 0,
|
||||
// Disallow space before function opening parenthesis.
|
||||
"space-before-function-paren": [1, "never"],
|
||||
// Disable the rule that checks if spaces inside {} and [] are there or not.
|
||||
// Our code is split on conventions, and it'd be nice to have 2 rules
|
||||
// instead, one for [] and one for {}. So, disabling until we write them.
|
||||
"space-in-brackets": 0,
|
||||
// Disallow spaces inside parentheses.
|
||||
"space-in-parens": [1, "never"],
|
||||
// Require spaces around operators, except for a|0.
|
||||
"space-infix-ops": [1, {"int32Hint": true}],
|
||||
// Require a space after return, throw, and case.
|
||||
"space-return-throw-case": 1,
|
||||
// Require spaces before/after unary operators (words on by default,
|
||||
// nonwords off by default).
|
||||
"space-unary-ops": [1, { "words": true, "nonwords": false }],
|
||||
// Deprecated, will be removed in 1.0.
|
||||
"space-unary-word-ops": 0,
|
||||
// Require a space immediately following the // in a line comment.
|
||||
"spaced-line-comment": [1, "always"],
|
||||
// Require "use strict" to be defined globally in the script.
|
||||
"strict": [2, "global"],
|
||||
// Disallow comparisons with the value NaN.
|
||||
"use-isnan": 2,
|
||||
// Warn about invalid JSDoc comments.
|
||||
// Disabled for now because of https://github.com/eslint/eslint/issues/2270
|
||||
// The rule fails on some jsdoc comments like in:
|
||||
// browser/devtools/webconsole/console-output.js
|
||||
"valid-jsdoc": 0,
|
||||
// Ensure that the results of typeof are compared against a valid string.
|
||||
"valid-typeof": 2,
|
||||
// Allow vars to be declared anywhere in the scope.
|
||||
"vars-on-top": 0,
|
||||
// Don't require immediate function invocation to be wrapped in parentheses.
|
||||
"wrap-iife": 0,
|
||||
// Don't require regex literals to be wrapped in parentheses (which
|
||||
// supposedly prevent them from being mistaken for division operators).
|
||||
"wrap-regex": 0,
|
||||
// Disallow Yoda conditions (where literal value comes first).
|
||||
"yoda": 2,
|
||||
|
||||
// And these are the rules that haven't been discussed so far, and that are
|
||||
// disabled for now until we introduce them, one at a time.
|
||||
|
||||
// Require for-in loops to have an if statement.
|
||||
"guard-for-in": 0,
|
||||
// allow/disallow an empty newline after var statement
|
||||
"newline-after-var": 0,
|
||||
// disallow the use of alert, confirm, and prompt
|
||||
"no-alert": 0,
|
||||
// disallow comparisons to null without a type-checking operator
|
||||
"no-eq-null": 0,
|
||||
// disallow overwriting functions written as function declarations
|
||||
"no-func-assign": 0,
|
||||
// disallow use of eval()-like methods
|
||||
"no-implied-eval": 0,
|
||||
// disallow function or variable declarations in nested blocks
|
||||
"no-inner-declarations": 0,
|
||||
// disallow invalid regular expression strings in the RegExp constructor
|
||||
"no-invalid-regexp": 0,
|
||||
// disallow irregular whitespace outside of strings and comments
|
||||
"no-irregular-whitespace": 0,
|
||||
// disallow usage of __iterator__ property
|
||||
"no-iterator": 0,
|
||||
// disallow labels that share a name with a variable
|
||||
"no-label-var": 0,
|
||||
// disallow use of labeled statements
|
||||
"no-labels": 0,
|
||||
// disallow unnecessary nested blocks
|
||||
"no-lone-blocks": 0,
|
||||
// disallow creation of functions within loops
|
||||
"no-loop-func": 0,
|
||||
// disallow negation of the left operand of an in expression
|
||||
"no-negated-in-lhs": 0,
|
||||
// disallow use of new operator when not part of the assignment or
|
||||
// comparison
|
||||
"no-new": 0,
|
||||
// disallow use of new operator for Function object
|
||||
"no-new-func": 0,
|
||||
// disallow use of the Object constructor
|
||||
"no-new-object": 0,
|
||||
// disallows creating new instances of String,Number, and Boolean
|
||||
"no-new-wrappers": 0,
|
||||
// disallow the use of object properties of the global object (Math and
|
||||
// JSON) as functions
|
||||
"no-obj-calls": 0,
|
||||
// disallow use of octal escape sequences in string literals, such as
|
||||
// var foo = "Copyright \251";
|
||||
"no-octal-escape": 0,
|
||||
// disallow use of undefined when initializing variables
|
||||
"no-undef-init": 0,
|
||||
// disallow usage of expressions in statement position
|
||||
"no-unused-expressions": 0,
|
||||
// disallow use of void operator
|
||||
"no-void": 0,
|
||||
// disallow wrapping of non-IIFE statements in parens
|
||||
"no-wrap-func": 0,
|
||||
// require assignment operator shorthand where possible or prohibit it
|
||||
// entirely
|
||||
"operator-assignment": 0,
|
||||
// enforce operators to be placed before or after line breaks
|
||||
"operator-linebreak": 0,
|
||||
}
|
||||
}
|
50
browser/devtools/.eslintrc.mochitests
Normal file
50
browser/devtools/.eslintrc.mochitests
Normal file
@ -0,0 +1,50 @@
|
||||
// Parent config file for all devtools browser mochitest files.
|
||||
{
|
||||
"rules": {
|
||||
// Only disallow non-global unused vars, so that things like the test
|
||||
// function do not produce errors.
|
||||
"no-unused-vars": [2, {"vars": "local"}],
|
||||
// Allow using undefined variables so that tests can refer to functions
|
||||
// and variables defined in head.js files, without having to maintain a
|
||||
// list of globals in each .eslintrc file.
|
||||
// Note that bug 1168340 will eventually help auto-registering globals
|
||||
// from head.js files.
|
||||
"no-undef": 0,
|
||||
"block-scoped-var": 0
|
||||
},
|
||||
// All globals made available in the test environment.
|
||||
"globals": {
|
||||
"add_task": true,
|
||||
"Assert": true,
|
||||
"content": true,
|
||||
"document": true,
|
||||
"EventUtils": true,
|
||||
"executeSoon": true,
|
||||
"export_assertions": true,
|
||||
"finish": true,
|
||||
"gBrowser": true,
|
||||
"gDevTools": true,
|
||||
"getRootDirectory": true,
|
||||
"getTestFilePath": true,
|
||||
"gTestPath": true,
|
||||
"info": true,
|
||||
"is": true,
|
||||
"isnot": true,
|
||||
"navigator": true,
|
||||
"ok": true,
|
||||
"promise": true,
|
||||
"registerCleanupFunction": true,
|
||||
"requestLongerTimeout": true,
|
||||
"setTimeout": true,
|
||||
"SimpleTest": true,
|
||||
"SpecialPowers": true,
|
||||
"test": true,
|
||||
"todo": true,
|
||||
"todo_is": true,
|
||||
"todo_isnot": true,
|
||||
"waitForClipboard": true,
|
||||
"waitForExplicitFinish": true,
|
||||
"waitForFocus": true,
|
||||
"window": true,
|
||||
}
|
||||
}
|
51
browser/devtools/.eslintrc.xpcshell
Normal file
51
browser/devtools/.eslintrc.xpcshell
Normal file
@ -0,0 +1,51 @@
|
||||
// Parent config file for all devtools browser mochitest files.
|
||||
{
|
||||
"rules": {
|
||||
// Allow non-camelcase so that run_test doesn't produce a warning.
|
||||
"camelcase": 0,
|
||||
// Only disallow non-global unused vars, so that things like the test
|
||||
// function do not produce errors.
|
||||
"no-unused-vars": [2, {"vars": "local"}],
|
||||
// Allow using undefined variables so that tests can refer to functions
|
||||
// and variables defined in head.js files, without having to maintain a
|
||||
// list of globals in each .eslintrc file.
|
||||
// Note that bug 1168340 will eventually help auto-registering globals
|
||||
// from head.js files.
|
||||
"no-undef": 0,
|
||||
"block-scoped-var": 0
|
||||
},
|
||||
// All globals made available in the test environment.
|
||||
"globals": {
|
||||
"add_task": true,
|
||||
"add_test": true,
|
||||
"Assert": true,
|
||||
"deepEqual": true,
|
||||
"do_check_eq": true,
|
||||
"do_check_false": true,
|
||||
"do_check_neq": true,
|
||||
"do_check_null": true,
|
||||
"do_check_true": true,
|
||||
"do_execute_soon": true,
|
||||
"do_get_cwd": true,
|
||||
"do_get_file": true,
|
||||
"do_get_idle": true,
|
||||
"do_get_profile": true,
|
||||
"do_load_module": true,
|
||||
"do_parse_document": true,
|
||||
"do_print": true,
|
||||
"do_register_cleanup": true,
|
||||
"do_test_finished": true,
|
||||
"do_test_pending": true,
|
||||
"do_throw": true,
|
||||
"do_timeout": true,
|
||||
"equal": true,
|
||||
"load": true,
|
||||
"notDeepEqual": true,
|
||||
"notEqual": true,
|
||||
"notStrictEqual": true,
|
||||
"ok": true,
|
||||
"run_next_test": true,
|
||||
"run_test": true,
|
||||
"strictEqual": true,
|
||||
}
|
||||
}
|
4
browser/devtools/animationinspector/test/.eslintrc
Normal file
4
browser/devtools/animationinspector/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/app-manager/test/.eslintrc
Normal file
4
browser/devtools/app-manager/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/canvasdebugger/test/.eslintrc
Normal file
4
browser/devtools/canvasdebugger/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/commandline/test/.eslintrc
Normal file
4
browser/devtools/commandline/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
@ -63,6 +63,7 @@ support-files =
|
||||
[browser_cmd_jsb.js]
|
||||
support-files =
|
||||
browser_cmd_jsb_script.jsi
|
||||
[browser_cmd_listen.js]
|
||||
[browser_cmd_media.js]
|
||||
support-files =
|
||||
browser_cmd_media.html
|
||||
|
79
browser/devtools/commandline/test/browser_cmd_listen.js
Normal file
79
browser/devtools/commandline/test/browser_cmd_listen.js
Normal file
@ -0,0 +1,79 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that the listen/unlisten commands work as they should.
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/commandline/"+
|
||||
"test/browser_cmd_cookie.html";
|
||||
|
||||
function test() {
|
||||
return Task.spawn(testTask).then(finish, helpers.handleError);
|
||||
}
|
||||
|
||||
let tests = {
|
||||
testInput: function(options) {
|
||||
return helpers.audit(options, [
|
||||
{
|
||||
setup: 'listen',
|
||||
check: {
|
||||
input: 'listen',
|
||||
markup: 'VVVVVV',
|
||||
status: 'VALID'
|
||||
},
|
||||
},
|
||||
{
|
||||
setup: 'unlisten',
|
||||
check: {
|
||||
input: 'unlisten',
|
||||
markup: 'VVVVVVVV',
|
||||
status: 'VALID'
|
||||
},
|
||||
exec: {
|
||||
output: 'All TCP ports closed'
|
||||
}
|
||||
},
|
||||
{
|
||||
setup: function() {
|
||||
return helpers.setInput(options, 'listen');
|
||||
},
|
||||
check: {
|
||||
input: 'listen',
|
||||
hints: ' [port]',
|
||||
markup: 'VVVVVV',
|
||||
status: 'VALID'
|
||||
},
|
||||
exec: {
|
||||
output: 'Listening on port 6080'
|
||||
}
|
||||
},
|
||||
{
|
||||
setup: function() {
|
||||
return helpers.setInput(options, 'listen 8000');
|
||||
},
|
||||
exec: {
|
||||
output: 'Listening on port 8000'
|
||||
}
|
||||
},
|
||||
{
|
||||
setup: function() {
|
||||
return helpers.setInput(options, 'unlisten');
|
||||
},
|
||||
exec: {
|
||||
output: 'All TCP ports closed'
|
||||
}
|
||||
}
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
function* testTask() {
|
||||
Services.prefs.setBoolPref('devtools.debugger.remote-enabled', true);
|
||||
let options = yield helpers.openTab(TEST_URI);
|
||||
yield helpers.openToolbar(options);
|
||||
|
||||
yield helpers.runTests(options, tests);
|
||||
|
||||
yield helpers.closeToolbar(options);
|
||||
yield helpers.closeTab(options);
|
||||
Services.prefs.clearUserPref('devtools.debugger.remote-enabled');
|
||||
}
|
4
browser/devtools/debugger/test/.eslintrc
Normal file
4
browser/devtools/debugger/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/eyedropper/test/.eslintrc
Normal file
4
browser/devtools/eyedropper/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/fontinspector/test/.eslintrc
Normal file
4
browser/devtools/fontinspector/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/framework/test/.eslintrc
Normal file
4
browser/devtools/framework/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/inspector/test/.eslintrc
Normal file
4
browser/devtools/inspector/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/layoutview/test/.eslintrc
Normal file
4
browser/devtools/layoutview/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/markupview/test/.eslintrc
Normal file
4
browser/devtools/markupview/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/netmonitor/test/.eslintrc
Normal file
4
browser/devtools/netmonitor/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
@ -104,6 +104,21 @@ const CATEGORY_MAPPINGS = {
|
||||
* for `.marker-details-bullet.{COLORNAME}` for the equivilent
|
||||
* entry in ./browser/themes/shared/devtools/performance.inc.css
|
||||
* https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors
|
||||
* - collapseFunc: A function determining how markers are collapsed together.
|
||||
* Invoked with 3 arguments: the current parent marker, the
|
||||
* current marker and a method for peeking i markers ahead. If
|
||||
* nothing is returned, the marker is added as a standalone entry
|
||||
* in the waterfall. Otherwise, an object needs to be returned
|
||||
* with the following properties:
|
||||
* - toParent: The parent marker name (needs to be an entry in
|
||||
* the `TIMELINE_BLUEPRINT` itself).
|
||||
* - withData: An object containing some properties to staple
|
||||
* on the parent marker.
|
||||
* - forceNew: True if a new parent marker needs to be created
|
||||
* even though there is one currently available
|
||||
* with the same name.
|
||||
* - forceEnd: True if the current parent marker is full after
|
||||
* this collapse operation and should be finalized.
|
||||
* - fields: An optional array of marker properties you wish to display in the
|
||||
* marker details view. For example, a field in the array such as
|
||||
* { property: "aCauseName", label: "Cause" } would render a string
|
||||
@ -127,51 +142,64 @@ const TIMELINE_BLUEPRINT = {
|
||||
"Styles": {
|
||||
group: 0,
|
||||
colorName: "graphs-purple",
|
||||
collapseFunc: collapseConsecutiveIdentical,
|
||||
label: L10N.getStr("timeline.label.styles2"),
|
||||
fields: getStylesFields,
|
||||
},
|
||||
"Reflow": {
|
||||
group: 0,
|
||||
colorName: "graphs-purple",
|
||||
label: L10N.getStr("timeline.label.reflow2")
|
||||
collapseFunc: collapseConsecutiveIdentical,
|
||||
label: L10N.getStr("timeline.label.reflow2"),
|
||||
},
|
||||
"Paint": {
|
||||
group: 0,
|
||||
colorName: "graphs-green",
|
||||
label: L10N.getStr("timeline.label.paint")
|
||||
collapseFunc: collapseConsecutiveIdentical,
|
||||
label: L10N.getStr("timeline.label.paint"),
|
||||
},
|
||||
|
||||
/* Group 1 - JS */
|
||||
"DOMEvent": {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
collapseFunc: collapseDOMIntoDOMJS,
|
||||
label: L10N.getStr("timeline.label.domevent"),
|
||||
fields: getDOMEventFields,
|
||||
},
|
||||
"Javascript": {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
collapseFunc: either(collapseJSIntoDOMJS, collapseConsecutiveIdentical),
|
||||
label: getJSLabel,
|
||||
fields: getJSFields,
|
||||
},
|
||||
"meta::DOMEvent+JS": {
|
||||
colorName: "graphs-yellow",
|
||||
label: getDOMJSLabel,
|
||||
fields: getDOMEventFields,
|
||||
},
|
||||
"Parse HTML": {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
label: L10N.getStr("timeline.label.parseHTML")
|
||||
collapseFunc: collapseConsecutiveIdentical,
|
||||
label: L10N.getStr("timeline.label.parseHTML"),
|
||||
},
|
||||
"Parse XML": {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
label: L10N.getStr("timeline.label.parseXML")
|
||||
collapseFunc: collapseConsecutiveIdentical,
|
||||
label: L10N.getStr("timeline.label.parseXML"),
|
||||
},
|
||||
"GarbageCollection": {
|
||||
group: 1,
|
||||
colorName: "graphs-red",
|
||||
collapseFunc: collapseAdjacentGC,
|
||||
label: getGCLabel,
|
||||
fields: [
|
||||
{ property: "causeName", label: "Reason:" },
|
||||
{ property: "nonincrementalReason", label: "Non-incremental Reason:" }
|
||||
]
|
||||
],
|
||||
},
|
||||
|
||||
/* Group 2 - User Controlled */
|
||||
@ -182,7 +210,7 @@ const TIMELINE_BLUEPRINT = {
|
||||
fields: [{
|
||||
property: "causeName",
|
||||
label: L10N.getStr("timeline.markerDetail.consoleTimerName")
|
||||
}]
|
||||
}],
|
||||
},
|
||||
"TimeStamp": {
|
||||
group: 2,
|
||||
@ -191,10 +219,83 @@ const TIMELINE_BLUEPRINT = {
|
||||
fields: [{
|
||||
property: "causeName",
|
||||
label: "Label:"
|
||||
}]
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper for creating a function that returns the first defined result from
|
||||
* a list of functions passed in as params, in order.
|
||||
* @param ...function fun
|
||||
* @return any
|
||||
*/
|
||||
function either(...fun) {
|
||||
return function() {
|
||||
for (let f of fun) {
|
||||
let result = f.apply(null, arguments);
|
||||
if (result !== undefined) return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A series of collapsers used by the blueprint. These functions are
|
||||
* consecutively invoked on a moving window of two markers.
|
||||
*/
|
||||
|
||||
function collapseConsecutiveIdentical(parent, curr, peek) {
|
||||
// If there is a parent marker currently being filled and the current marker
|
||||
// should go into the parent marker, make it so.
|
||||
if (parent && parent.name == curr.name) {
|
||||
return { toParent: parent.name };
|
||||
}
|
||||
// Otherwise if the current marker is the same type as the next marker type,
|
||||
// create a new parent marker containing the current marker.
|
||||
let next = peek(1);
|
||||
if (next && curr.name == next.name) {
|
||||
return { toParent: curr.name };
|
||||
}
|
||||
}
|
||||
|
||||
function collapseAdjacentGC(parent, curr, peek) {
|
||||
let next = peek(1);
|
||||
if (next && (next.start < curr.end || next.start - curr.end <= 10 /* ms */)) {
|
||||
return collapseConsecutiveIdentical(parent, curr, peek);
|
||||
}
|
||||
}
|
||||
|
||||
function collapseDOMIntoDOMJS(parent, curr, peek) {
|
||||
// If the next marker is a JavaScript marker, create a new meta parent marker
|
||||
// containing the current marker.
|
||||
let next = peek(1);
|
||||
if (next && next.name == "Javascript") {
|
||||
return {
|
||||
forceNew: true,
|
||||
toParent: "meta::DOMEvent+JS",
|
||||
withData: {
|
||||
type: curr.type,
|
||||
eventPhase: curr.eventPhase
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function collapseJSIntoDOMJS(parent, curr, peek) {
|
||||
// If there is a parent marker currently being filled, and it's the one
|
||||
// created from a `DOMEvent` via `collapseDOMIntoDOMJS`, then the current
|
||||
// marker has to go into that one.
|
||||
if (parent && parent.name == "meta::DOMEvent+JS") {
|
||||
return {
|
||||
forceEnd: true,
|
||||
toParent: "meta::DOMEvent+JS",
|
||||
withData: {
|
||||
stack: curr.stack,
|
||||
endStack: curr.endStack
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A series of formatters used by the blueprint.
|
||||
*/
|
||||
@ -236,6 +337,10 @@ function getJSLabel (marker={}) {
|
||||
return generic;
|
||||
}
|
||||
|
||||
function getDOMJSLabel (marker={}) {
|
||||
return `Event (${marker.type})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash for computing a fields object for a JS marker. If the cause
|
||||
* is considered content (so an entry exists in the JS_MARKER_MAP), do not display it
|
||||
|
122
browser/devtools/performance/modules/logic/waterfall-utils.js
Normal file
122
browser/devtools/performance/modules/logic/waterfall-utils.js
Normal file
@ -0,0 +1,122 @@
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* Utility functions for collapsing markers into a waterfall.
|
||||
*/
|
||||
|
||||
loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
|
||||
"devtools/performance/global", true);
|
||||
|
||||
/**
|
||||
* Collapses markers into a tree-like structure. Currently, this only goes
|
||||
* one level deep.
|
||||
* @param object markerNode
|
||||
* @param array markersList
|
||||
*/
|
||||
function collapseMarkersIntoNode({ markerNode, markersList }) {
|
||||
let [getOrCreateParentNode, getCurrentParentNode, clearParentNode] = makeParentNodeFactory();
|
||||
|
||||
for (let i = 0, len = markersList.length; i < len; i++) {
|
||||
let curr = markersList[i];
|
||||
let blueprint = TIMELINE_BLUEPRINT[curr.name];
|
||||
|
||||
let parentNode = getCurrentParentNode();
|
||||
let collapse = blueprint.collapseFunc || (() => null);
|
||||
let peek = distance => markersList[i + distance];
|
||||
let collapseInfo = collapse(parentNode, curr, peek);
|
||||
|
||||
if (collapseInfo) {
|
||||
let { toParent, withData, forceNew, forceEnd } = collapseInfo;
|
||||
|
||||
// If the `forceNew` prop is set on the collapse info, then a new parent
|
||||
// marker needs to be created even if there is one already available.
|
||||
if (forceNew) {
|
||||
clearParentNode();
|
||||
}
|
||||
// If the `toParent` prop is set on the collapse info, then this marker
|
||||
// can be collapsed into a higher-level parent marker.
|
||||
if (toParent) {
|
||||
let parentNode = getOrCreateParentNode(markerNode, toParent, curr.start);
|
||||
parentNode.end = curr.end;
|
||||
parentNode.submarkers.push(curr);
|
||||
for (let key in withData) {
|
||||
parentNode[key] = withData[key];
|
||||
}
|
||||
}
|
||||
// If the `forceEnd` prop is set on the collapse info, then the higher-level
|
||||
// parent marker is full and should be finalized.
|
||||
if (forceEnd) {
|
||||
clearParentNode();
|
||||
}
|
||||
} else {
|
||||
clearParentNode();
|
||||
markerNode.submarkers.push(curr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty parent marker, which functions like a regular marker,
|
||||
* but is able to hold additional child markers.
|
||||
* @param string name
|
||||
* @param number start [optional]
|
||||
* @param number end [optional]
|
||||
* @return object
|
||||
*/
|
||||
function makeEmptyMarkerNode(name, start, end) {
|
||||
return {
|
||||
name: name,
|
||||
start: start,
|
||||
end: end,
|
||||
submarkers: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a factory for markers containing other markers.
|
||||
* @return array[function]
|
||||
*/
|
||||
function makeParentNodeFactory() {
|
||||
let marker;
|
||||
|
||||
return [
|
||||
/**
|
||||
* Gets the current parent marker for the given marker name. If it doesn't
|
||||
* exist, it creates it and appends it to another parent marker.
|
||||
* @param object owner
|
||||
* @param string name
|
||||
* @param number start
|
||||
* @return object
|
||||
*/
|
||||
function getOrCreateParentNode(owner, name, start) {
|
||||
if (marker && marker.name == name) {
|
||||
return marker;
|
||||
} else {
|
||||
marker = makeEmptyMarkerNode(name, start);
|
||||
owner.submarkers.push(marker);
|
||||
return marker;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the current marker marker.
|
||||
* @return object
|
||||
*/
|
||||
function getCurrentParentNode() {
|
||||
return marker;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the current marker marker.
|
||||
*/
|
||||
function clearParentNode() {
|
||||
marker = null;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
exports.makeEmptyMarkerNode = makeEmptyMarkerNode;
|
||||
exports.collapseMarkersIntoNode = collapseMarkersIntoNode;
|
@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
|
||||
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
@ -29,27 +28,29 @@ loader.lazyRequireGetter(this, "MarkerUtils",
|
||||
*/
|
||||
function MarkerDetails(parent, splitter) {
|
||||
EventEmitter.decorate(this);
|
||||
this._onClick = this._onClick.bind(this);
|
||||
|
||||
this._document = parent.ownerDocument;
|
||||
this._parent = parent;
|
||||
this._splitter = splitter;
|
||||
this._splitter.addEventListener("mouseup", () => this.emit("resize"));
|
||||
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onSplitterMouseUp = this._onSplitterMouseUp.bind(this);
|
||||
|
||||
this._parent.addEventListener("click", this._onClick);
|
||||
this._splitter.addEventListener("mouseup", this._onSplitterMouseUp);
|
||||
}
|
||||
|
||||
MarkerDetails.prototype = {
|
||||
/**
|
||||
* Removes any node references from this view.
|
||||
* Sets this view's width.
|
||||
* @param boolean
|
||||
*/
|
||||
destroy: function() {
|
||||
this.empty();
|
||||
this._parent.removeEventListener("click", this._onClick);
|
||||
this._parent = null;
|
||||
this._splitter = null;
|
||||
set width(value) {
|
||||
this._parent.setAttribute("width", value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the view.
|
||||
* Clears the marker details from this view.
|
||||
*/
|
||||
empty: function() {
|
||||
this._parent.innerHTML = "";
|
||||
@ -60,8 +61,8 @@ MarkerDetails.prototype = {
|
||||
*
|
||||
* @param object params
|
||||
* An options object holding:
|
||||
* marker - The marker to display.
|
||||
* frames - Array of stack frame information; see stack.js.
|
||||
* - marker: The marker to display.
|
||||
* - frames: Array of stack frame information; see stack.js.
|
||||
*/
|
||||
render: function({ marker, frames }) {
|
||||
this.empty();
|
||||
@ -69,10 +70,10 @@ MarkerDetails.prototype = {
|
||||
let elements = [];
|
||||
elements.push(MarkerUtils.DOM.buildTitle(this._document, marker));
|
||||
elements.push(MarkerUtils.DOM.buildDuration(this._document, marker));
|
||||
MarkerUtils.DOM.buildFields(this._document, marker).forEach(field => elements.push(field));
|
||||
MarkerUtils.DOM.buildFields(this._document, marker).forEach(f => elements.push(f));
|
||||
|
||||
// Build a stack element -- and use the "startStack" label if
|
||||
// we have both a star and endStack.
|
||||
// we have both a startStack and endStack.
|
||||
if (marker.stack) {
|
||||
let type = marker.endStack ? "startStack" : "stack";
|
||||
elements.push(MarkerUtils.DOM.buildStackTrace(this._document, {
|
||||
@ -98,6 +99,13 @@ MarkerDetails.prototype = {
|
||||
this.emit("view-source", data.url, data.line);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the "mouseup" event on the marker details view splitter.
|
||||
*/
|
||||
_onSplitterMouseUp: function() {
|
||||
this.emit("resize");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
305
browser/devtools/performance/modules/widgets/marker-view.js
Normal file
305
browser/devtools/performance/modules/widgets/marker-view.js
Normal file
@ -0,0 +1,305 @@
|
||||
/* 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 file contains the "marker" view, essentially a detailed list
|
||||
* of all the markers in the timeline data.
|
||||
*/
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
const { AbstractTreeItem } = require("resource:///modules/devtools/AbstractTreeItem.jsm");
|
||||
const { TIMELINE_BLUEPRINT: ORIGINAL_BP } = require("devtools/performance/global");
|
||||
|
||||
loader.lazyRequireGetter(this, "MarkerUtils",
|
||||
"devtools/performance/marker-utils");
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const LEVEL_INDENT = 10; // px
|
||||
const ARROW_NODE_OFFSET = -15; // px
|
||||
const WATERFALL_MARKER_SIDEBAR_WIDTH = 175; // px
|
||||
const WATERFALL_MARKER_TIMEBAR_WIDTH_MIN = 5; // px
|
||||
|
||||
/**
|
||||
* A detailed waterfall view for the timeline data.
|
||||
*
|
||||
* @param MarkerView owner
|
||||
* The MarkerView considered the "owner" marker. This newly created
|
||||
* instance will be represent the "submarker". Should be null for root nodes.
|
||||
* @param object marker
|
||||
* Details about this marker, like { name, start, end, submarkers } etc.
|
||||
* @param number level [optional]
|
||||
* The indentation level in the waterfall tree. The root node is at level 0.
|
||||
* @param boolean hidden [optional]
|
||||
* Whether this node should be hidden and not contribute to depth/level
|
||||
* calculations. Defaults to false.
|
||||
*/
|
||||
function MarkerView({ owner, marker, level, hidden }) {
|
||||
AbstractTreeItem.call(this, {
|
||||
parent: owner,
|
||||
level: level|0 - (hidden ? 1 : 0)
|
||||
});
|
||||
|
||||
this.marker = marker;
|
||||
this.hidden = !!hidden;
|
||||
|
||||
this._onItemBlur = this._onItemBlur.bind(this);
|
||||
this._onItemFocus = this._onItemFocus.bind(this);
|
||||
}
|
||||
|
||||
MarkerView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
/**
|
||||
* Calculates and stores the available width for the waterfall.
|
||||
* This should be invoked every time the container node is resized.
|
||||
*/
|
||||
recalculateBounds: function() {
|
||||
this.root._waterfallWidth = this.bounds.width - WATERFALL_MARKER_SIDEBAR_WIDTH;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a list of names and colors used to paint markers.
|
||||
* @see TIMELINE_BLUEPRINT in timeline/widgets/global.js
|
||||
* @param object blueprint
|
||||
*/
|
||||
set blueprint(blueprint) {
|
||||
this.root._blueprint = blueprint;
|
||||
},
|
||||
get blueprint() {
|
||||
return this.root._blueprint;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the { startTime, endTime }, in milliseconds.
|
||||
* @param object interval
|
||||
*/
|
||||
set interval(interval) {
|
||||
this.root._interval = interval;
|
||||
},
|
||||
get interval() {
|
||||
return this.root._interval;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the current waterfall width.
|
||||
* @return number
|
||||
*/
|
||||
getWaterfallWidth: function() {
|
||||
return this._waterfallWidth;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the data scale amount for the current width and interval.
|
||||
* @return number
|
||||
*/
|
||||
getDataScale: function() {
|
||||
let startTime = this.root._interval.startTime|0;
|
||||
let endTime = this.root._interval.endTime|0;
|
||||
return this.root._waterfallWidth / (endTime - startTime);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the view for this waterfall node.
|
||||
* @param nsIDOMNode document
|
||||
* @param nsIDOMNode arrowNode
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
_displaySelf: function(document, arrowNode) {
|
||||
let targetNode = document.createElement("hbox");
|
||||
targetNode.className = "waterfall-tree-item";
|
||||
|
||||
if (this == this.root) {
|
||||
// Bounds are needed for properly positioning and scaling markers in
|
||||
// the waterfall, but it's sufficient to make those calculations only
|
||||
// for the root node.
|
||||
this.root.recalculateBounds();
|
||||
// The AbstractTreeItem propagates events to the root, so we don't
|
||||
// need to listen them on descendant items in the tree.
|
||||
this._addEventListeners();
|
||||
} else {
|
||||
// Root markers are an implementation detail and shouldn't be shown.
|
||||
this._buildMarkerCells(document, targetNode, arrowNode);
|
||||
}
|
||||
|
||||
if (this.hidden) {
|
||||
targetNode.style.display = "none";
|
||||
}
|
||||
|
||||
return targetNode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates this node in the waterfall tree with the corresponding "markers".
|
||||
* @param array:AbstractTreeItem children
|
||||
*/
|
||||
_populateSelf: function(children) {
|
||||
let submarkers = this.marker.submarkers;
|
||||
if (!submarkers || !submarkers.length) {
|
||||
return;
|
||||
}
|
||||
let blueprint = this.root._blueprint;
|
||||
let startTime = this.root._interval.startTime;
|
||||
let endTime = this.root._interval.endTime;
|
||||
let newLevel = this.level + 1;
|
||||
|
||||
for (let i = 0, len = submarkers.length; i < len; i++) {
|
||||
let marker = submarkers[i];
|
||||
|
||||
// If this marker isn't in the global timeline blueprint, don't display
|
||||
// it, but dump a warning message to the console.
|
||||
if (!(marker.name in blueprint)) {
|
||||
if (!(marker.name in ORIGINAL_BP)) {
|
||||
console.warn(`Marker not found in timeline blueprint: ${marker.name}.`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!isMarkerInRange(marker, startTime|0, endTime|0)) {
|
||||
continue;
|
||||
}
|
||||
children.push(new MarkerView({
|
||||
owner: this,
|
||||
marker: marker,
|
||||
level: newLevel,
|
||||
inverted: this.inverted
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Builds all the nodes representing a marker in the waterfall.
|
||||
* @param nsIDOMNode document
|
||||
* @param nsIDOMNode targetNode
|
||||
* @param nsIDOMNode arrowNode
|
||||
*/
|
||||
_buildMarkerCells: function(doc, targetNode, arrowNode) {
|
||||
// Root markers are an implementation detail and shouldn't be shown.
|
||||
let marker = this.marker;
|
||||
if (marker.name == "(root)") {
|
||||
return;
|
||||
}
|
||||
|
||||
let style = this.root._blueprint[marker.name];
|
||||
let startTime = this.root._interval.startTime;
|
||||
let endTime = this.root._interval.endTime;
|
||||
|
||||
let sidebarCell = this._buildMarkerSidebar(
|
||||
doc, style, marker);
|
||||
|
||||
let timebarCell = this._buildMarkerTimebar(
|
||||
doc, style, marker, startTime, endTime, arrowNode);
|
||||
|
||||
targetNode.appendChild(sidebarCell);
|
||||
targetNode.appendChild(timebarCell);
|
||||
|
||||
// Don't render an expando-arrow for leaf nodes.
|
||||
let submarkers = this.marker.submarkers;
|
||||
let hasDescendants = submarkers && submarkers.length > 0;
|
||||
if (hasDescendants) {
|
||||
targetNode.setAttribute("expandable", "");
|
||||
} else {
|
||||
arrowNode.setAttribute("invisible", "");
|
||||
}
|
||||
|
||||
targetNode.setAttribute("level", this.level);
|
||||
},
|
||||
|
||||
/**
|
||||
* Functions creating each cell in this waterfall view.
|
||||
* Invoked by `_displaySelf`.
|
||||
*/
|
||||
_buildMarkerSidebar: function(doc, style, marker) {
|
||||
let cell = doc.createElement("hbox");
|
||||
cell.className = "waterfall-sidebar theme-sidebar";
|
||||
cell.setAttribute("width", WATERFALL_MARKER_SIDEBAR_WIDTH);
|
||||
cell.setAttribute("align", "center");
|
||||
|
||||
let bullet = doc.createElement("hbox");
|
||||
bullet.className = `waterfall-marker-bullet marker-color-${style.colorName}`;
|
||||
bullet.style.transform = `translateX(${this.level * LEVEL_INDENT}px)`;
|
||||
bullet.setAttribute("type", marker.name);
|
||||
cell.appendChild(bullet);
|
||||
|
||||
let name = doc.createElement("description");
|
||||
let label = MarkerUtils.getMarkerLabel(marker);
|
||||
name.className = "plain waterfall-marker-name";
|
||||
name.style.transform = `translateX(${this.level * LEVEL_INDENT}px)`;
|
||||
name.setAttribute("crop", "end");
|
||||
name.setAttribute("flex", "1");
|
||||
name.setAttribute("value", label);
|
||||
name.setAttribute("tooltiptext", label);
|
||||
cell.appendChild(name);
|
||||
|
||||
return cell;
|
||||
},
|
||||
_buildMarkerTimebar: function(doc, style, marker, startTime, endTime, arrowNode) {
|
||||
let cell = doc.createElement("hbox");
|
||||
cell.className = "waterfall-marker waterfall-background-ticks";
|
||||
cell.setAttribute("align", "center");
|
||||
cell.setAttribute("flex", "1");
|
||||
|
||||
let dataScale = this.getDataScale();
|
||||
let offset = (marker.start - startTime) * dataScale;
|
||||
let width = (marker.end - marker.start) * dataScale;
|
||||
|
||||
arrowNode.style.transform =`translateX(${offset + ARROW_NODE_OFFSET}px)`;
|
||||
cell.appendChild(arrowNode);
|
||||
|
||||
let bar = doc.createElement("hbox");
|
||||
bar.className = `waterfall-marker-bar marker-color-${style.colorName}`;
|
||||
bar.style.transform = `translateX(${offset}px)`;
|
||||
bar.setAttribute("type", marker.name);
|
||||
bar.setAttribute("width", Math.max(width, WATERFALL_MARKER_TIMEBAR_WIDTH_MIN));
|
||||
cell.appendChild(bar);
|
||||
|
||||
return cell;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the event listeners for this particular tree item.
|
||||
*/
|
||||
_addEventListeners: function() {
|
||||
this.on("focus", this._onItemFocus);
|
||||
this.on("blur", this._onItemBlur);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "blur" event on the root item.
|
||||
*/
|
||||
_onItemBlur: function() {
|
||||
this.root.emit("unselected");
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "mousedown" event on the root item.
|
||||
*/
|
||||
_onItemFocus: function(e, item) {
|
||||
this.root.emit("selected", item.marker);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks if a given marker is in the specified time range.
|
||||
*
|
||||
* @param object e
|
||||
* The marker containing the { start, end } timestamps.
|
||||
* @param number start
|
||||
* The earliest allowed time.
|
||||
* @param number end
|
||||
* The latest allowed time.
|
||||
* @return boolean
|
||||
* True if the marker fits inside the specified time range.
|
||||
*/
|
||||
function isMarkerInRange(e, start, end) {
|
||||
let m_start = e.start|0;
|
||||
let m_end = e.end|0;
|
||||
|
||||
return (m_start >= start && m_end <= end) || // bounds inside
|
||||
(m_start < start && m_end > end) || // bounds outside
|
||||
(m_start < start && m_end >= start && m_end <= end) || // overlap start
|
||||
(m_end > end && m_start >= start && m_start <= end); // overlap end
|
||||
}
|
||||
|
||||
exports.MarkerView = MarkerView;
|
||||
exports.WATERFALL_MARKER_SIDEBAR_WIDTH = WATERFALL_MARKER_SIDEBAR_WIDTH;
|
@ -19,6 +19,8 @@ loader.lazyRequireGetter(this, "getColor",
|
||||
"devtools/shared/theme", true);
|
||||
loader.lazyRequireGetter(this, "L10N",
|
||||
"devtools/performance/global", true);
|
||||
loader.lazyRequireGetter(this, "TickUtils",
|
||||
"devtools/performance/waterfall-ticks", true);
|
||||
|
||||
const OVERVIEW_HEADER_HEIGHT = 14; // px
|
||||
const OVERVIEW_ROW_HEIGHT = 11; // px
|
||||
@ -75,7 +77,7 @@ MarkersOverview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
|
||||
for (let type in blueprint) {
|
||||
this._paintBatches.set(type, { style: blueprint[type], batch: [] });
|
||||
this._lastGroup = Math.max(this._lastGroup, blueprint[type].group);
|
||||
this._lastGroup = Math.max(this._lastGroup, blueprint[type].group || 0);
|
||||
}
|
||||
},
|
||||
|
||||
@ -143,7 +145,12 @@ MarkersOverview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
let fontFamily = OVERVIEW_HEADER_TEXT_FONT_FAMILY;
|
||||
let textPaddingLeft = OVERVIEW_HEADER_TEXT_PADDING_LEFT * this._pixelRatio;
|
||||
let textPaddingTop = OVERVIEW_HEADER_TEXT_PADDING_TOP * this._pixelRatio;
|
||||
let tickInterval = this._findOptimalTickInterval(dataScale);
|
||||
|
||||
let tickInterval = TickUtils.findOptimalTickInterval({
|
||||
ticksMultiple: OVERVIEW_HEADER_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: OVERVIEW_HEADER_TICKS_SPACING_MIN,
|
||||
dataScale: dataScale
|
||||
});
|
||||
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.font = fontSize + "px " + fontFamily;
|
||||
@ -190,32 +197,6 @@ MarkersOverview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
return canvas;
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds the optimal tick interval between time markers in this overview.
|
||||
*/
|
||||
_findOptimalTickInterval: function(dataScale) {
|
||||
let timingStep = OVERVIEW_HEADER_TICKS_MULTIPLE;
|
||||
let spacingMin = OVERVIEW_HEADER_TICKS_SPACING_MIN * this._pixelRatio;
|
||||
let maxIters = FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS;
|
||||
let numIters = 0;
|
||||
|
||||
if (dataScale > spacingMin) {
|
||||
return dataScale;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
let scaledStep = dataScale * timingStep;
|
||||
if (++numIters > maxIters) {
|
||||
return scaledStep;
|
||||
}
|
||||
if (scaledStep < spacingMin) {
|
||||
timingStep <<= 1;
|
||||
continue;
|
||||
}
|
||||
return scaledStep;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the theme via `theme` to either "light" or "dark",
|
||||
* and updates the internal styling to match. Requires a redraw
|
||||
|
@ -56,11 +56,11 @@ const sum = vals => vals.reduce((a, b) => a + b, 0);
|
||||
* parent node is used for all rows.
|
||||
*
|
||||
* @param CallView caller
|
||||
* The CallView considered the "caller" frame. This instance will be
|
||||
* represent the "callee". Should be null for root nodes.
|
||||
* The CallView considered the "caller" frame. This newly created
|
||||
* instance will be represent the "callee". Should be null for root nodes.
|
||||
* @param ThreadNode | FrameNode frame
|
||||
* Details about this function, like { samples, duration, calls } etc.
|
||||
* @param number level
|
||||
* @param number level [optional]
|
||||
* The indentation level in the call tree. The root node is at level 0.
|
||||
* @param boolean hidden [optional]
|
||||
* Whether this node should be hidden and not contribute to depth/level
|
||||
@ -213,7 +213,7 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
* Invoked by `_displaySelf`.
|
||||
*/
|
||||
_createTimeCell: function(doc, duration, isSelf = false) {
|
||||
let cell = doc.createElement("label");
|
||||
let cell = doc.createElement("description");
|
||||
cell.className = "plain call-tree-cell";
|
||||
cell.setAttribute("type", isSelf ? "self-duration" : "duration");
|
||||
cell.setAttribute("crop", "end");
|
||||
@ -221,7 +221,7 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
return cell;
|
||||
},
|
||||
_createExecutionCell: function(doc, percentage, isSelf = false) {
|
||||
let cell = doc.createElement("label");
|
||||
let cell = doc.createElement("description");
|
||||
cell.className = "plain call-tree-cell";
|
||||
cell.setAttribute("type", isSelf ? "self-percentage" : "percentage");
|
||||
cell.setAttribute("crop", "end");
|
||||
@ -229,7 +229,7 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
return cell;
|
||||
},
|
||||
_createAllocationsCell: function(doc, count, isSelf = false) {
|
||||
let cell = doc.createElement("label");
|
||||
let cell = doc.createElement("description");
|
||||
cell.className = "plain call-tree-cell";
|
||||
cell.setAttribute("type", isSelf ? "self-allocations" : "allocations");
|
||||
cell.setAttribute("crop", "end");
|
||||
@ -237,7 +237,7 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
return cell;
|
||||
},
|
||||
_createSamplesCell: function(doc, count) {
|
||||
let cell = doc.createElement("label");
|
||||
let cell = doc.createElement("description");
|
||||
cell.className = "plain call-tree-cell";
|
||||
cell.setAttribute("type", "samples");
|
||||
cell.setAttribute("crop", "end");
|
||||
@ -254,7 +254,7 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
// Don't render a name label node if there's no function name. A different
|
||||
// location label node will be rendered instead.
|
||||
if (frameName) {
|
||||
let nameNode = doc.createElement("label");
|
||||
let nameNode = doc.createElement("description");
|
||||
nameNode.className = "plain call-tree-name";
|
||||
nameNode.setAttribute("flex", "1");
|
||||
nameNode.setAttribute("crop", "end");
|
||||
@ -277,7 +277,7 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
},
|
||||
_appendFunctionDetailsCells: function(doc, cell, frameInfo) {
|
||||
if (frameInfo.fileName) {
|
||||
let urlNode = doc.createElement("label");
|
||||
let urlNode = doc.createElement("description");
|
||||
urlNode.className = "plain call-tree-url";
|
||||
urlNode.setAttribute("flex", "1");
|
||||
urlNode.setAttribute("crop", "end");
|
||||
@ -288,21 +288,21 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
}
|
||||
|
||||
if (frameInfo.line) {
|
||||
let lineNode = doc.createElement("label");
|
||||
let lineNode = doc.createElement("description");
|
||||
lineNode.className = "plain call-tree-line";
|
||||
lineNode.setAttribute("value", ":" + frameInfo.line);
|
||||
cell.appendChild(lineNode);
|
||||
}
|
||||
|
||||
if (frameInfo.column) {
|
||||
let columnNode = doc.createElement("label");
|
||||
let columnNode = doc.createElement("description");
|
||||
columnNode.className = "plain call-tree-column";
|
||||
columnNode.setAttribute("value", ":" + frameInfo.column);
|
||||
cell.appendChild(columnNode);
|
||||
}
|
||||
|
||||
if (frameInfo.host) {
|
||||
let hostNode = doc.createElement("label");
|
||||
let hostNode = doc.createElement("description");
|
||||
hostNode.className = "plain call-tree-host";
|
||||
hostNode.setAttribute("value", frameInfo.host);
|
||||
cell.appendChild(hostNode);
|
||||
@ -313,7 +313,7 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
cell.appendChild(spacerNode);
|
||||
|
||||
if (frameInfo.categoryData.label) {
|
||||
let categoryNode = doc.createElement("label");
|
||||
let categoryNode = doc.createElement("description");
|
||||
categoryNode.className = "plain call-tree-category";
|
||||
categoryNode.style.color = frameInfo.categoryData.color;
|
||||
categoryNode.setAttribute("value", frameInfo.categoryData.label);
|
||||
|
187
browser/devtools/performance/modules/widgets/waterfall-ticks.js
Normal file
187
browser/devtools/performance/modules/widgets/waterfall-ticks.js
Normal file
@ -0,0 +1,187 @@
|
||||
/* 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 file contains the "waterfall ticks" view, a header for the
|
||||
* markers displayed in the waterfall.
|
||||
*/
|
||||
|
||||
loader.lazyRequireGetter(this, "L10N",
|
||||
"devtools/performance/global", true);
|
||||
loader.lazyRequireGetter(this, "WATERFALL_MARKER_SIDEBAR_WIDTH",
|
||||
"devtools/performance/marker-view", true);
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS = 100;
|
||||
|
||||
const WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
|
||||
const WATERFALL_HEADER_TICKS_SPACING_MIN = 50; // px
|
||||
const WATERFALL_HEADER_TEXT_PADDING = 3; // px
|
||||
|
||||
const WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
|
||||
const WATERFALL_BACKGROUND_TICKS_SCALES = 3;
|
||||
const WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
|
||||
const WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
|
||||
const WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32; // byte
|
||||
const WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32; // byte
|
||||
|
||||
/**
|
||||
* A header for a markers waterfall.
|
||||
*
|
||||
* @param MarkerView root
|
||||
* The root item of the waterfall tree.
|
||||
*/
|
||||
function WaterfallHeader(root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
WaterfallHeader.prototype = {
|
||||
/**
|
||||
* Creates and appends this header as the first element of the specified
|
||||
* parent element.
|
||||
*
|
||||
* @param nsIDOMNode parentNode
|
||||
* The parent element for this header.
|
||||
*/
|
||||
attachTo: function(parentNode) {
|
||||
let document = parentNode.ownerDocument;
|
||||
let startTime = this.root.interval.startTime;
|
||||
let dataScale = this.root.getDataScale();
|
||||
let waterfallWidth = this.root.getWaterfallWidth();
|
||||
|
||||
let header = this._buildNode(document, startTime, dataScale, waterfallWidth);
|
||||
parentNode.insertBefore(header, parentNode.firstChild);
|
||||
|
||||
this._drawWaterfallBackground(document, dataScale, waterfallWidth);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the node displaying this view.
|
||||
*/
|
||||
_buildNode: function(doc, startTime, dataScale, waterfallWidth) {
|
||||
let container = doc.createElement("hbox");
|
||||
container.className = "waterfall-header-container";
|
||||
container.setAttribute("flex", "1");
|
||||
|
||||
let sidebar = doc.createElement("hbox");
|
||||
sidebar.className = "waterfall-sidebar theme-sidebar";
|
||||
sidebar.setAttribute("width", WATERFALL_MARKER_SIDEBAR_WIDTH);
|
||||
sidebar.setAttribute("align", "center");
|
||||
container.appendChild(sidebar);
|
||||
|
||||
let name = doc.createElement("description");
|
||||
name.className = "plain waterfall-header-name";
|
||||
name.setAttribute("value", L10N.getStr("timeline.records"));
|
||||
sidebar.appendChild(name);
|
||||
|
||||
let ticks = doc.createElement("hbox");
|
||||
ticks.className = "waterfall-header-ticks waterfall-background-ticks";
|
||||
ticks.setAttribute("align", "center");
|
||||
ticks.setAttribute("flex", "1");
|
||||
container.appendChild(ticks);
|
||||
|
||||
let tickInterval = findOptimalTickInterval({
|
||||
ticksMultiple: WATERFALL_HEADER_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: WATERFALL_HEADER_TICKS_SPACING_MIN,
|
||||
dataScale: dataScale
|
||||
});
|
||||
|
||||
for (let x = 0; x < waterfallWidth; x += tickInterval) {
|
||||
let left = x + WATERFALL_HEADER_TEXT_PADDING;
|
||||
let time = Math.round(x / dataScale + startTime);
|
||||
let label = L10N.getFormatStr("timeline.tick", time);
|
||||
|
||||
let node = doc.createElement("description");
|
||||
node.className = "plain waterfall-header-tick";
|
||||
node.style.transform = "translateX(" + left + "px)";
|
||||
node.setAttribute("value", label);
|
||||
ticks.appendChild(node);
|
||||
}
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the background displayed on the marker's waterfall.
|
||||
*/
|
||||
_drawWaterfallBackground: function(doc, dataScale, waterfallWidth) {
|
||||
if (!this._canvas || !this._ctx) {
|
||||
this._canvas = doc.createElementNS(HTML_NS, "canvas");
|
||||
this._ctx = this._canvas.getContext("2d");
|
||||
}
|
||||
let canvas = this._canvas;
|
||||
let ctx = this._ctx;
|
||||
|
||||
// Nuke the context.
|
||||
let canvasWidth = canvas.width = waterfallWidth;
|
||||
let canvasHeight = canvas.height = 1; // Awww yeah, 1px, repeats on Y axis.
|
||||
|
||||
// Start over.
|
||||
let imageData = ctx.createImageData(canvasWidth, canvasHeight);
|
||||
let pixelArray = imageData.data;
|
||||
|
||||
let buf = new ArrayBuffer(pixelArray.length);
|
||||
let view8bit = new Uint8ClampedArray(buf);
|
||||
let view32bit = new Uint32Array(buf);
|
||||
|
||||
// Build new millisecond tick lines...
|
||||
let [r, g, b] = WATERFALL_BACKGROUND_TICKS_COLOR_RGB;
|
||||
let alphaComponent = WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
|
||||
let tickInterval = findOptimalTickInterval({
|
||||
ticksMultiple: WATERFALL_BACKGROUND_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: WATERFALL_BACKGROUND_TICKS_SPACING_MIN,
|
||||
dataScale: dataScale
|
||||
});
|
||||
|
||||
// Insert one pixel for each division on each scale.
|
||||
for (let i = 1; i <= WATERFALL_BACKGROUND_TICKS_SCALES; i++) {
|
||||
let increment = tickInterval * Math.pow(2, i);
|
||||
for (let x = 0; x < canvasWidth; x += increment) {
|
||||
let position = x | 0;
|
||||
view32bit[position] = (alphaComponent << 24) | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
alphaComponent += WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
|
||||
}
|
||||
|
||||
// Flush the image data and cache the waterfall background.
|
||||
pixelArray.set(view8bit);
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
doc.mozSetImageElement("waterfall-background", canvas);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the optimal tick interval between time markers in this timeline.
|
||||
*
|
||||
* @param number ticksMultiple
|
||||
* @param number ticksSpacingMin
|
||||
* @param number dataScale
|
||||
* @return number
|
||||
*/
|
||||
function findOptimalTickInterval({ ticksMultiple, ticksSpacingMin, dataScale }) {
|
||||
let timingStep = ticksMultiple;
|
||||
let maxIters = FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS;
|
||||
let numIters = 0;
|
||||
|
||||
if (dataScale > ticksSpacingMin) {
|
||||
return dataScale;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
let scaledStep = dataScale * timingStep;
|
||||
if (++numIters > maxIters) {
|
||||
return scaledStep;
|
||||
}
|
||||
if (scaledStep < ticksSpacingMin) {
|
||||
timingStep <<= 1;
|
||||
continue;
|
||||
}
|
||||
return scaledStep;
|
||||
}
|
||||
}
|
||||
|
||||
exports.WaterfallHeader = WaterfallHeader;
|
||||
exports.TickUtils = { findOptimalTickInterval };
|
@ -1,620 +0,0 @@
|
||||
/* 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 file contains the "waterfall" view, essentially a detailed list
|
||||
* of all the markers in the timeline data.
|
||||
*/
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
|
||||
loader.lazyRequireGetter(this, "L10N",
|
||||
"devtools/performance/global", true);
|
||||
loader.lazyRequireGetter(this, "MarkerUtils",
|
||||
"devtools/performance/marker-utils");
|
||||
|
||||
loader.lazyImporter(this, "setNamedTimeout",
|
||||
"resource:///modules/devtools/ViewHelpers.jsm");
|
||||
loader.lazyImporter(this, "clearNamedTimeout",
|
||||
"resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const WATERFALL_SIDEBAR_WIDTH = 200; // px
|
||||
|
||||
const WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT = 30;
|
||||
const WATERFALL_FLUSH_OUTSTANDING_MARKERS_DELAY = 75; // ms
|
||||
|
||||
const FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS = 100;
|
||||
const WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
|
||||
const WATERFALL_HEADER_TICKS_SPACING_MIN = 50; // px
|
||||
const WATERFALL_HEADER_TEXT_PADDING = 3; // px
|
||||
|
||||
const WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
|
||||
const WATERFALL_BACKGROUND_TICKS_SCALES = 3;
|
||||
const WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
|
||||
const WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
|
||||
const WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32; // byte
|
||||
const WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32; // byte
|
||||
const WATERFALL_MARKER_BAR_WIDTH_MIN = 5; // px
|
||||
|
||||
const WATERFALL_ROWCOUNT_ONPAGEUPDOWN = 10;
|
||||
|
||||
/**
|
||||
* A detailed waterfall view for the timeline data.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the waterfall.
|
||||
* @param nsIDOMNode container
|
||||
* The container node that key events should be bound to.
|
||||
* @param Object blueprint
|
||||
* List of names and colors defining markers.
|
||||
*/
|
||||
function Waterfall(parent, container, blueprint) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this._parent = parent;
|
||||
this._document = parent.ownerDocument;
|
||||
this._container = container;
|
||||
this._fragment = this._document.createDocumentFragment();
|
||||
this._outstandingMarkers = [];
|
||||
|
||||
this._headerContents = this._document.createElement("hbox");
|
||||
this._headerContents.className = "waterfall-header-contents";
|
||||
this._parent.appendChild(this._headerContents);
|
||||
|
||||
this._listContents = this._document.createElement("vbox");
|
||||
this._listContents.className = "waterfall-list-contents";
|
||||
this._listContents.setAttribute("flex", "1");
|
||||
this._parent.appendChild(this._listContents);
|
||||
|
||||
this.setupKeys();
|
||||
|
||||
this._isRTL = this._getRTL();
|
||||
|
||||
// Lazy require is a bit slow, and these are hot objects.
|
||||
this._l10n = L10N;
|
||||
this._blueprint = blueprint;
|
||||
this._setNamedTimeout = setNamedTimeout;
|
||||
this._clearNamedTimeout = clearNamedTimeout;
|
||||
|
||||
// Selected row index. By default, we want the first
|
||||
// row to be selected.
|
||||
this._selectedRowIdx = 0;
|
||||
|
||||
// Default rowCount
|
||||
this.rowCount = WATERFALL_ROWCOUNT_ONPAGEUPDOWN;
|
||||
}
|
||||
|
||||
Waterfall.prototype = {
|
||||
/**
|
||||
* Removes any node references from this view.
|
||||
*/
|
||||
destroy: function() {
|
||||
this._parent = this._document = this._container = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates this view with the provided data source.
|
||||
*
|
||||
* @param object data
|
||||
* An object containing the following properties:
|
||||
* - markers: a list of markers received from the controller
|
||||
* - interval: the { startTime, endTime }, in milliseconds
|
||||
*/
|
||||
setData: function({ markers, interval }) {
|
||||
this.clearView();
|
||||
this._markers = markers;
|
||||
this._interval = interval;
|
||||
|
||||
let { startTime, endTime } = interval;
|
||||
let dataScale = this._waterfallWidth / (endTime - startTime);
|
||||
this._drawWaterfallBackground(dataScale);
|
||||
|
||||
this._buildHeader(this._headerContents, startTime, dataScale);
|
||||
this._buildMarkers(this._listContents, markers, startTime, endTime, dataScale);
|
||||
this.selectRow(this._selectedRowIdx);
|
||||
},
|
||||
|
||||
/**
|
||||
* List of names and colors used to paint markers.
|
||||
* @see TIMELINE_BLUEPRINT in timeline/widgets/global.js
|
||||
*/
|
||||
setBlueprint: function(blueprint) {
|
||||
this._blueprint = blueprint;
|
||||
},
|
||||
|
||||
/**
|
||||
* Keybindings.
|
||||
*/
|
||||
setupKeys: function() {
|
||||
let pane = this._container;
|
||||
pane.addEventListener("keydown", e => {
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_UP) {
|
||||
e.preventDefault();
|
||||
this.selectNearestRow(this._selectedRowIdx - 1);
|
||||
}
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DOWN) {
|
||||
e.preventDefault();
|
||||
this.selectNearestRow(this._selectedRowIdx + 1);
|
||||
}
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_HOME) {
|
||||
e.preventDefault();
|
||||
this.selectNearestRow(0);
|
||||
}
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_END) {
|
||||
e.preventDefault();
|
||||
this.selectNearestRow(this._listContents.children.length);
|
||||
}
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP) {
|
||||
e.preventDefault();
|
||||
this.selectNearestRow(this._selectedRowIdx - this.rowCount);
|
||||
}
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
|
||||
e.preventDefault();
|
||||
this.selectNearestRow(this._selectedRowIdx + this.rowCount);
|
||||
}
|
||||
}, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Depopulates this view.
|
||||
*/
|
||||
clearView: function() {
|
||||
while (this._headerContents.hasChildNodes()) {
|
||||
this._headerContents.firstChild.remove();
|
||||
}
|
||||
while (this._listContents.hasChildNodes()) {
|
||||
this._listContents.firstChild.remove();
|
||||
}
|
||||
this._listContents.scrollTop = 0;
|
||||
this._outstandingMarkers.length = 0;
|
||||
this._clearNamedTimeout("flush-outstanding-markers");
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculates and stores the available width for the waterfall.
|
||||
* This should be invoked every time the container window is resized.
|
||||
*/
|
||||
recalculateBounds: function() {
|
||||
let bounds = this._parent.getBoundingClientRect();
|
||||
this._waterfallWidth = bounds.width - WATERFALL_SIDEBAR_WIDTH;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the header part of this view.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the header.
|
||||
* @param number startTime
|
||||
* @see Waterfall.prototype.setData
|
||||
* @param number dataScale
|
||||
* The time scale of the data source.
|
||||
*/
|
||||
_buildHeader: function(parent, startTime, dataScale) {
|
||||
let container = this._document.createElement("hbox");
|
||||
container.className = "waterfall-header-container";
|
||||
container.setAttribute("flex", "1");
|
||||
|
||||
let sidebar = this._document.createElement("hbox");
|
||||
sidebar.className = "waterfall-sidebar theme-sidebar";
|
||||
sidebar.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
|
||||
sidebar.setAttribute("align", "center");
|
||||
container.appendChild(sidebar);
|
||||
|
||||
let name = this._document.createElement("label");
|
||||
name.className = "plain waterfall-header-name";
|
||||
name.setAttribute("value", this._l10n.getStr("timeline.records"));
|
||||
sidebar.appendChild(name);
|
||||
|
||||
let ticks = this._document.createElement("hbox");
|
||||
ticks.className = "waterfall-header-ticks waterfall-background-ticks";
|
||||
ticks.setAttribute("align", "center");
|
||||
ticks.setAttribute("flex", "1");
|
||||
container.appendChild(ticks);
|
||||
|
||||
let offset = this._isRTL ? this._waterfallWidth : 0;
|
||||
let direction = this._isRTL ? -1 : 1;
|
||||
let tickInterval = this._findOptimalTickInterval({
|
||||
ticksMultiple: WATERFALL_HEADER_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: WATERFALL_HEADER_TICKS_SPACING_MIN,
|
||||
dataScale: dataScale
|
||||
});
|
||||
|
||||
for (let x = 0; x < this._waterfallWidth; x += tickInterval) {
|
||||
let left = x + direction * WATERFALL_HEADER_TEXT_PADDING;
|
||||
let time = Math.round(x / dataScale + startTime);
|
||||
let label = this._l10n.getFormatStr("timeline.tick", time);
|
||||
|
||||
let node = this._document.createElement("label");
|
||||
node.className = "plain waterfall-header-tick";
|
||||
node.style.transform = "translateX(" + (left - offset) + "px)";
|
||||
node.setAttribute("value", label);
|
||||
ticks.appendChild(node);
|
||||
}
|
||||
|
||||
parent.appendChild(container);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the markers part of this view.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the markers.
|
||||
* @param number startTime
|
||||
* @see Waterfall.prototype.setData
|
||||
* @param number dataScale
|
||||
* The time scale of the data source.
|
||||
*/
|
||||
_buildMarkers: function(parent, markers, startTime, endTime, dataScale) {
|
||||
let rowsCount = 0;
|
||||
let markerIdx = -1;
|
||||
|
||||
for (let marker of markers) {
|
||||
markerIdx++;
|
||||
|
||||
if (!isMarkerInRange(marker, startTime, endTime)) {
|
||||
continue;
|
||||
}
|
||||
if (!(marker.name in this._blueprint)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only build and display a finite number of markers initially, to
|
||||
// preserve a snappy UI. After a certain delay, continue building the
|
||||
// outstanding markers while there's (hopefully) no user interaction.
|
||||
let arguments_ = [this._fragment, marker, startTime, dataScale, markerIdx, rowsCount];
|
||||
if (rowsCount++ < WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT) {
|
||||
this._buildMarker.apply(this, arguments_);
|
||||
} else {
|
||||
this._outstandingMarkers.push(arguments_);
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no outstanding markers, add a dummy "spacer" at the end
|
||||
// to fill up any remaining available space in the UI.
|
||||
if (!this._outstandingMarkers.length) {
|
||||
this._buildMarker(this._fragment, null);
|
||||
}
|
||||
// Otherwise prepare flushing the outstanding markers after a small delay.
|
||||
else {
|
||||
let delay = WATERFALL_FLUSH_OUTSTANDING_MARKERS_DELAY;
|
||||
let func = () => this._buildOutstandingMarkers(parent);
|
||||
this._setNamedTimeout("flush-outstanding-markers", delay, func);
|
||||
}
|
||||
|
||||
parent.appendChild(this._fragment);
|
||||
},
|
||||
|
||||
/**
|
||||
* Finishes building the outstanding markers in this view.
|
||||
* @see Waterfall.prototype._buildMarkers
|
||||
*/
|
||||
_buildOutstandingMarkers: function(parent) {
|
||||
if (!this._outstandingMarkers.length) {
|
||||
return;
|
||||
}
|
||||
for (let args of this._outstandingMarkers) {
|
||||
this._buildMarker.apply(this, args);
|
||||
}
|
||||
this._outstandingMarkers.length = 0;
|
||||
parent.appendChild(this._fragment);
|
||||
this.selectRow(this._selectedRowIdx);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a single marker in this view.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the marker.
|
||||
* @param object marker
|
||||
* The { name, start, end } marker in the data source.
|
||||
* @param startTime
|
||||
* @see Waterfall.prototype.setData
|
||||
* @param number dataScale
|
||||
* @see Waterfall.prototype._buildMarkers
|
||||
* @param number markerIdx
|
||||
* Index of the marker in this._markers
|
||||
* @param number rowIdx
|
||||
* Index of current row
|
||||
*/
|
||||
_buildMarker: function(parent, marker, startTime, dataScale, markerIdx, rowIdx) {
|
||||
let container = this._document.createElement("hbox");
|
||||
container.setAttribute("markerIdx", markerIdx);
|
||||
container.className = "waterfall-marker-container";
|
||||
|
||||
if (marker) {
|
||||
this._buildMarkerSidebar(container, marker);
|
||||
this._buildMarkerWaterfall(container, marker, startTime, dataScale, markerIdx);
|
||||
container.onclick = () => this.selectRow(rowIdx);
|
||||
} else {
|
||||
this._buildMarkerSpacer(container);
|
||||
container.setAttribute("flex", "1");
|
||||
container.setAttribute("is-spacer", "");
|
||||
}
|
||||
|
||||
parent.appendChild(container);
|
||||
},
|
||||
|
||||
/**
|
||||
* Select first row.
|
||||
*/
|
||||
resetSelection: function() {
|
||||
this.selectRow(0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Select a marker in the waterfall.
|
||||
*
|
||||
* @param number idx
|
||||
* Index of the row to select. -1 clears the selection.
|
||||
*/
|
||||
selectRow: function(idx) {
|
||||
let prev = this._listContents.children[this._selectedRowIdx];
|
||||
if (prev) {
|
||||
prev.classList.remove("selected");
|
||||
}
|
||||
|
||||
this._selectedRowIdx = idx;
|
||||
|
||||
let row = this._listContents.children[idx];
|
||||
if (row && !row.hasAttribute("is-spacer")) {
|
||||
row.focus();
|
||||
row.classList.add("selected");
|
||||
|
||||
let markerIdx = row.getAttribute("markerIdx");
|
||||
this.emit("selected", this._markers[markerIdx]);
|
||||
this.ensureRowIsVisible(row);
|
||||
} else {
|
||||
this.emit("unselected");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Find a valid row to select.
|
||||
*
|
||||
* @param number idx
|
||||
* Index of the row to select.
|
||||
*/
|
||||
selectNearestRow: function(idx) {
|
||||
if (this._listContents.children.length == 0) {
|
||||
return;
|
||||
}
|
||||
idx = Math.max(idx, 0);
|
||||
idx = Math.min(idx, this._listContents.children.length - 1);
|
||||
let row = this._listContents.children[idx];
|
||||
if (row && row.hasAttribute("is-spacer")) {
|
||||
if (idx > 0) {
|
||||
return this.selectNearestRow(idx - 1);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.selectRow(idx);
|
||||
},
|
||||
|
||||
/**
|
||||
* Scroll waterfall to ensure row is in the viewport.
|
||||
*
|
||||
* @param number idx
|
||||
* Index of the row to select.
|
||||
*/
|
||||
ensureRowIsVisible: function(row) {
|
||||
let parent = row.parentNode;
|
||||
let parentRect = parent.getBoundingClientRect();
|
||||
let rowRect = row.getBoundingClientRect();
|
||||
let yDelta = rowRect.top - parentRect.top;
|
||||
if (yDelta < 0) {
|
||||
parent.scrollTop += yDelta;
|
||||
}
|
||||
yDelta = parentRect.bottom - rowRect.bottom;
|
||||
if (yDelta < 0) {
|
||||
parent.scrollTop -= yDelta;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the sidebar part of a marker in this view.
|
||||
*
|
||||
* @param nsIDOMNode container
|
||||
* The container node representing the marker in this view.
|
||||
* @param object marker
|
||||
* @see Waterfall.prototype._buildMarker
|
||||
*/
|
||||
_buildMarkerSidebar: function(container, marker) {
|
||||
let blueprint = this._blueprint[marker.name];
|
||||
|
||||
let sidebar = this._document.createElement("hbox");
|
||||
sidebar.className = "waterfall-sidebar theme-sidebar";
|
||||
sidebar.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
|
||||
sidebar.setAttribute("align", "center");
|
||||
|
||||
let bullet = this._document.createElement("hbox");
|
||||
bullet.className = `waterfall-marker-bullet marker-color-${blueprint.colorName}`;
|
||||
bullet.setAttribute("type", marker.name);
|
||||
sidebar.appendChild(bullet);
|
||||
|
||||
let name = this._document.createElement("label");
|
||||
name.setAttribute("crop", "end");
|
||||
name.setAttribute("flex", "1");
|
||||
name.className = "plain waterfall-marker-name";
|
||||
|
||||
let label = MarkerUtils.getMarkerLabel(marker);
|
||||
name.setAttribute("value", label);
|
||||
name.setAttribute("tooltiptext", label);
|
||||
sidebar.appendChild(name);
|
||||
|
||||
container.appendChild(sidebar);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the waterfall part of a marker in this view.
|
||||
*
|
||||
* @param nsIDOMNode container
|
||||
* The container node representing the marker.
|
||||
* @param object marker
|
||||
* @see Waterfall.prototype._buildMarker
|
||||
* @param startTime
|
||||
* @see Waterfall.prototype.setData
|
||||
* @param number dataScale
|
||||
* @see Waterfall.prototype._buildMarkers
|
||||
*/
|
||||
_buildMarkerWaterfall: function(container, marker, startTime, dataScale) {
|
||||
let blueprint = this._blueprint[marker.name];
|
||||
|
||||
let waterfall = this._document.createElement("hbox");
|
||||
waterfall.className = "waterfall-marker-item waterfall-background-ticks";
|
||||
waterfall.setAttribute("align", "center");
|
||||
waterfall.setAttribute("flex", "1");
|
||||
|
||||
let start = (marker.start - startTime) * dataScale;
|
||||
let width = (marker.end - marker.start) * dataScale;
|
||||
let offset = this._isRTL ? this._waterfallWidth : 0;
|
||||
|
||||
let bar = this._document.createElement("hbox");
|
||||
bar.className = `waterfall-marker-bar marker-color-${blueprint.colorName}`;
|
||||
bar.style.transform = "translateX(" + (start - offset) + "px)";
|
||||
bar.setAttribute("type", marker.name);
|
||||
bar.setAttribute("width", Math.max(width, WATERFALL_MARKER_BAR_WIDTH_MIN));
|
||||
waterfall.appendChild(bar);
|
||||
|
||||
container.appendChild(waterfall);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a dummy spacer as an empty marker.
|
||||
*
|
||||
* @param nsIDOMNode container
|
||||
* The container node representing the marker.
|
||||
*/
|
||||
_buildMarkerSpacer: function(container) {
|
||||
let sidebarSpacer = this._document.createElement("spacer");
|
||||
sidebarSpacer.className = "waterfall-sidebar theme-sidebar";
|
||||
sidebarSpacer.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
|
||||
|
||||
let waterfallSpacer = this._document.createElement("spacer");
|
||||
waterfallSpacer.className = "waterfall-marker-item waterfall-background-ticks";
|
||||
waterfallSpacer.setAttribute("flex", "1");
|
||||
|
||||
container.appendChild(sidebarSpacer);
|
||||
container.appendChild(waterfallSpacer);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the background displayed on the marker's waterfall.
|
||||
*
|
||||
* @param number dataScale
|
||||
* @see Waterfall.prototype._buildMarkers
|
||||
*/
|
||||
_drawWaterfallBackground: function(dataScale) {
|
||||
if (!this._canvas || !this._ctx) {
|
||||
this._canvas = this._document.createElementNS(HTML_NS, "canvas");
|
||||
this._ctx = this._canvas.getContext("2d");
|
||||
}
|
||||
let canvas = this._canvas;
|
||||
let ctx = this._ctx;
|
||||
|
||||
// Nuke the context.
|
||||
let canvasWidth = canvas.width = this._waterfallWidth;
|
||||
let canvasHeight = canvas.height = 1; // Awww yeah, 1px, repeats on Y axis.
|
||||
|
||||
// Start over.
|
||||
let imageData = ctx.createImageData(canvasWidth, canvasHeight);
|
||||
let pixelArray = imageData.data;
|
||||
|
||||
let buf = new ArrayBuffer(pixelArray.length);
|
||||
let view8bit = new Uint8ClampedArray(buf);
|
||||
let view32bit = new Uint32Array(buf);
|
||||
|
||||
// Build new millisecond tick lines...
|
||||
let [r, g, b] = WATERFALL_BACKGROUND_TICKS_COLOR_RGB;
|
||||
let alphaComponent = WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
|
||||
let tickInterval = this._findOptimalTickInterval({
|
||||
ticksMultiple: WATERFALL_BACKGROUND_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: WATERFALL_BACKGROUND_TICKS_SPACING_MIN,
|
||||
dataScale: dataScale
|
||||
});
|
||||
|
||||
// Insert one pixel for each division on each scale.
|
||||
for (let i = 1; i <= WATERFALL_BACKGROUND_TICKS_SCALES; i++) {
|
||||
let increment = tickInterval * Math.pow(2, i);
|
||||
for (let x = 0; x < canvasWidth; x += increment) {
|
||||
let position = x | 0;
|
||||
view32bit[position] = (alphaComponent << 24) | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
alphaComponent += WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
|
||||
}
|
||||
|
||||
// Flush the image data and cache the waterfall background.
|
||||
pixelArray.set(view8bit);
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
this._document.mozSetImageElement("waterfall-background", canvas);
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds the optimal tick interval between time markers in this timeline.
|
||||
*
|
||||
* @param number ticksMultiple
|
||||
* @param number ticksSpacingMin
|
||||
* @param number dataScale
|
||||
* @return number
|
||||
*/
|
||||
_findOptimalTickInterval: function({ ticksMultiple, ticksSpacingMin, dataScale }) {
|
||||
let timingStep = ticksMultiple;
|
||||
let maxIters = FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS;
|
||||
let numIters = 0;
|
||||
|
||||
if (dataScale > ticksSpacingMin) {
|
||||
return dataScale;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
let scaledStep = dataScale * timingStep;
|
||||
if (++numIters > maxIters) {
|
||||
return scaledStep;
|
||||
}
|
||||
if (scaledStep < ticksSpacingMin) {
|
||||
timingStep <<= 1;
|
||||
continue;
|
||||
}
|
||||
return scaledStep;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if this is document is in RTL mode.
|
||||
* @return boolean
|
||||
*/
|
||||
_getRTL: function() {
|
||||
let win = this._document.defaultView;
|
||||
let doc = this._document.documentElement;
|
||||
return win.getComputedStyle(doc, null).direction == "rtl";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a given marker is in the specified time range.
|
||||
*
|
||||
* @param object e
|
||||
* The marker containing the { start, end } timestamps.
|
||||
* @param number start
|
||||
* The earliest allowed time.
|
||||
* @param number end
|
||||
* The latest allowed time.
|
||||
* @return boolean
|
||||
* True if the marker fits inside the specified time range.
|
||||
*/
|
||||
function isMarkerInRange(e, start, end) {
|
||||
return (e.start >= start && e.end <= end) || // bounds inside
|
||||
(e.start < start && e.end > end) || // bounds outside
|
||||
(e.start < start && e.end >= start && e.end <= end) || // overlap start
|
||||
(e.end > end && e.start >= start && e.start <= end); // overlap end
|
||||
}
|
||||
|
||||
exports.Waterfall = Waterfall;
|
@ -15,11 +15,13 @@ EXTRA_JS_MODULES.devtools.performance += [
|
||||
'modules/logic/recording-model.js',
|
||||
'modules/logic/recording-utils.js',
|
||||
'modules/logic/tree-model.js',
|
||||
'modules/logic/waterfall-utils.js',
|
||||
'modules/widgets/graphs.js',
|
||||
'modules/widgets/marker-details.js',
|
||||
'modules/widgets/marker-view.js',
|
||||
'modules/widgets/markers-overview.js',
|
||||
'modules/widgets/tree-view.js',
|
||||
'modules/widgets/waterfall.js',
|
||||
'modules/widgets/waterfall-ticks.js',
|
||||
'panel.js'
|
||||
]
|
||||
|
||||
|
@ -25,12 +25,16 @@ loader.lazyRequireGetter(this, "RecordingModel",
|
||||
"devtools/performance/recording-model", true);
|
||||
loader.lazyRequireGetter(this, "GraphsController",
|
||||
"devtools/performance/graphs", true);
|
||||
loader.lazyRequireGetter(this, "Waterfall",
|
||||
"devtools/performance/waterfall", true);
|
||||
loader.lazyRequireGetter(this, "WaterfallHeader",
|
||||
"devtools/performance/waterfall-ticks", true);
|
||||
loader.lazyRequireGetter(this, "MarkerView",
|
||||
"devtools/performance/marker-view", true);
|
||||
loader.lazyRequireGetter(this, "MarkerDetails",
|
||||
"devtools/performance/marker-details", true);
|
||||
loader.lazyRequireGetter(this, "MarkerUtils",
|
||||
"devtools/performance/marker-utils");
|
||||
loader.lazyRequireGetter(this, "WaterfallUtils",
|
||||
"devtools/performance/waterfall-utils");
|
||||
loader.lazyRequireGetter(this, "CallView",
|
||||
"devtools/performance/tree-view", true);
|
||||
loader.lazyRequireGetter(this, "ThreadNode",
|
||||
|
@ -241,12 +241,13 @@
|
||||
|
||||
<!-- Waterfall -->
|
||||
<hbox id="waterfall-view" flex="1">
|
||||
<vbox id="waterfall-breakdown" flex="1" />
|
||||
<vbox flex="1">
|
||||
<hbox id="waterfall-header" />
|
||||
<vbox id="waterfall-breakdown" flex="1" />
|
||||
</vbox>
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
<vbox id="waterfall-details"
|
||||
class="theme-sidebar"
|
||||
width="150"
|
||||
height="150"/>
|
||||
class="theme-sidebar"/>
|
||||
</hbox>
|
||||
|
||||
<!-- JS Tree and JIT view -->
|
||||
|
4
browser/devtools/performance/test/.eslintrc
Normal file
4
browser/devtools/performance/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
@ -145,4 +145,6 @@ skip-if = e10s # GC events seem unreliable in multiprocess
|
||||
[browser_timeline-filters.js]
|
||||
[browser_timeline-waterfall-background.js]
|
||||
[browser_timeline-waterfall-generic.js]
|
||||
[browser_timeline-waterfall-rerender.js]
|
||||
[browser_timeline-waterfall-sidebar.js]
|
||||
[browser_waterfall-collapse.js]
|
||||
|
@ -68,8 +68,11 @@ function* spawnTest() {
|
||||
counters.ticks.push({ delta, timestamps });
|
||||
lastTickDelta = delta;
|
||||
}
|
||||
else if (name === "frames") {
|
||||
// Nothing to do here.
|
||||
}
|
||||
else {
|
||||
throw new Error("unknown event " + name);
|
||||
ok(false, `Received unknown event: ${name}`);
|
||||
}
|
||||
|
||||
if (name === "markers" && counters[name].length === 1 ||
|
||||
|
@ -18,6 +18,11 @@ function* spawnTest() {
|
||||
let treeRoot = new MyCustomTreeItem(gDataSrc, { parent: null });
|
||||
treeRoot.attachTo(container);
|
||||
|
||||
ok(!treeRoot.expanded,
|
||||
"The root node should not be expanded yet.");
|
||||
ok(!treeRoot.populated,
|
||||
"The root node should not be populated yet.");
|
||||
|
||||
is(container.childNodes.length, 1,
|
||||
"The container node should have one child available.");
|
||||
is(container.childNodes[0], treeRoot.target,
|
||||
|
@ -16,14 +16,9 @@ function* spawnTest() {
|
||||
// Populate the tree and test `expand`, `collapse` and `getChild`...
|
||||
|
||||
let treeRoot = new MyCustomTreeItem(gDataSrc, { parent: null });
|
||||
treeRoot.autoExpandDepth = 1;
|
||||
treeRoot.attachTo(container);
|
||||
|
||||
ok(!treeRoot.expanded,
|
||||
"The root node should not be expanded yet.");
|
||||
ok(!treeRoot.populated,
|
||||
"The root node should not be populated yet.");
|
||||
|
||||
treeRoot.expand();
|
||||
ok(treeRoot.expanded,
|
||||
"The root node should now be expanded.");
|
||||
ok(treeRoot.populated,
|
||||
|
@ -15,11 +15,20 @@ function* spawnTest() {
|
||||
"The timeline blueprint has at least one entry.");
|
||||
|
||||
for (let [key, value] of Iterator(TIMELINE_BLUEPRINT)) {
|
||||
ok("group" in value,
|
||||
"Each entry in the timeline blueprint contains a `group` key.");
|
||||
ok("colorName" in value,
|
||||
"Each entry in the timeline blueprint contains a `colorName` key.");
|
||||
ok("label" in value,
|
||||
"Each entry in the timeline blueprint contains a `label` key.");
|
||||
if (key.startsWith("meta::")) {
|
||||
ok(!("group" in value),
|
||||
"No meta entry in the timeline blueprint can contain a `group` key.");
|
||||
ok("colorName" in value,
|
||||
"Each meta entry in the timeline blueprint contains a `colorName` key.");
|
||||
ok("label" in value,
|
||||
"Each meta entry in the timeline blueprint contains a `label` key.");
|
||||
} else {
|
||||
ok("group" in value,
|
||||
"Each entry in the timeline blueprint contains a `group` key.");
|
||||
ok("colorName" in value,
|
||||
"Each entry in the timeline blueprint contains a `colorName` key.");
|
||||
ok("label" in value,
|
||||
"Each entry in the timeline blueprint contains a `label` key.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
function* spawnTest() {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { $, $$, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
|
||||
let { $, $$, EVENTS, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
|
||||
let { TimelineGraph } = devtools.require("devtools/performance/graphs");
|
||||
let { rowHeight: MARKERS_GRAPH_ROW_HEIGHT } = TimelineGraph.prototype;
|
||||
|
||||
@ -24,20 +24,15 @@ function* spawnTest() {
|
||||
|
||||
yield stopRecording(panel);
|
||||
|
||||
let overview = OverviewView.graphs.get("timeline");
|
||||
let waterfall = WaterfallView.waterfall;
|
||||
|
||||
// Select everything
|
||||
OverviewView.setTimeInterval({ startTime: 0, endTime: Number.MAX_VALUE })
|
||||
|
||||
$("#filter-button").click();
|
||||
|
||||
yield waitUntil(() => !waterfall._outstandingMarkers.length);
|
||||
|
||||
let menuItem1 = $("menuitem[marker-type=Styles]");
|
||||
let menuItem2 = $("menuitem[marker-type=Reflow]");
|
||||
let menuItem3 = $("menuitem[marker-type=Paint]");
|
||||
|
||||
let overview = OverviewView.graphs.get("timeline");
|
||||
let originalHeight = overview.fixedHeight;
|
||||
|
||||
ok($(".waterfall-marker-bar[type=Styles]"), "Found at least one 'Styles' marker (1)");
|
||||
@ -46,9 +41,7 @@ function* spawnTest() {
|
||||
|
||||
let heightBefore = overview.fixedHeight;
|
||||
EventUtils.synthesizeMouseAtCenter(menuItem1, {type: "mouseup"}, panel.panelWin);
|
||||
yield once(menuItem1, "command");
|
||||
|
||||
yield waitUntil(() => !waterfall._outstandingMarkers.length);
|
||||
yield waitForOverviewAndCommand(overview, menuItem1);
|
||||
|
||||
is(overview.fixedHeight, heightBefore, "Overview height hasn't changed");
|
||||
ok(!$(".waterfall-marker-bar[type=Styles]"), "No 'Styles' marker (2)");
|
||||
@ -57,9 +50,7 @@ function* spawnTest() {
|
||||
|
||||
heightBefore = overview.fixedHeight;
|
||||
EventUtils.synthesizeMouseAtCenter(menuItem2, {type: "mouseup"}, panel.panelWin);
|
||||
yield once(menuItem2, "command");
|
||||
|
||||
yield waitUntil(() => !waterfall._outstandingMarkers.length);
|
||||
yield waitForOverviewAndCommand(overview, menuItem2);
|
||||
|
||||
is(overview.fixedHeight, heightBefore, "Overview height hasn't changed");
|
||||
ok(!$(".waterfall-marker-bar[type=Styles]"), "No 'Styles' marker (3)");
|
||||
@ -68,9 +59,7 @@ function* spawnTest() {
|
||||
|
||||
heightBefore = overview.fixedHeight;
|
||||
EventUtils.synthesizeMouseAtCenter(menuItem3, {type: "mouseup"}, panel.panelWin);
|
||||
yield once(menuItem3, "command");
|
||||
|
||||
yield waitUntil(() => !waterfall._outstandingMarkers.length);
|
||||
yield waitForOverviewAndCommand(overview, menuItem3);
|
||||
|
||||
is(overview.fixedHeight, heightBefore - MARKERS_GRAPH_ROW_HEIGHT, "Overview is smaller");
|
||||
ok(!$(".waterfall-marker-bar[type=Styles]"), "No 'Styles' marker (4)");
|
||||
@ -79,11 +68,9 @@ function* spawnTest() {
|
||||
|
||||
for (let item of [menuItem1, menuItem2, menuItem3]) {
|
||||
EventUtils.synthesizeMouseAtCenter(item, {type: "mouseup"}, panel.panelWin);
|
||||
yield once(item, "command");
|
||||
yield waitForOverviewAndCommand(overview, item);
|
||||
}
|
||||
|
||||
yield waitUntil(() => !waterfall._outstandingMarkers.length);
|
||||
|
||||
ok($(".waterfall-marker-bar[type=Styles]"), "Found at least one 'Styles' marker (5)");
|
||||
ok($(".waterfall-marker-bar[type=Reflow]"), "Found at least one 'Reflow' marker (5)");
|
||||
ok($(".waterfall-marker-bar[type=Paint]"), "Found at least one 'Paint' marker (5)");
|
||||
@ -93,3 +80,9 @@ function* spawnTest() {
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
||||
|
||||
function waitForOverviewAndCommand(overview, item) {
|
||||
let overviewRendered = overview.once("refresh");
|
||||
let menuitemCommandDispatched = once(item, "command");
|
||||
return Promise.all([overviewRendered, menuitemCommandDispatched]);
|
||||
}
|
||||
|
@ -25,27 +25,30 @@ function* spawnTest() {
|
||||
DetailsView.selectView("waterfall"),
|
||||
once(WaterfallView, EVENTS.WATERFALL_RENDERED)
|
||||
]);
|
||||
|
||||
yield stopRecording(panel);
|
||||
ok(true, "Recording has ended.");
|
||||
|
||||
yield rendered;
|
||||
ok(true, "Recording has rendered.");
|
||||
|
||||
// Test the waterfall background.
|
||||
|
||||
let parentWidth = $("#waterfall-view").getBoundingClientRect().width;
|
||||
let sidebarWidth = $(".waterfall-sidebar").getBoundingClientRect().width;
|
||||
let detailsWidth = $("#waterfall-details").getBoundingClientRect().width;
|
||||
let waterfallWidth = WaterfallView.waterfall._waterfallWidth;
|
||||
let waterfallWidth = WaterfallView._markersRoot._waterfallWidth;
|
||||
is(waterfallWidth, parentWidth - sidebarWidth - detailsWidth,
|
||||
"The waterfall width is correct.")
|
||||
|
||||
ok(WaterfallView.waterfall._canvas,
|
||||
ok(WaterfallView._waterfallHeader._canvas,
|
||||
"A canvas should be created after the recording ended.");
|
||||
ok(WaterfallView.waterfall._ctx,
|
||||
ok(WaterfallView._waterfallHeader._ctx,
|
||||
"A 2d context should be created after the recording ended.");
|
||||
|
||||
is(WaterfallView.waterfall._canvas.width, waterfallWidth,
|
||||
is(WaterfallView._waterfallHeader._canvas.width, waterfallWidth,
|
||||
"The canvas width is correct.");
|
||||
is(WaterfallView.waterfall._canvas.height, 1,
|
||||
is(WaterfallView._waterfallHeader._canvas.height, 1,
|
||||
"The canvas height is correct.");
|
||||
|
||||
yield teardown(panel);
|
||||
|
@ -42,26 +42,22 @@ function* spawnTest() {
|
||||
ok($$(".waterfall-header-ticks > .waterfall-header-tick").length > 0,
|
||||
"Some header tick labels should have been created inside the tick node.");
|
||||
|
||||
// Test the markers container.
|
||||
|
||||
ok($(".waterfall-marker-container"),
|
||||
"A marker container should have been created.");
|
||||
|
||||
// Test the markers sidebar (left).
|
||||
|
||||
ok($$(".waterfall-marker-container > .waterfall-sidebar").length,
|
||||
ok($$(".waterfall-tree-item > .waterfall-sidebar").length,
|
||||
"Some marker sidebar nodes should have been created.");
|
||||
ok($$(".waterfall-marker-container > .waterfall-sidebar:not(spacer) > .waterfall-marker-bullet").length,
|
||||
ok($$(".waterfall-tree-item > .waterfall-sidebar > .waterfall-marker-bullet").length,
|
||||
"Some marker color bullets should have been created inside the sidebar.");
|
||||
ok($$(".waterfall-marker-container > .waterfall-sidebar:not(spacer) > .waterfall-marker-name").length,
|
||||
ok($$(".waterfall-tree-item > .waterfall-sidebar > .waterfall-marker-name").length,
|
||||
"Some marker name labels should have been created inside the sidebar.");
|
||||
|
||||
// Test the markers waterfall (right).
|
||||
|
||||
ok($$(".waterfall-marker-item").length,
|
||||
ok($$(".waterfall-tree-item > .waterfall-marker").length,
|
||||
"Some marker waterfall nodes should have been created.");
|
||||
ok($$(".waterfall-marker-item:not(spacer) > .waterfall-marker-bar").length,
|
||||
ok($$(".waterfall-tree-item > .waterfall-marker > .waterfall-marker-bar").length,
|
||||
"Some marker color bars should have been created inside the waterfall.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
||||
|
@ -0,0 +1,57 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the waterfall remembers the selection when rerendering.
|
||||
*/
|
||||
|
||||
function* spawnTest() {
|
||||
let { target, panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { $, $$, EVENTS, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
ok(true, "Recording has started.");
|
||||
|
||||
let updated = 0;
|
||||
OverviewView.on(EVENTS.OVERVIEW_RENDERED, () => updated++);
|
||||
|
||||
ok((yield waitUntil(() => updated > 0)),
|
||||
"The overview graphs were updated a bunch of times.");
|
||||
ok((yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length > 0)),
|
||||
"There are some markers available.");
|
||||
|
||||
yield stopRecording(panel);
|
||||
ok(true, "Recording has ended.");
|
||||
|
||||
let initialBarsCount = $$(".waterfall-marker-bar").length;
|
||||
|
||||
// Select a portion of the overview.
|
||||
let timeline = OverviewView.graphs.get("timeline");
|
||||
let rerendered = WaterfallView.once(EVENTS.WATERFALL_RENDERED);
|
||||
timeline.setSelection({ start: 0, end: timeline.width / 2 })
|
||||
yield rerendered;
|
||||
|
||||
// Focus the second item in the tree.
|
||||
WaterfallView._markersRoot.getChild(1).focus();
|
||||
|
||||
let beforeResizeBarsCount = $$(".waterfall-marker-bar").length;
|
||||
ok(beforeResizeBarsCount < initialBarsCount,
|
||||
"A subset of the total markers was selected.");
|
||||
|
||||
is(Array.indexOf($$(".waterfall-tree-item"), $(".waterfall-tree-item:focus")), 2,
|
||||
"The correct item was focused in the tree.");
|
||||
|
||||
rerendered = WaterfallView.once(EVENTS.WATERFALL_RENDERED);
|
||||
EventUtils.sendMouseEvent({ type: "mouseup" }, WaterfallView.detailsSplitter);
|
||||
yield rerendered;
|
||||
|
||||
let afterResizeBarsCount = $$(".waterfall-marker-bar").length;
|
||||
is(afterResizeBarsCount, beforeResizeBarsCount,
|
||||
"The same subset of the total markers remained visible.");
|
||||
|
||||
is(Array.indexOf($$(".waterfall-tree-item"), $(".waterfall-tree-item:focus")), 2,
|
||||
"The correct item is still focused in the tree.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
@ -7,10 +7,18 @@
|
||||
|
||||
function* spawnTest() {
|
||||
let { target, panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { $, $$, EVENTS, PerformanceController, OverviewView } = panel.panelWin;
|
||||
let { $, $$, EVENTS, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
|
||||
let { L10N, TIMELINE_BLUEPRINT } = devtools.require("devtools/performance/global");
|
||||
let { getMarkerLabel } = devtools.require("devtools/performance/marker-utils");
|
||||
|
||||
// Hijack the markers massaging part of creating the waterfall view,
|
||||
// to prevent collapsing markers and allowing this test to verify
|
||||
// everything individually. A better solution would be to just expand
|
||||
// all markers first and then skip the meta nodes, but I'm lazy.
|
||||
WaterfallView._prepareWaterfallTree = markers => {
|
||||
return { submarkers: markers };
|
||||
};
|
||||
|
||||
yield startRecording(panel);
|
||||
ok(true, "Recording has started.");
|
||||
|
||||
@ -26,34 +34,39 @@ function* spawnTest() {
|
||||
ok(true, "Recording has ended.");
|
||||
|
||||
// Select everything
|
||||
OverviewView.graphs.get("timeline").setSelection({ start: 0, end: OverviewView.graphs.get("timeline").width })
|
||||
let timeline = OverviewView.graphs.get("timeline");
|
||||
let rerendered = WaterfallView.once(EVENTS.WATERFALL_RENDERED);
|
||||
timeline.setSelection({ start: 0, end: timeline.width })
|
||||
yield rerendered;
|
||||
|
||||
let bars = $$(".waterfall-marker-item:not(spacer) > .waterfall-marker-bar");
|
||||
let bars = $$(".waterfall-marker-bar");
|
||||
let markers = PerformanceController.getCurrentRecording().getMarkers();
|
||||
|
||||
ok(bars.length > 2, "got at least 3 markers");
|
||||
ok(bars.length > 2, "Got at least 3 markers (1)");
|
||||
ok(markers.length > 2, "Got at least 3 markers (2)");
|
||||
|
||||
let sidebar = $("#waterfall-details");
|
||||
for (let i = 0; i < bars.length; i++) {
|
||||
let bar = bars[i];
|
||||
bar.click();
|
||||
let m = markers[i];
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, bar);
|
||||
|
||||
is($("#waterfall-details .marker-details-type").getAttribute("value"), getMarkerLabel(m),
|
||||
"sidebar title matches markers name");
|
||||
"Sidebar title matches markers name.");
|
||||
|
||||
let tooltip = $(".marker-details-duration").getAttribute("tooltiptext");
|
||||
let printedDuration = $(".marker-details-duration .marker-details-labelvalue").getAttribute("value");
|
||||
let duration = $(".marker-details-duration .marker-details-labelvalue").getAttribute("value");
|
||||
|
||||
let toMs = ms => L10N.getFormatStrWithNumbers("timeline.tick", ms);
|
||||
|
||||
// Values are rounded. We don't use a strict equality.
|
||||
is(toMs(m.end - m.start), printedDuration, "sidebar duration is valid");
|
||||
is(toMs(m.end - m.start), duration, "Sidebar duration is valid.");
|
||||
|
||||
// For some reason, anything that creates "→" here turns it into a "â" for some reason.
|
||||
// So just check that start and end time are in there somewhere.
|
||||
ok(tooltip.indexOf(toMs(m.start)) !== -1, "tooltip has start time");
|
||||
ok(tooltip.indexOf(toMs(m.end)) !== -1, "tooltip has end time");
|
||||
ok(tooltip.indexOf(toMs(m.start)) !== -1, "Tooltip has start time.");
|
||||
ok(tooltip.indexOf(toMs(m.end)) !== -1, "Tooltip has end time.");
|
||||
}
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
||||
|
358
browser/devtools/performance/test/browser_waterfall-collapse.js
Normal file
358
browser/devtools/performance/test/browser_waterfall-collapse.js
Normal file
@ -0,0 +1,358 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the waterfall collapsing logic works properly.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
const WaterfallUtils = devtools.require("devtools/performance/waterfall-utils");
|
||||
|
||||
let rootMarkerNode = WaterfallUtils.makeEmptyMarkerNode("(root)");
|
||||
|
||||
WaterfallUtils.collapseMarkersIntoNode({
|
||||
markerNode: rootMarkerNode,
|
||||
markersList: gTestMarkers
|
||||
});
|
||||
|
||||
is(rootMarkerNode.toSource(), gExpectedOutput.toSource(),
|
||||
"The markers didn't collapse properly.");
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
const gTestMarkers = [
|
||||
// Test collapsing Style markers
|
||||
{
|
||||
start: 1,
|
||||
end: 2,
|
||||
name: "Styles"
|
||||
},
|
||||
{
|
||||
start: 3,
|
||||
end: 4,
|
||||
name: "Styles"
|
||||
},
|
||||
// Test collapsing Reflow markers
|
||||
{
|
||||
start: 5,
|
||||
end: 6,
|
||||
name: "Reflow"
|
||||
},
|
||||
{
|
||||
start: 7,
|
||||
end: 8,
|
||||
name: "Reflow"
|
||||
},
|
||||
// Test collapsing Paint markers
|
||||
{
|
||||
start: 9,
|
||||
end: 10,
|
||||
name: "Paint"
|
||||
}, {
|
||||
start: 11,
|
||||
end: 12,
|
||||
name: "Paint"
|
||||
},
|
||||
// Test standalone DOMEvent markers followed by a different marker
|
||||
{
|
||||
start: 13,
|
||||
end: 14,
|
||||
name: "DOMEvent",
|
||||
eventPhase: 1,
|
||||
type: "foo1"
|
||||
},
|
||||
{
|
||||
start: 15,
|
||||
end: 16,
|
||||
name: "TimeStamp"
|
||||
},
|
||||
// Test a DOMEvent marker followed by a Javascript marker.
|
||||
{
|
||||
start: 17,
|
||||
end: 18,
|
||||
name: "DOMEvent",
|
||||
eventPhase: 2,
|
||||
type: "foo2"
|
||||
}, {
|
||||
start: 19,
|
||||
end: 20,
|
||||
name: "Javascript",
|
||||
stack: 1,
|
||||
endStack: 2
|
||||
},
|
||||
// Test another DOMEvent marker followed by a Javascript marker.
|
||||
{
|
||||
start: 21,
|
||||
end: 22,
|
||||
name: "DOMEvent",
|
||||
eventPhase: 3,
|
||||
type: "foo3"
|
||||
}, {
|
||||
start: 23,
|
||||
end: 24,
|
||||
name: "Javascript",
|
||||
stack: 3,
|
||||
endStack: 4
|
||||
},
|
||||
// Test a DOMEvent marker followed by multiple Javascript markers.
|
||||
{
|
||||
start: 25,
|
||||
end: 26,
|
||||
name: "DOMEvent",
|
||||
eventPhase: 4,
|
||||
type: "foo4"
|
||||
}, {
|
||||
start: 27,
|
||||
end: 28,
|
||||
name: "Javascript",
|
||||
stack: 5,
|
||||
endStack: 6
|
||||
}, {
|
||||
start: 29,
|
||||
end: 30,
|
||||
name: "Javascript",
|
||||
stack: 7,
|
||||
endStack: 8
|
||||
}, {
|
||||
start: 31,
|
||||
end: 32,
|
||||
name: "Javascript",
|
||||
stack: 9,
|
||||
endStack: 10
|
||||
},
|
||||
// Test multiple DOMEvent markers followed by multiple Javascript markers.
|
||||
{
|
||||
start: 33,
|
||||
end: 34,
|
||||
name: "DOMEvent",
|
||||
eventPhase: 5,
|
||||
type: "foo5"
|
||||
}, {
|
||||
start: 35,
|
||||
end: 36,
|
||||
name: "DOMEvent",
|
||||
eventPhase: 6,
|
||||
type: "foo6"
|
||||
}, {
|
||||
start: 37,
|
||||
end: 38,
|
||||
name: "DOMEvent",
|
||||
eventPhase: 7,
|
||||
type: "foo6"
|
||||
}, {
|
||||
start: 39,
|
||||
end: 40,
|
||||
name: "Javascript",
|
||||
stack: 11,
|
||||
endStack: 12
|
||||
}, {
|
||||
start: 41,
|
||||
end: 42,
|
||||
name: "Javascript",
|
||||
stack: 13,
|
||||
endStack: 14
|
||||
}, {
|
||||
start: 43,
|
||||
end: 44,
|
||||
name: "Javascript",
|
||||
stack: 15,
|
||||
endStack: 16
|
||||
},
|
||||
// Test a lonely marker at the end.
|
||||
{
|
||||
start: 45,
|
||||
end: 46,
|
||||
name: "GarbageCollection"
|
||||
}
|
||||
];
|
||||
|
||||
const gExpectedOutput = {
|
||||
name: "(root)",
|
||||
start: (void 0),
|
||||
end: (void 0),
|
||||
submarkers: [{
|
||||
name: "Styles",
|
||||
start: 1,
|
||||
end: 4,
|
||||
submarkers: [{
|
||||
start: 1,
|
||||
end: 2,
|
||||
name: "Styles"
|
||||
}, {
|
||||
start: 3,
|
||||
end: 4,
|
||||
name: "Styles"
|
||||
}]
|
||||
}, {
|
||||
name: "Reflow",
|
||||
start: 5,
|
||||
end: 8,
|
||||
submarkers: [{
|
||||
start: 5,
|
||||
end: 6,
|
||||
name: "Reflow"
|
||||
}, {
|
||||
start: 7,
|
||||
end: 8,
|
||||
name: "Reflow"
|
||||
}]
|
||||
}, {
|
||||
name: "Paint",
|
||||
start: 9,
|
||||
end: 12,
|
||||
submarkers: [{
|
||||
start: 9,
|
||||
end: 10,
|
||||
name: "Paint"
|
||||
}, {
|
||||
start: 11,
|
||||
end: 12,
|
||||
name: "Paint"
|
||||
}]
|
||||
}, {
|
||||
start: 13,
|
||||
end: 14,
|
||||
name: "DOMEvent",
|
||||
eventPhase: 1,
|
||||
type: "foo1"
|
||||
}, {
|
||||
start: 15,
|
||||
end: 16,
|
||||
name: "TimeStamp"
|
||||
}, {
|
||||
name: "meta::DOMEvent+JS",
|
||||
start: 17,
|
||||
end: 20,
|
||||
submarkers: [{
|
||||
start: 17,
|
||||
end: 18,
|
||||
name: "DOMEvent",
|
||||
eventPhase: 2,
|
||||
type: "foo2"
|
||||
}, {
|
||||
start: 19,
|
||||
end: 20,
|
||||
name: "Javascript",
|
||||
stack: 1,
|
||||
endStack: 2
|
||||
}],
|
||||
type: "foo2",
|
||||
eventPhase: 2,
|
||||
stack: 1,
|
||||
endStack: 2
|
||||
}, {
|
||||
name: "meta::DOMEvent+JS",
|
||||
start: 21,
|
||||
end: 24,
|
||||
submarkers: [{
|
||||
start: 21,
|
||||
end: 22,
|
||||
name: "DOMEvent",
|
||||
eventPhase: 3,
|
||||
type: "foo3"
|
||||
}, {
|
||||
start: 23,
|
||||
end: 24,
|
||||
name: "Javascript",
|
||||
stack: 3,
|
||||
endStack: 4
|
||||
}],
|
||||
type: "foo3",
|
||||
eventPhase: 3,
|
||||
stack: 3,
|
||||
endStack: 4
|
||||
}, {
|
||||
name: "meta::DOMEvent+JS",
|
||||
start: 25,
|
||||
end: 28,
|
||||
submarkers: [{
|
||||
start: 25,
|
||||
end: 26,
|
||||
name: "DOMEvent",
|
||||
eventPhase: 4,
|
||||
type: "foo4"
|
||||
}, {
|
||||
start: 27,
|
||||
end: 28,
|
||||
name: "Javascript",
|
||||
stack: 5,
|
||||
endStack: 6
|
||||
}],
|
||||
type: "foo4",
|
||||
eventPhase: 4,
|
||||
stack: 5,
|
||||
endStack: 6
|
||||
}, {
|
||||
name: "Javascript",
|
||||
start: 29,
|
||||
end: 32,
|
||||
submarkers: [{
|
||||
start: 29,
|
||||
end: 30,
|
||||
name: "Javascript",
|
||||
stack: 7,
|
||||
endStack: 8
|
||||
}, {
|
||||
start: 31,
|
||||
end: 32,
|
||||
name: "Javascript",
|
||||
stack: 9,
|
||||
endStack: 10
|
||||
}]
|
||||
}, {
|
||||
start: 33,
|
||||
end: 34,
|
||||
name: "DOMEvent",
|
||||
eventPhase: 5,
|
||||
type: "foo5"
|
||||
}, {
|
||||
start: 35,
|
||||
end: 36,
|
||||
name: "DOMEvent",
|
||||
eventPhase: 6,
|
||||
type: "foo6"
|
||||
}, {
|
||||
name: "meta::DOMEvent+JS",
|
||||
start: 37,
|
||||
end: 40,
|
||||
submarkers: [{
|
||||
start: 37,
|
||||
end: 38,
|
||||
name: "DOMEvent",
|
||||
eventPhase: 7,
|
||||
type: "foo6"
|
||||
}, {
|
||||
start: 39,
|
||||
end: 40,
|
||||
name: "Javascript",
|
||||
stack: 11,
|
||||
endStack: 12
|
||||
}],
|
||||
type: "foo6",
|
||||
eventPhase: 7,
|
||||
stack: 11,
|
||||
endStack: 12
|
||||
}, {
|
||||
name: "Javascript",
|
||||
start: 41,
|
||||
end: 44,
|
||||
submarkers: [{
|
||||
start: 41,
|
||||
end: 42,
|
||||
name: "Javascript",
|
||||
stack: 13,
|
||||
endStack: 14
|
||||
}, {
|
||||
start: 43,
|
||||
end: 44,
|
||||
name: "Javascript",
|
||||
stack: 15,
|
||||
endStack: 16
|
||||
}]
|
||||
}, {
|
||||
start: 45,
|
||||
end: 46,
|
||||
name: "GarbageCollection"
|
||||
}]
|
||||
};
|
@ -3,7 +3,8 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const MARKER_DETAILS_WIDTH = 300;
|
||||
const WATERFALL_RESIZE_EVENTS_DRAIN = 100; // ms
|
||||
const MARKER_DETAILS_WIDTH = 200;
|
||||
|
||||
/**
|
||||
* Waterfall view containing the timeline markers, controlled by DetailsView.
|
||||
@ -26,24 +27,22 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
initialize: function () {
|
||||
DetailsSubview.initialize.call(this);
|
||||
|
||||
// TODO bug 1167093 save the previously set width, and ensure minimum width
|
||||
$("#waterfall-details").setAttribute("width", MARKER_DETAILS_WIDTH);
|
||||
|
||||
this.waterfall = new Waterfall($("#waterfall-breakdown"), $("#waterfall-view"));
|
||||
this.details = new MarkerDetails($("#waterfall-details"), $("#waterfall-view > splitter"));
|
||||
|
||||
this._onMarkerSelected = this._onMarkerSelected.bind(this);
|
||||
this._onResize = this._onResize.bind(this);
|
||||
this._onViewSource = this._onViewSource.bind(this);
|
||||
|
||||
this.waterfall.on("selected", this._onMarkerSelected);
|
||||
this.waterfall.on("unselected", this._onMarkerSelected);
|
||||
this.headerContainer = $("#waterfall-header");
|
||||
this.breakdownContainer = $("#waterfall-breakdown");
|
||||
this.detailsContainer = $("#waterfall-details");
|
||||
this.detailsSplitter = $("#waterfall-view > splitter");
|
||||
|
||||
this.details = new MarkerDetails($("#waterfall-details"), $("#waterfall-view > splitter"));
|
||||
this.details.on("resize", this._onResize);
|
||||
this.details.on("view-source", this._onViewSource);
|
||||
window.addEventListener("resize", this._onResize);
|
||||
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
this.waterfall.setBlueprint(blueprint);
|
||||
this.waterfall.recalculateBounds();
|
||||
// TODO bug 1167093 save the previously set width, and ensure minimum width
|
||||
this.details.width = MARKER_DETAILS_WIDTH;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -52,10 +51,9 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
destroy: function () {
|
||||
DetailsSubview.destroy.call(this);
|
||||
|
||||
this.waterfall.off("selected", this._onMarkerSelected);
|
||||
this.waterfall.off("unselected", this._onMarkerSelected);
|
||||
this.details.off("resize", this._onResize);
|
||||
this.details.off("view-source", this._onViewSource);
|
||||
window.removeEventListener("resize", this._onResize);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -69,7 +67,9 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
let startTime = interval.startTime || 0;
|
||||
let endTime = interval.endTime || recording.getDuration();
|
||||
let markers = recording.getMarkers();
|
||||
this.waterfall.setData({ markers, interval: { startTime, endTime } });
|
||||
let rootMarkerNode = this._prepareWaterfallTree(markers);
|
||||
|
||||
this._populateWaterfallTree(rootMarkerNode, { startTime, endTime });
|
||||
this.emit(EVENTS.WATERFALL_RENDERED);
|
||||
},
|
||||
|
||||
@ -79,18 +79,15 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
*/
|
||||
_onMarkerSelected: function (event, marker) {
|
||||
let recording = PerformanceController.getCurrentRecording();
|
||||
// Race condition in tests due to lazy rendering of markers in the
|
||||
// waterfall? intermittent bug 1157523
|
||||
if (!recording) {
|
||||
return;
|
||||
}
|
||||
let frames = recording.getFrames();
|
||||
|
||||
if (event === "selected") {
|
||||
this.details.render({ toolbox: gToolbox, marker, frames });
|
||||
this._selected = marker;
|
||||
}
|
||||
if (event === "unselected") {
|
||||
this.details.empty();
|
||||
this._selected = null;
|
||||
}
|
||||
},
|
||||
|
||||
@ -98,8 +95,10 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
* Called when the marker details view is resized.
|
||||
*/
|
||||
_onResize: function () {
|
||||
this.waterfall.recalculateBounds();
|
||||
this.render();
|
||||
setNamedTimeout("waterfall-resize", WATERFALL_RESIZE_EVENTS_DRAIN, () => {
|
||||
this._markersRoot.recalculateBounds();
|
||||
this.render(OverviewView.getTimeInterval());
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -107,7 +106,7 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
*/
|
||||
_onObservedPrefChange: function(_, prefName) {
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
this.waterfall.setBlueprint(blueprint);
|
||||
this._markersRoot.blueprint = blueprint;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -117,5 +116,59 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
gToolbox.viewSourceInDebugger(file, line);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the recording is stopped and prepares data to
|
||||
* populate the waterfall tree.
|
||||
*/
|
||||
_prepareWaterfallTree: function(markers) {
|
||||
let rootMarkerNode = WaterfallUtils.makeEmptyMarkerNode("(root)");
|
||||
|
||||
WaterfallUtils.collapseMarkersIntoNode({
|
||||
markerNode: rootMarkerNode,
|
||||
markersList: markers
|
||||
});
|
||||
|
||||
return rootMarkerNode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the waterfall tree.
|
||||
*/
|
||||
_populateWaterfallTree: function(rootMarkerNode, interval) {
|
||||
let root = new MarkerView({
|
||||
marker: rootMarkerNode,
|
||||
// The root node is irrelevant in a waterfall tree.
|
||||
hidden: true,
|
||||
// The waterfall tree should not expand by default.
|
||||
autoExpandDepth: 0
|
||||
});
|
||||
|
||||
let header = new WaterfallHeader(root);
|
||||
|
||||
this._markersRoot = root;
|
||||
this._waterfallHeader = header;
|
||||
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
root.blueprint = blueprint;
|
||||
root.interval = interval;
|
||||
root.on("selected", this._onMarkerSelected);
|
||||
root.on("unselected", this._onMarkerSelected);
|
||||
|
||||
this.breakdownContainer.innerHTML = "";
|
||||
root.attachTo(this.breakdownContainer);
|
||||
|
||||
this.headerContainer.innerHTML = "";
|
||||
header.attachTo(this.headerContainer);
|
||||
|
||||
// If an item was previously selected in this view, attempt to
|
||||
// re-select it by traversing the newly created tree.
|
||||
if (this._selected) {
|
||||
let item = root.find(i => i.marker == this._selected);
|
||||
if (item) {
|
||||
item.focus();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toString: () => "[object WaterfallView]"
|
||||
});
|
||||
|
4
browser/devtools/projecteditor/test/.eslintrc
Normal file
4
browser/devtools/projecteditor/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/responsivedesign/test/.eslintrc
Normal file
4
browser/devtools/responsivedesign/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/scratchpad/test/.eslintrc
Normal file
4
browser/devtools/scratchpad/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/shadereditor/test/.eslintrc
Normal file
4
browser/devtools/shadereditor/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/shared/test/.eslintrc
Normal file
4
browser/devtools/shared/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/shared/test/unit/.eslintrc
Normal file
4
browser/devtools/shared/test/unit/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the common devtools xpcshell eslintrc config.
|
||||
"extends": "../../../.eslintrc.xpcshell"
|
||||
}
|
@ -5,12 +5,16 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/event-emitter.js");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
|
||||
"resource://gre/modules/devtools/event-emitter.js");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["AbstractTreeItem"];
|
||||
|
||||
@ -117,13 +121,12 @@ function AbstractTreeItem({ parent, level }) {
|
||||
this._parentItem = parent;
|
||||
this._level = level || 0;
|
||||
this._childTreeItems = [];
|
||||
this._onArrowClick = this._onArrowClick.bind(this);
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onDoubleClick = this._onDoubleClick.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
this._onFocus = this._onFocus.bind(this);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
// Events are always propagated through the root item. Decorating every
|
||||
// tree item as an event emitter is a very costly operation.
|
||||
if (this == this._rootItem) {
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
}
|
||||
|
||||
AbstractTreeItem.prototype = {
|
||||
@ -150,7 +153,8 @@ AbstractTreeItem.prototype = {
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
_displaySelf: function(document, arrowNode) {
|
||||
throw "This method needs to be implemented by inheriting classes.";
|
||||
throw new Error(
|
||||
"The `_displaySelf` method needs to be implemented by inheriting classes.");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -162,7 +166,16 @@ AbstractTreeItem.prototype = {
|
||||
* @param array:AbstractTreeItem children
|
||||
*/
|
||||
_populateSelf: function(children) {
|
||||
throw "This method needs to be implemented by inheriting classes.";
|
||||
throw new Error(
|
||||
"The `_populateSelf` method needs to be implemented by inheriting classes.");
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the this tree's owner document.
|
||||
* @return Document
|
||||
*/
|
||||
get document() {
|
||||
return this._containerNode.ownerDocument;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -221,18 +234,36 @@ AbstractTreeItem.prototype = {
|
||||
return this._expanded;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the bounds for this tree's container without flushing.
|
||||
* @return object
|
||||
*/
|
||||
get bounds() {
|
||||
let win = this.document.defaultView;
|
||||
let utils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
return utils.getBoundsWithoutFlushing(this._containerNode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates and appends this tree item to the specified parent element.
|
||||
*
|
||||
* @param nsIDOMNode containerNode
|
||||
* The parent element for this tree item (and every other tree item).
|
||||
* @param nsIDOMNode beforeNode
|
||||
* The child element which should succeed this tree item.
|
||||
* @param nsIDOMNode fragmentNode [optional]
|
||||
* An optional document fragment temporarily holding this tree item in
|
||||
* the current batch. Defaults to the `containerNode`.
|
||||
* @param nsIDOMNode beforeNode [optional]
|
||||
* An optional child element which should succeed this tree item.
|
||||
*/
|
||||
attachTo: function(containerNode, beforeNode = null) {
|
||||
attachTo: function(containerNode, fragmentNode = containerNode, beforeNode = null) {
|
||||
this._containerNode = containerNode;
|
||||
this._constructTargetNode();
|
||||
containerNode.insertBefore(this._targetNode, beforeNode);
|
||||
|
||||
if (beforeNode) {
|
||||
fragmentNode.insertBefore(this._targetNode, beforeNode);
|
||||
} else {
|
||||
fragmentNode.appendChild(this._targetNode);
|
||||
}
|
||||
|
||||
if (this._level < this.autoExpandDepth) {
|
||||
this.expand();
|
||||
@ -265,6 +296,7 @@ AbstractTreeItem.prototype = {
|
||||
}
|
||||
this._expanded = true;
|
||||
this._arrowNode.setAttribute("open", "");
|
||||
this._targetNode.setAttribute("expanded", "");
|
||||
this._toggleChildren(true);
|
||||
this._rootItem.emit("expand", this);
|
||||
},
|
||||
@ -278,6 +310,7 @@ AbstractTreeItem.prototype = {
|
||||
}
|
||||
this._expanded = false;
|
||||
this._arrowNode.removeAttribute("open");
|
||||
this._targetNode.removeAttribute("expanded", "");
|
||||
this._toggleChildren(false);
|
||||
this._rootItem.emit("collapse", this);
|
||||
},
|
||||
@ -292,6 +325,33 @@ AbstractTreeItem.prototype = {
|
||||
return this._childTreeItems[index];
|
||||
},
|
||||
|
||||
/**
|
||||
* Calls the provided function on all the descendants of this item.
|
||||
* If this item was never expanded, then no descendents exist yet.
|
||||
* @param function cb
|
||||
*/
|
||||
traverse: function(cb) {
|
||||
for (let child of this._childTreeItems) {
|
||||
cb(child);
|
||||
child.bfs();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Calls the provided function on all descendants of this item until
|
||||
* a truthy value is returned by the predicate.
|
||||
* @param function predicate
|
||||
* @return AbstractTreeItem
|
||||
*/
|
||||
find: function(predicate) {
|
||||
for (let child of this._childTreeItems) {
|
||||
if (predicate(child) || child.find(predicate)) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows or hides all the children of this item in the tree. If neessary,
|
||||
* populates this item with children.
|
||||
@ -315,17 +375,16 @@ AbstractTreeItem.prototype = {
|
||||
* Shows all children of this item in the tree.
|
||||
*/
|
||||
_showChildren: function() {
|
||||
let childTreeItems = this._childTreeItems;
|
||||
let expandedChildTreeItems = childTreeItems.filter(e => e._expanded);
|
||||
let nextNode = this._getSiblingAtDelta(1);
|
||||
|
||||
// First append the child items, and afterwards append any descendants.
|
||||
// Otherwise, the tree will become garbled and nodes will intertwine.
|
||||
for (let item of childTreeItems) {
|
||||
item.attachTo(this._containerNode, nextNode);
|
||||
// If this is the root item and we're not expanding any child nodes,
|
||||
// it is safe to append everything at once.
|
||||
if (this == this._rootItem && this.autoExpandDepth == 0) {
|
||||
this._appendChildrenBatch();
|
||||
}
|
||||
for (let item of expandedChildTreeItems) {
|
||||
item._showChildren();
|
||||
// Otherwise, append the child items and their descendants successively;
|
||||
// if not, the tree will become garbled and nodes will intertwine,
|
||||
// since all the tree items are sharing a single container node.
|
||||
else {
|
||||
this._appendChildrenSuccessive();
|
||||
}
|
||||
},
|
||||
|
||||
@ -339,6 +398,40 @@ AbstractTreeItem.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Appends all children in a single batch.
|
||||
* This only works properly for root nodes when no child nodes will expand.
|
||||
*/
|
||||
_appendChildrenBatch: function() {
|
||||
if (this._fragment === undefined) {
|
||||
this._fragment = this.document.createDocumentFragment();
|
||||
}
|
||||
|
||||
let childTreeItems = this._childTreeItems;
|
||||
|
||||
for (let i = 0, len = childTreeItems.length; i < len; i++) {
|
||||
childTreeItems[i].attachTo(this._containerNode, this._fragment);
|
||||
}
|
||||
|
||||
this._containerNode.appendChild(this._fragment);
|
||||
},
|
||||
|
||||
/**
|
||||
* Appends all children successively.
|
||||
*/
|
||||
_appendChildrenSuccessive: function() {
|
||||
let childTreeItems = this._childTreeItems;
|
||||
let expandedChildTreeItems = childTreeItems.filter(e => e._expanded);
|
||||
let nextNode = this._getSiblingAtDelta(1);
|
||||
|
||||
for (let i = 0, len = childTreeItems.length; i < len; i++) {
|
||||
childTreeItems[i].attachTo(this._containerNode, undefined, nextNode);
|
||||
}
|
||||
for (let i = 0, len = expandedChildTreeItems.length; i < len; i++) {
|
||||
expandedChildTreeItems[i]._showChildren();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Constructs and stores the target node displaying this tree item.
|
||||
*/
|
||||
@ -346,7 +439,14 @@ AbstractTreeItem.prototype = {
|
||||
if (this._constructed) {
|
||||
return;
|
||||
}
|
||||
let document = this._containerNode.ownerDocument;
|
||||
this._onArrowClick = this._onArrowClick.bind(this);
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onDoubleClick = this._onDoubleClick.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
this._onFocus = this._onFocus.bind(this);
|
||||
this._onBlur = this._onBlur.bind(this);
|
||||
|
||||
let document = this.document;
|
||||
|
||||
let arrowNode = this._arrowNode = document.createElement("hbox");
|
||||
arrowNode.className = "arrow theme-twisty";
|
||||
@ -359,6 +459,7 @@ AbstractTreeItem.prototype = {
|
||||
targetNode.addEventListener("dblclick", this._onDoubleClick);
|
||||
targetNode.addEventListener("keypress", this._onKeyPress);
|
||||
targetNode.addEventListener("focus", this._onFocus);
|
||||
targetNode.addEventListener("blur", this._onBlur);
|
||||
|
||||
this._constructed = true;
|
||||
},
|
||||
@ -434,7 +535,6 @@ AbstractTreeItem.prototype = {
|
||||
if (!e.target.classList.contains("arrow")) {
|
||||
this._onArrowClick(e);
|
||||
}
|
||||
|
||||
this.focus();
|
||||
},
|
||||
|
||||
@ -477,5 +577,12 @@ AbstractTreeItem.prototype = {
|
||||
*/
|
||||
_onFocus: function(e) {
|
||||
this._rootItem.emit("focus", this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "blur" event on the element displaying this tree item.
|
||||
*/
|
||||
_onBlur: function(e) {
|
||||
this._rootItem.emit("blur", this);
|
||||
}
|
||||
};
|
||||
|
@ -3,19 +3,32 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { ViewHelpers } = require("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
const { AbstractCanvasGraph, GraphArea, GraphAreaDragger } = require("resource:///modules/devtools/Graphs.jsm");
|
||||
const { Promise } = require("resource://gre/modules/Promise.jsm");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const { getColor } = require("devtools/shared/theme");
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
const FrameUtils = require("devtools/performance/frame-utils");
|
||||
const { ViewHelpers } = require("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
const { setNamedTimeout, clearNamedTimeout } = require("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
|
||||
loader.lazyRequireGetter(this, "getColor",
|
||||
"devtools/shared/theme", true);
|
||||
|
||||
loader.lazyRequireGetter(this, "CATEGORY_MAPPINGS",
|
||||
"devtools/performance/global", true);
|
||||
loader.lazyRequireGetter(this, "FrameUtils",
|
||||
"devtools/performance/frame-utils");
|
||||
|
||||
loader.lazyImporter(this, "AbstractCanvasGraph",
|
||||
"resource:///modules/devtools/Graphs.jsm");
|
||||
loader.lazyImporter(this, "GraphArea",
|
||||
"resource:///modules/devtools/Graphs.jsm");
|
||||
loader.lazyImporter(this, "GraphAreaDragger",
|
||||
"resource:///modules/devtools/Graphs.jsm");
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const GRAPH_SRC = "chrome://browser/content/devtools/graphs-frame.xhtml";
|
||||
|
||||
const L10N = new ViewHelpers.L10N();
|
||||
|
||||
const GRAPH_RESIZE_EVENTS_DRAIN = 100; // ms
|
||||
@ -112,7 +125,7 @@ function FlameGraph(parent, sharpness) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this._parent = parent;
|
||||
this._ready = Promise.defer();
|
||||
this._ready = promise.defer();
|
||||
|
||||
this.setTheme();
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
4
browser/devtools/sourceeditor/test/.eslintrc
Normal file
4
browser/devtools/sourceeditor/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/storage/test/.eslintrc
Normal file
4
browser/devtools/storage/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
4
browser/devtools/styleeditor/test/.eslintrc
Normal file
4
browser/devtools/styleeditor/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
@ -2685,13 +2685,45 @@ RuleEditor.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
let ruleView = this.ruleView;
|
||||
let elementStyle = ruleView._elementStyle;
|
||||
let element = elementStyle.element;
|
||||
let supportsUnmatchedRules =
|
||||
this.rule.domRule.supportsModifySelectorUnmatched;
|
||||
|
||||
this.isEditing = true;
|
||||
|
||||
this.rule.domRule.modifySelector(aValue).then(isModified => {
|
||||
this.rule.domRule.modifySelector(element, aValue).then(response => {
|
||||
this.isEditing = false;
|
||||
|
||||
if (isModified) {
|
||||
this.ruleView.refreshPanel();
|
||||
if (!supportsUnmatchedRules) {
|
||||
if (response) {
|
||||
this.ruleView.refreshPanel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let {ruleProps, isMatching} = response;
|
||||
if (!ruleProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newRule = new Rule(elementStyle, ruleProps);
|
||||
let editor = new RuleEditor(ruleView, newRule);
|
||||
let rules = elementStyle.rules;
|
||||
|
||||
rules.splice(rules.indexOf(this.rule), 1);
|
||||
rules.push(newRule);
|
||||
elementStyle._changed();
|
||||
|
||||
editor.element.setAttribute("unmatched", !isMatching);
|
||||
this.element.parentNode.replaceChild(editor.element, this.element);
|
||||
|
||||
// Remove highlight for modified selector
|
||||
if (ruleView.highlightedSelector &&
|
||||
ruleView.highlightedSelector == this.rule.selectorText) {
|
||||
ruleView.toggleSelectorHighlighter(ruleView.lastSelectorIcon,
|
||||
ruleView.highlightedSelector);
|
||||
}
|
||||
}).then(null, err => {
|
||||
this.isEditing = false;
|
||||
|
4
browser/devtools/styleinspector/test/.eslintrc
Normal file
4
browser/devtools/styleinspector/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
@ -87,6 +87,9 @@ skip-if = e10s # Bug 1039528: "inspect element" contextual-menu doesn't work wit
|
||||
[browser_ruleview_edit-selector-commit.js]
|
||||
[browser_ruleview_edit-selector_01.js]
|
||||
[browser_ruleview_edit-selector_02.js]
|
||||
[browser_ruleview_edit-selector_03.js]
|
||||
[browser_ruleview_edit-selector_04.js]
|
||||
[browser_ruleview_edit-selector_05.js]
|
||||
[browser_ruleview_eyedropper.js]
|
||||
[browser_ruleview_filtereditor-appears-on-swatch-click.js]
|
||||
[browser_ruleview_filtereditor-commit-on-ENTER.js]
|
||||
|
@ -26,7 +26,7 @@ add_task(function*() {
|
||||
info("Selecting the test element");
|
||||
yield selectNode("#testid", inspector);
|
||||
|
||||
info("Waiting for rule view to change");
|
||||
info("Waiting for rule view to update");
|
||||
let onRuleViewChanged = once(view, "ruleview-changed");
|
||||
|
||||
info("Adding the new rule");
|
||||
@ -36,7 +36,7 @@ add_task(function*() {
|
||||
|
||||
yield testEditSelector(view, "span");
|
||||
|
||||
info("Selecting the modified element");
|
||||
info("Selecting the modified element with the new rule");
|
||||
yield selectNode("span", inspector);
|
||||
yield checkModifiedElement(view, "span");
|
||||
});
|
||||
@ -49,14 +49,15 @@ function* testEditSelector(view, name) {
|
||||
info("Entering a new selector name and committing");
|
||||
editor.value = name;
|
||||
|
||||
info("Waiting for rule view to refresh");
|
||||
let onRuleViewRefresh = once(view, "ruleview-refreshed");
|
||||
info("Waiting for rule view to update");
|
||||
let onRuleViewChanged = once(view, "ruleview-changed");
|
||||
|
||||
info("Entering the commit key");
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
yield onRuleViewRefresh;
|
||||
yield onRuleViewChanged;
|
||||
|
||||
is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
|
||||
is(view._elementStyle.rules.length, 3, "Should have 3 rules.");
|
||||
ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
|
||||
}
|
||||
|
||||
function* checkModifiedElement(view, name) {
|
||||
|
@ -79,14 +79,14 @@ function* testEditSelector(view, name) {
|
||||
info("Entering a new selector name: " + name);
|
||||
editor.input.value = name;
|
||||
|
||||
info("Waiting for rule view to refresh");
|
||||
let onRuleViewRefresh = once(view, "ruleview-refreshed");
|
||||
info("Waiting for rule view to update");
|
||||
let onRuleViewChanged = once(view, "ruleview-changed");
|
||||
|
||||
info("Entering the commit key");
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
yield onRuleViewRefresh;
|
||||
yield onRuleViewChanged;
|
||||
|
||||
is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
|
||||
is(view._elementStyle.rules.length, 3, "Should have 3 rules.");
|
||||
}
|
||||
|
||||
function* checkModifiedElement(view, name, index) {
|
||||
|
@ -48,12 +48,7 @@ const TEST_DATA = [
|
||||
];
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8,test escaping selector change reverts back to original value");
|
||||
|
||||
info("Creating the test document");
|
||||
content.document.body.innerHTML = PAGE_CONTENT;
|
||||
|
||||
info("Opening the rule-view");
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(PAGE_CONTENT));
|
||||
let {toolbox, inspector, view} = yield openRuleView();
|
||||
|
||||
info("Iterating over the test data");
|
||||
@ -88,7 +83,7 @@ function* runTestData(inspector, view, data) {
|
||||
"Value is as expected: " + expected);
|
||||
is(idRuleEditor.isEditing, false, "Selector is not being edited.")
|
||||
} else {
|
||||
yield once(view, "ruleview-refreshed");
|
||||
yield once(view, "ruleview-changed");
|
||||
ok(getRuleViewRule(view, expected),
|
||||
"Rule with " + name + " selector exists.");
|
||||
}
|
||||
|
@ -13,24 +13,19 @@ let PAGE_CONTENT = [
|
||||
' }',
|
||||
'</style>',
|
||||
'<div id="testid" class="testclass">Styled Node</div>',
|
||||
'<span id="testid2">This is a span</span>'
|
||||
'<span>This is a span</span>',
|
||||
].join("\n");
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8,test rule view selector changes");
|
||||
|
||||
info("Creating the test document");
|
||||
content.document.body.innerHTML = PAGE_CONTENT;
|
||||
|
||||
info("Opening the rule-view");
|
||||
let {toolbox, inspector, view} = yield openRuleView();
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(PAGE_CONTENT));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
info("Selecting the test element");
|
||||
yield selectNode("#testid", inspector);
|
||||
yield testEditSelector(view, "span");
|
||||
|
||||
info("Selecting the modified element");
|
||||
yield selectNode("#testid2", inspector);
|
||||
info("Selecting the modified element with the new rule");
|
||||
yield selectNode("span", inspector);
|
||||
yield checkModifiedElement(view, "span");
|
||||
});
|
||||
|
||||
@ -48,16 +43,17 @@ function* testEditSelector(view, name) {
|
||||
info("Entering a new selector name and committing");
|
||||
editor.input.value = name;
|
||||
|
||||
info("Waiting for rule view to refresh");
|
||||
let onRuleViewRefresh = once(view, "ruleview-refreshed");
|
||||
info("Waiting for rule view to update");
|
||||
let onRuleViewChanged = once(view, "ruleview-changed");
|
||||
|
||||
info("Entering the commit key");
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
yield onRuleViewRefresh;
|
||||
yield onRuleViewChanged;
|
||||
|
||||
is(view._elementStyle.rules.length, 1, "Should have 1 rule.");
|
||||
is(getRuleViewRule(view, name), undefined,
|
||||
name + " selector has been removed.");
|
||||
is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
|
||||
ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
|
||||
ok(getRuleViewRuleEditor(view, 1).element.getAttribute("unmatched"),
|
||||
"Rule with " + name + " does not match the current element.");
|
||||
}
|
||||
|
||||
function* checkModifiedElement(view, name) {
|
||||
|
@ -12,7 +12,7 @@ let PAGE_CONTENT = [
|
||||
' .testclass {',
|
||||
' text-align: center;',
|
||||
' }',
|
||||
' #testid3:first-letter {',
|
||||
' #testid3::first-letter {',
|
||||
' text-decoration: "italic"',
|
||||
' }',
|
||||
'</style>',
|
||||
@ -23,21 +23,16 @@ let PAGE_CONTENT = [
|
||||
].join("\n");
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8,test rule view selector changes");
|
||||
|
||||
info("Creating the test document");
|
||||
content.document.body.innerHTML = PAGE_CONTENT;
|
||||
|
||||
info("Opening the rule-view");
|
||||
let {toolbox, inspector, view} = yield openRuleView();
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(PAGE_CONTENT));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
info("Selecting the test element");
|
||||
yield selectNode(".testclass", inspector);
|
||||
yield testEditSelector(view, "div:nth-child(2)");
|
||||
yield testEditSelector(view, "div:nth-child(1)");
|
||||
|
||||
info("Selecting the modified element");
|
||||
yield selectNode("#testid", inspector);
|
||||
yield checkModifiedElement(view, "div:nth-child(2)");
|
||||
yield checkModifiedElement(view, "div:nth-child(1)");
|
||||
|
||||
info("Selecting the test element");
|
||||
yield selectNode("#testid3", inspector);
|
||||
@ -63,16 +58,20 @@ function* testEditSelector(view, name) {
|
||||
info("Entering a new selector name: " + name);
|
||||
editor.input.value = name;
|
||||
|
||||
info("Waiting for rule view to refresh");
|
||||
let onRuleViewRefresh = once(view, "ruleview-refreshed");
|
||||
info("Waiting for rule view to update");
|
||||
let onRuleViewChanged = once(view, "ruleview-changed");
|
||||
|
||||
info("Entering the commit key");
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
yield onRuleViewRefresh;
|
||||
yield onRuleViewChanged;
|
||||
|
||||
is(view._elementStyle.rules.length, 1, "Should have 1 rule.");
|
||||
is(getRuleViewRule(view, name), undefined,
|
||||
name + " selector has been removed.");
|
||||
is(view._elementStyle.rules.length, 2, "Should have 2 rule.");
|
||||
ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
|
||||
|
||||
let newRuleEditor = getRuleViewRuleEditor(view, 1) ||
|
||||
getRuleViewRuleEditor(view, 1, 0);
|
||||
ok(newRuleEditor.element.getAttribute("unmatched"),
|
||||
"Rule with " + name + " does not match the current element.");
|
||||
}
|
||||
|
||||
function* checkModifiedElement(view, name) {
|
||||
|
@ -0,0 +1,46 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Testing selector inplace-editor behaviors in the rule-view with invalid
|
||||
// selectors
|
||||
|
||||
let TEST_URI = [
|
||||
'<style type="text/css">',
|
||||
' .testclass {',
|
||||
' text-align: center;',
|
||||
' }',
|
||||
'</style>',
|
||||
'<div class="testclass">Styled Node</div>',
|
||||
].join("\n");
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode(".testclass", inspector);
|
||||
yield testEditSelector(view, "asd@:::!");
|
||||
});
|
||||
|
||||
function* testEditSelector(view, name) {
|
||||
info("Test editing existing selector fields");
|
||||
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
|
||||
info("Focusing an existing selector name in the rule-view");
|
||||
let editor = yield focusEditableField(ruleEditor.selectorText);
|
||||
|
||||
is(inplaceEditor(ruleEditor.selectorText), editor,
|
||||
"The selector editor got focused");
|
||||
|
||||
info("Entering a new selector name and committing");
|
||||
editor.input.value = name;
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
|
||||
is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
|
||||
is(getRuleViewRule(view, name), undefined,
|
||||
"Rule with " + name + " selector should not exist.");
|
||||
ok(getRuleViewRule(view, ".testclass"),
|
||||
"Rule with .testclass selector exists.");
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that the selector highlighter is removed when modifying a selector and
|
||||
// the selector highlighter works for the newly added unmatched rule.
|
||||
|
||||
const TEST_URI = [
|
||||
'<style type="text/css">',
|
||||
' p {',
|
||||
' background: red;',
|
||||
' }',
|
||||
'</style>',
|
||||
'<p>Test the selector highlighter</p>'
|
||||
].join("\n");
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
ok(!view.selectorHighlighter, "No selectorhighlighter exist in the rule-view");
|
||||
|
||||
yield selectNode("p", inspector);
|
||||
yield testSelectorHighlight(view, "p");
|
||||
yield testEditSelector(view, "body");
|
||||
yield testSelectorHighlight(view, "body");
|
||||
});
|
||||
|
||||
function* testSelectorHighlight(view, name) {
|
||||
info("Test creating selector highlighter");
|
||||
|
||||
info("Clicking on a selector icon");
|
||||
let icon = getRuleViewSelectorHighlighterIcon(view, name);
|
||||
|
||||
let onToggled = view.once("ruleview-selectorhighlighter-toggled");
|
||||
EventUtils.synthesizeMouseAtCenter(icon, {}, view.doc.defaultView);
|
||||
let isVisible = yield onToggled;
|
||||
|
||||
ok(view.selectorHighlighter, "The selectorhighlighter instance was created");
|
||||
ok(isVisible, "The toggle event says the highlighter is visible");
|
||||
}
|
||||
|
||||
function* testEditSelector(view, name) {
|
||||
info("Test editing existing selector fields");
|
||||
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
|
||||
info("Focusing an existing selector name in the rule-view");
|
||||
let editor = yield focusEditableField(ruleEditor.selectorText);
|
||||
|
||||
is(inplaceEditor(ruleEditor.selectorText), editor,
|
||||
"The selector editor got focused");
|
||||
|
||||
info("Waiting for rule view to update");
|
||||
let onToggled = view.once("ruleview-selectorhighlighter-toggled");
|
||||
|
||||
info("Entering a new selector name and committing");
|
||||
editor.input.value = name;
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
|
||||
let isVisible = yield onToggled;
|
||||
|
||||
ok(!view.highlightedSelector, "The selectorhighlighter instance was removed");
|
||||
ok(!isVisible, "The toggle event says the highlighter is not visible");
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that adding a new property of an unmatched rule works properly.
|
||||
|
||||
let TEST_URI = [
|
||||
'<style type="text/css">',
|
||||
' #testid {',
|
||||
' }',
|
||||
' .testclass {',
|
||||
' background-color: white;',
|
||||
' }',
|
||||
'</style>',
|
||||
'<div id="testid">Styled Node</div>',
|
||||
'<span class="testclass">This is a span</span>'
|
||||
].join("\n");
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
info("Selecting the test element");
|
||||
yield selectNode("#testid", inspector);
|
||||
yield testEditSelector(view, "span");
|
||||
yield testAddProperty(view);
|
||||
|
||||
info("Selecting the modified element with the new rule");
|
||||
yield selectNode("span", inspector);
|
||||
yield checkModifiedElement(view, "span");
|
||||
});
|
||||
|
||||
function* testEditSelector(view, name) {
|
||||
info("Test editing existing selector fields");
|
||||
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
|
||||
info("Focusing an existing selector name in the rule-view");
|
||||
let editor = yield focusEditableField(ruleEditor.selectorText);
|
||||
|
||||
is(inplaceEditor(ruleEditor.selectorText), editor,
|
||||
"The selector editor got focused");
|
||||
|
||||
info("Entering a new selector name and committing");
|
||||
editor.input.value = name;
|
||||
|
||||
info("Waiting for rule view to update");
|
||||
let onRuleViewChanged = once(view, "ruleview-changed");
|
||||
|
||||
info("Entering the commit key");
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
yield onRuleViewChanged;
|
||||
|
||||
is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
|
||||
ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
|
||||
ok(getRuleViewRuleEditor(view, 1).element.getAttribute("unmatched"),
|
||||
"Rule with " + name + " does not match the current element.");
|
||||
}
|
||||
|
||||
function* checkModifiedElement(view, name) {
|
||||
is(view._elementStyle.rules.length, 3, "Should have 3 rules.");
|
||||
ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
|
||||
}
|
||||
|
||||
function* testAddProperty(view) {
|
||||
info("Test creating a new property");
|
||||
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
|
||||
info("Focusing a new property name in the rule-view");
|
||||
let editor = yield focusEditableField(ruleEditor.closeBrace);
|
||||
|
||||
is(inplaceEditor(ruleEditor.newPropSpan), editor,
|
||||
"The new property editor got focused");
|
||||
let input = editor.input;
|
||||
|
||||
info("Entering text-align in the property name editor");
|
||||
input.value = "text-align";
|
||||
|
||||
info("Pressing return to commit and focus the new value field");
|
||||
let onValueFocus = once(ruleEditor.element, "focus", true);
|
||||
let onModifications = ruleEditor.rule._applyingModifications;
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
|
||||
yield onValueFocus;
|
||||
yield onModifications;
|
||||
|
||||
// Getting the new value editor after focus
|
||||
editor = inplaceEditor(view.doc.activeElement);
|
||||
let textProp = ruleEditor.rule.textProps[0];
|
||||
|
||||
is(ruleEditor.rule.textProps.length, 1, "Created a new text property.");
|
||||
is(ruleEditor.propertyList.children.length, 1, "Created a property editor.");
|
||||
is(editor, inplaceEditor(textProp.editor.valueSpan),
|
||||
"Editing the value span now.");
|
||||
|
||||
info("Entering a value and bluring the field to expect a rule change");
|
||||
editor.input.value = "center";
|
||||
let onBlur = once(editor.input, "blur");
|
||||
onModifications = ruleEditor.rule._applyingModifications;
|
||||
editor.input.blur();
|
||||
yield onBlur;
|
||||
yield onModifications;
|
||||
|
||||
is(textProp.value, "center", "Text prop should have been changed.");
|
||||
is(textProp.overridden, false, "Property should not be overridden");
|
||||
}
|
4
browser/devtools/styleinspector/test/unit/.eslintrc
Normal file
4
browser/devtools/styleinspector/test/unit/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the common devtools xpcshell eslintrc config.
|
||||
"extends": "../../../.eslintrc.xpcshell"
|
||||
}
|
4
browser/devtools/tilt/test/.eslintrc
Normal file
4
browser/devtools/tilt/test/.eslintrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../.eslintrc.mochitests"
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user