diff --git a/.circleci/config.yml b/.circleci/config.yml
index 93060d4d78..bd3e9d4c51 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -96,7 +96,7 @@ jobs:
steps:
- add_ssh_keys:
fingerprints:
- - "20:8e:de:d1:dd:ed:41:a9:9c:f0:32:20:0a:f0:1b:2e"
+ - "92:e1:5d:84:70:96:c5:19:76:55:1c:b1:7a:12:9e:53"
- checkout
- run: git config user.email "perf-html@mozilla.com"
- run: git config user.name "Firefox Profiler [bot]"
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000..6a30a53a52
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ // Bypass check for node_modules/flow-bin/SHASUM256.txt.sign since it
+ // doesn't exist in flow-bin 0.96.0.
+ "flow.useNPMPackagedFlow": false,
+ "flow.pathToFlow": "${workspaceFolder}/node_modules/.bin/flow",
+}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 781a3aa6dc..b33925edd9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -44,6 +44,8 @@ To get started clone the repo and get the web application started.
5. Point your browser to [http://localhost:4242](http://localhost:4242).
6. If port `4242` is taken, then you can run the web app on a different port: `FX_PROFILER_PORT=1234 yarn start`
+[Flow](https://flow.org/) is used for type checking. VSCode users can install the ["Flow Language Support" extension](https://marketplace.visualstudio.com/items?itemName=flowtype.flow-for-vscode), and disable VSCode's built-in TypeScript extension in the workspace via the [setup instructions here](https://github.com/flow/flow-for-vscode#setup).
+
## Using Gitpod
Alternatively, you can also develop the Firefox Profiler online in a pre-configured development environment:
diff --git a/README.md b/README.md
index df2ebbd1cf..c34e33a2cc 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,8 @@ yarn install
yarn start
```
+[Flow](https://flow.org/) is used for type checking. VSCode users can install the ["Flow Language Support" extension](https://marketplace.visualstudio.com/items?itemName=flowtype.flow-for-vscode), and disable VSCode's built-in TypeScript extension in the workspace via the [setup instructions here](https://github.com/flow/flow-for-vscode#setup).
+
You can also develop the Firefox Profiler online in a pre-configured development environment.
[](https://gitpod.io/#https://github.com/firefox-devtools/profiler)
diff --git a/.babelrc b/babel.config.json
similarity index 100%
rename from .babelrc
rename to babel.config.json
diff --git a/locales/be/app.ftl b/locales/be/app.ftl
index c8420d3f0d..c9227c934e 100644
--- a/locales/be/app.ftl
+++ b/locales/be/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox для Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -74,6 +75,8 @@ CallTree--tracing-ms-total = Час працы (мс)
на працягу якога гэта функцыя знаходзілася ў стэку. Сюды ўваходзіць час,
на працягу якога функцыя фактычна выконвалася, а таксама час выканання
функцый, якія вызвала гэта функцыі.
+CallTree--samples-total = Усяго (узоры)
+ .title = Лічыльнік “Усяго (узоры)” уключае ў сабе суму кожнага ўзору, у якога гэтая функцыя была выяўлена ў стэку. Сюды ўваходзіць час фактычнай працы функцыі, а таксама час, чакання вызаваў, якія рабіла гэтая функцыя.
## Call tree "badges" (icons) with tooltips
##
@@ -138,7 +141,6 @@ Home--menu-button = Уключыць кнопку меню { -profiler-brand-nam
Home--menu-button-instructions =
Уключыце кнопку меню прафайлера, каб пачаць запіс профілю прадукцыйнасці
у { -firefox-brand-name }, затым прааналізуйце яго і падзяліцеся з profiler.firefox.com.
-Home--instructions-title = Як праглядаць і запісваць профілі
Home--instructions-content =
Для запісу профіляў прадукцыйнасці патрабуецца { -firefox-brand-name }.
Аднак існуючыя профілі можна праглядаць у любым сучасным браўзеры.
@@ -309,11 +311,16 @@ MenuButtons--metaInfo-renderRowOfList-label-extensions = Пашырэнні:
MenuButtons--metaOverheadStatistics-mean = Сярэдняе
MenuButtons--metaOverheadStatistics-max = Макс
MenuButtons--metaOverheadStatistics-min = Мін
+MenuButtons--metaOverheadStatistics-statkeys-counter = Лічыльнік
+ .title = Час збору ўсіх лічыльнікаў
+MenuButtons--metaOverheadStatistics-statkeys-interval = Інтэрвал
+ .title = Зафіксаваны інтэрвал паміж двума ўзорамі
MenuButtons--metaOverheadStatistics-profiled-duration = Працягласць запісу профілю:
## Publish panel
## These strings are used in the publishing panel.
+MenuButtons--publish--renderCheckbox-label-include-other-tabs = Уключыць даныя з іншых картак
MenuButtons--publish--renderCheckbox-label-extension = Уключыць інфармацыю аб пашырэнні
MenuButtons--publish--renderCheckbox-label-private-browsing = Уключыць даныя з вокнаў прыватнага прагляду
MenuButtons--publish--renderCheckbox-label-private-browsing-warning-image =
@@ -448,8 +455,12 @@ TrackContextMenu--hide-track = Схаваць “{ $trackName }”
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
## TrackSearchField
diff --git a/locales/de/app.ftl b/locales/de/app.ftl
index da342f84fd..9762b9304d 100644
--- a/locales/de/app.ftl
+++ b/locales/de/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox für Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -233,6 +234,7 @@ Home--menu-button = { -profiler-brand-name }-Menüschaltfläche aktivieren
Home--menu-button-instructions =
Aktivieren Sie die Profiler-Menüschaltfläche, um Leistung in einem Profil von { -firefox-brand-name }
aufzuzeichnen, dann analysieren Sie sie und teilen Sie das Ergebnis auf profiler.firefox.com.
+Home--profile-firefox-android-instructions = Sie können auch Leistungsprofile für { -firefox-android-brand-name } erstellen. Weitere Informationen finden Sie in der Dokumentation Profiling { -firefox-android-brand-name } directly on device.
# The word WebChannel should not be translated.
# This message can be seen on https://main--perf-html.netlify.app/ in the tooltip
# of the "Enable Firefox Profiler menu button" button.
@@ -245,7 +247,6 @@ Home--record-instructions =
Um die Profilerstellung zu starten, klicken Sie auf die Schaltfläche Profilerstellung oder verwenden Sie die
Tastatürkürzel. Das Symbol ist blau, wenn ein Profil aufzeichnet.
Drücken Sie Aufzeichnen, um die Daten in profiler.firefox.com zu laden.
-Home--instructions-title = So können Sie Profile anzeigen und aufzeichnen
Home--instructions-content =
Das Aufzeichnen von Leistungsprofilen benötigt { -firefox-brand-name }.
Vorhandene Profile können jedoch in jedem modernen Browser angezeigt werden.
@@ -256,6 +257,13 @@ Home--additional-content-title = Bestehende Profile laden
Home--additional-content-content = Sie können eine Profildatei per Ziehen und Ablegen hierher bewegen, um sie zu laden, oder:
Home--compare-recordings-info = Sie können auch Aufnahmen vergleichen. Öffnen Sie die Vergleichsschnittstelle.
Home--your-recent-uploaded-recordings-title = Ihre kürzlich hochgeladenen Aufzeichnungen
+# We replace the elements such as and with links to the
+# documentation to use these tools.
+Home--load-files-from-other-tools =
+ Der { -profiler-brand-name } kann auch Leistungsprofile von anderen Profilern importieren, wie z. B.
+ Linux perf, Android SimplePerf, die
+ Chrome Performance Panel, Android Studio oder
+ eine Datei im dhat-Format. Erfahren Sie, wie Sie Ihren eigenen Importeur schreiben.
## IdleSearchField
## The component that is used for all the search inputs in the application.
@@ -685,8 +693,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = Operationen seit der vo
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -702,38 +714,44 @@ TrackPower--tooltip-power-milliwatt = { $value } mW
# watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh
- .label = Im sichtbaren Bereich verbrauchte Energie
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-range-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
+ .label = Im sichtbaren Bereich verwendete Energie
# This is used in the tooltip when the energy used in the current range uses the
# milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh
- .label = Im sichtbaren Bereich verbrauchte Energie
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
+ .label = Im sichtbaren Bereich verwendete Energie
# This is used in the tooltip when the energy used in the current range uses the
# microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh
- .label = Im sichtbaren Bereich verbrauchte Energie
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
+ .label = Im sichtbaren Bereich verwendete Energie
# This is used in the tooltip when the energy used in the current preview
# selection uses the watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh
- .label = In der aktuellen Auswahl verbrauchte Energie
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-preview-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
+ .label = In der aktuellen Auswahl verwendete Energie
# This is used in the tooltip when the energy used in the current preview
# selection uses the milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh
- .label = In der aktuellen Auswahl verbrauchte Energie
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
+ .label = In der aktuellen Auswahl verwendete Energie
# This is used in the tooltip when the energy used in the current preview
# selection uses the microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh
- .label = Im ausgewählten Bereich verbrauchte Energie
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
+ .label = In der aktuellen Auswahl verwendete Energie
## TrackSearchField
## The component that is used for the search input in the track context menu.
diff --git a/locales/el/app.ftl b/locales/el/app.ftl
index c54435fcb2..5b81152355 100644
--- a/locales/el/app.ftl
+++ b/locales/el/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox για Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -243,6 +244,10 @@ Home--menu-button = Ενεργοποίηση κουμπιού μενού του
Home--menu-button-instructions =
Ενεργοποιήστε το κουμπί του μενού του εργαλείου καταγραφής για να ξεκινήσετε ένα προφίλ
επιδόσεων στο { -firefox-brand-name } και έπειτα, να το αναλύσετε και να το μοιραστείτε με το profiler.firefox.com.
+Home--profile-firefox-android-instructions =
+ Μπορείτε επίσης να καταγράψετε προφίλ για το { -firefox-android-brand-name }.
+ Για περισσότερες πληροφορίες, παρακαλούμε συμβουλευτείτε την τεκμηρίωση:
+ Καταγραφή προφίλ του { -firefox-android-brand-name } απευθείας στη συσκευή.
# The word WebChannel should not be translated.
# This message can be seen on https://main--perf-html.netlify.app/ in the tooltip
# of the "Enable Firefox Profiler menu button" button.
@@ -260,7 +265,6 @@ Home--record-instructions =
Για να αρχίσει η καταγραφή προφίλ, κάντε κλικ στο αντίστοιχο κουμπί ή χρησιμοποιήστε
τις συντομεύσεις πληκτρολογίου. Το εικονίδιο είναι μπλε κατά την καταγραφή ενός προφίλ.
Πατήστε το Καταγραφή για να φορτώσετε τα δεδομένα στο profiler.firefox.com.
-Home--instructions-title = Τρόπος προβολής και καταγραφής προφίλ
Home--instructions-content =
Η καταγραφή των προφίλ επιδόσεων απαιτεί το { -firefox-brand-name }.
Ωστόσο, τα υπάρχοντα προφίλ μπορούν να προβληθούν σε όλα τα σύγχρονα προγράμματα περιήγησης.
@@ -700,8 +704,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = λειτουργίες
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -717,38 +725,16 @@ TrackPower--tooltip-power-milliwatt = { $value } mW
# watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh
- .label = Ενέργεια που χρησιμοποιείται στο ορατό εύρος
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-range-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
+ .label = Η ενέργεια που χρησιμοποιείται στο ορατό εύρος
# This is used in the tooltip when the energy used in the current range uses the
# milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh
- .label = Ενέργεια που χρησιμοποιείται στο ορατό εύρος
-# This is used in the tooltip when the energy used in the current range uses the
-# microwatt-hour unit.
-# Variables:
-# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
.label = Η ενέργεια που χρησιμοποιείται στο ορατό εύρος
-# This is used in the tooltip when the energy used in the current preview
-# selection uses the watt-hour unit.
-# Variables:
-# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh
- .label = Ενέργεια που χρησιμοποιείται στην τρέχουσα επιλογή
-# This is used in the tooltip when the energy used in the current preview
-# selection uses the milliwatt-hour unit.
-# Variables:
-# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh
- .label = Ενέργεια που χρησιμοποιείται στην τρέχουσα επιλογή
-# This is used in the tooltip when the energy used in the current preview
-# selection uses the microwatt-hour unit.
-# Variables:
-# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh
- .label = Η ενέργεια που χρησιμοποιείται στην τρέχουσα επιλογή
## TrackSearchField
## The component that is used for the search input in the track context menu.
diff --git a/locales/en-GB/app.ftl b/locales/en-GB/app.ftl
index a98455cd1e..95efce0f9b 100644
--- a/locales/en-GB/app.ftl
+++ b/locales/en-GB/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox for Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -247,6 +248,10 @@ Home--menu-button = Enable { -profiler-brand-name } Menu Button
Home--menu-button-instructions =
Enable the profiler menu button to start recording a performance
profile in { -firefox-brand-name }, then analyse it and share it with profiler.firefox.com.
+Home--profile-firefox-android-instructions =
+ You can also profile { -firefox-android-brand-name }. For more
+ information, please consult this documentation:
+ Profiling { -firefox-android-brand-name } directly on device.
# The word WebChannel should not be translated.
# This message can be seen on https://main--perf-html.netlify.app/ in the tooltip
# of the "Enable Firefox Profiler menu button" button.
@@ -264,7 +269,6 @@ Home--record-instructions =
To start profiling, click on the profiling button, or use the
keyboard shortcuts. The icon is blue when a profile is recording.
Hit Capture to load the data into profiler.firefox.com.
-Home--instructions-title = How to view and record profiles
Home--instructions-content =
Recording performance profiles requires { -firefox-brand-name }.
However, existing profiles can be viewed in any modern browser.
@@ -275,6 +279,14 @@ Home--additional-content-title = Load existing profiles
Home--additional-content-content = You can drag and drop a profile file here to load it, or:
Home--compare-recordings-info = You can also compare recordings. Open the comparing interface.
Home--your-recent-uploaded-recordings-title = Your recent uploaded recordings
+# We replace the elements such as and with links to the
+# documentation to use these tools.
+Home--load-files-from-other-tools =
+ The { -profiler-brand-name } can also import profiles from other profilers, such as
+ Linux perf, Android SimplePerf, the
+ Chrome performance panel, Android Studio, or
+ any file using the dhat format. Learn how to write your
+ own importer.
## IdleSearchField
## The component that is used for all the search inputs in the application.
@@ -704,8 +716,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = operations since the pr
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -721,37 +737,43 @@ TrackPower--tooltip-power-milliwatt = { $value } mW
# watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-range-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
.label = Energy used in the visible range
# This is used in the tooltip when the energy used in the current range uses the
# milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
.label = Energy used in the visible range
# This is used in the tooltip when the energy used in the current range uses the
# microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
.label = Energy used in the visible range
# This is used in the tooltip when the energy used in the current preview
# selection uses the watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-preview-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
.label = Energy used in the current selection
# This is used in the tooltip when the energy used in the current preview
# selection uses the milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
.label = Energy used in the current selection
# This is used in the tooltip when the energy used in the current preview
# selection uses the microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
.label = Energy used in the current selection
## TrackSearchField
diff --git a/locales/en-US/app.ftl b/locales/en-US/app.ftl
index 833b51b6a4..0355ed418f 100644
--- a/locales/en-US/app.ftl
+++ b/locales/en-US/app.ftl
@@ -12,6 +12,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox for Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -257,6 +258,11 @@ Home--menu-button-instructions =
Enable the profiler menu button to start recording a performance
profile in { -firefox-brand-name }, then analyze it and share it with profiler.firefox.com.
+Home--profile-firefox-android-instructions =
+ You can also profile { -firefox-android-brand-name }. For more
+ information, please consult this documentation:
+ Profiling { -firefox-android-brand-name } directly on device.
+
# The word WebChannel should not be translated.
# This message can be seen on https://main--perf-html.netlify.app/ in the tooltip
# of the "Enable Firefox Profiler menu button" button.
@@ -277,7 +283,6 @@ Home--record-instructions =
keyboard shortcuts. The icon is blue when a profile is recording.
Hit Capture to load the data into profiler.firefox.com.
-Home--instructions-title = How to view and record profiles
Home--instructions-content =
Recording performance profiles requires { -firefox-brand-name }.
However, existing profiles can be viewed in any modern browser.
@@ -290,6 +295,15 @@ Home--additional-content-content = You can drag and drop a prof
Home--compare-recordings-info = You can also compare recordings. Open the comparing interface.
Home--your-recent-uploaded-recordings-title = Your recent uploaded recordings
+# We replace the elements such as and with links to the
+# documentation to use these tools.
+Home--load-files-from-other-tools =
+ The { -profiler-brand-name } can also import profiles from other profilers, such as
+ Linux perf, Android SimplePerf, the
+ Chrome performance panel, Android Studio, or
+ any file using the dhat format. Learn how to write your
+ own importer.
+
## IdleSearchField
## The component that is used for all the search inputs in the application.
@@ -757,8 +771,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = operations since the pr
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -776,42 +794,48 @@ TrackPower--tooltip-power-milliwatt = { $value } mW
# watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-range-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
.label = Energy used in the visible range
# This is used in the tooltip when the energy used in the current range uses the
# milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
.label = Energy used in the visible range
# This is used in the tooltip when the energy used in the current range uses the
# microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
.label = Energy used in the visible range
# This is used in the tooltip when the energy used in the current preview
# selection uses the watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-preview-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
.label = Energy used in the current selection
# This is used in the tooltip when the energy used in the current preview
# selection uses the milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
.label = Energy used in the current selection
# This is used in the tooltip when the energy used in the current preview
# selection uses the microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
.label = Energy used in the current selection
## TrackSearchField
diff --git a/locales/es-CL/app.ftl b/locales/es-CL/app.ftl
index b873973e2a..0b96825e9c 100644
--- a/locales/es-CL/app.ftl
+++ b/locales/es-CL/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox para Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -196,6 +197,10 @@ Home--load-from-url-submit-button =
Home--documentation-button = Documentación
Home--menu-button = Activar botón de menú de { -profiler-brand-name }
Home--menu-button-instructions = Habilita el botón de menú del perfilador para comenzar a registrar un perfil de rendimiento en { -firefox-brand-name }, luego analízalo y compártelo con profiler.firefox.com.
+Home--profile-firefox-android-instructions =
+ También puede perfilar { -firefox-android-brand-name }. Para más
+ información, consulta esta documentación:
+ Perfilando { -firefox-android-brand-name } directamente en el dispositivo.
# The word WebChannel should not be translated.
# This message can be seen on https://main--perf-html.netlify.app/ in the tooltip
# of the "Enable Firefox Profiler menu button" button.
@@ -205,7 +210,6 @@ Home--enable-button-unavailable =
# This message can be seen on https://main--perf-html.netlify.app/ .
Home--web-channel-unavailable = Esta instancia del perfilador no pudo conectarse a WebChannel. Esto usualmente significa que está ejecutándose en un servidor diferente del especificado en la preferencia devtools.performance.recording.ui-base-url. Si quieres capturar nuevos perfiles con esta instancia, y otorgarle control programático del botón del menú del perfilador, puedes ir a about:config y cambiar la preferencia.
Home--record-instructions = Para empezar a perfilar, haz clic en el botón de perfilado o utiliza los atajos del teclado. El icono se torna azul cuando se está grabando un perfil. Pulsa Capturar para cargar los datos en profiler.firefox.com.
-Home--instructions-title = Cómo ver y registrar perfiles
Home--instructions-content =
Registrar perfiles de rendimiento requiere de { -firefox-brand-name }.
Sin embargo, los perfiles existentes pueden ser vistos en cualquier navegador moderno.
@@ -216,6 +220,14 @@ Home--additional-content-title = Cargar perfiles existentes
Home--additional-content-content = Puedes arrastrar y soltar un archivo de perfil aquí para cargarlo, o:
Home--compare-recordings-info = También puedes comparar los registros. Abre la interfaz de comparación.
Home--your-recent-uploaded-recordings-title = Tus registros subidos recientemente
+# We replace the elements such as and with links to the
+# documentation to use these tools.
+Home--load-files-from-other-tools =
+ { -profiler-brand-name } también puede importar perfiles de otros generadores de perfiles, tales como
+ Linux perf, Android SimplePerf, el
+ panel de rendimiento de Chrome, Android Studio, o
+ cualquier archivo usando el formato dhat. Aprende a escribir tu
+ propio importador.
## IdleSearchField
## The component that is used for all the search inputs in the application.
@@ -641,8 +653,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = operaciones desde la mu
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -658,38 +674,44 @@ TrackPower--tooltip-power-milliwatt = { $value } mW
# watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-range-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
.label = Energía usada en el rango visible
# This is used in the tooltip when the energy used in the current range uses the
# milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
.label = Energía usada en el rango visible
# This is used in the tooltip when the energy used in the current range uses the
# microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh
- .label = Energía utilizada en el rango visible
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
+ .label = Energía usada en el rango visible
# This is used in the tooltip when the energy used in the current preview
# selection uses the watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-preview-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
.label = Energía usada en la selección actual
# This is used in the tooltip when the energy used in the current preview
# selection uses the milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
.label = Energía usada en la selección actual
# This is used in the tooltip when the energy used in the current preview
# selection uses the microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh
- .label = Energía utilizada en la selección actual
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
+ .label = Energía usada en la selección actual
## TrackSearchField
## The component that is used for the search input in the track context menu.
diff --git a/locales/fr/app.ftl b/locales/fr/app.ftl
index cdfc6087ed..8dad3a236d 100644
--- a/locales/fr/app.ftl
+++ b/locales/fr/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox pour Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -188,6 +189,10 @@ Home--load-from-url-submit-button =
Home--documentation-button = Documentation
Home--menu-button = Activer le bouton de menu { -profiler-brand-name }
Home--menu-button-instructions = Activez le bouton de menu du profileur pour commencer à enregistrer un profil des performances dans { -firefox-brand-name }, puis analysez-le et partagez-le avec profiler.firefox.com.
+Home--profile-firefox-android-instructions =
+ Vous pouvez également profiler { -firefox-android-brand-name }.
+ Pour plus d’informations, veuillez consulter cette documentation :
+ Profilage de { -firefox-android-brand-name } directement sur l’appareil.
# The word WebChannel should not be translated.
# This message can be seen on https://main--perf-html.netlify.app/ in the tooltip
# of the "Enable Firefox Profiler menu button" button.
@@ -197,7 +202,6 @@ Home--enable-button-unavailable =
# This message can be seen on https://main--perf-html.netlify.app/ .
Home--web-channel-unavailable = Cette instance du profileur n’a pas pu se connecter à WebChannel. Généralement, cela signifie qu’il s’exécute sur un hôte différent de celui spécifié dans la préférence devtools.performance.recording.ui-base-url. Si vous souhaitez capturer de nouveaux profils avec cette instance, et lui donner par programmation le contrôle du bouton de menu du profileur, vous pouvez ouvrir about:config et modifier la préférence.
Home--record-instructions = Pour démarrer le profilage, cliquez sur le bouton de profilage ou utilisez le raccourci clavier. L’icône est bleue lorsqu’un profil est en cours d’enregistrement. Appuyez sur Capturer pour charger les données dans profiler.firefox.com.
-Home--instructions-title = Comment afficher et enregistrer des profils
Home--instructions-content =
L’enregistrement de profils de performances nécessite { -firefox-brand-name }.
Cependant, les profils existants peuvent être consultés dans n’importe quel navigateur moderne.
@@ -208,6 +212,14 @@ Home--additional-content-title = Charger des profils existants
Home--additional-content-content = Vous pouvez glisser-déposer un fichier de profil ici pour le charger, ou :
Home--compare-recordings-info = Vous pouvez également comparer des enregistrements. Ouvrir l’interface de comparaison.
Home--your-recent-uploaded-recordings-title = Vos enregistrements récemment envoyés
+# We replace the elements such as and with links to the
+# documentation to use these tools.
+Home--load-files-from-other-tools =
+ Le { -profiler-brand-name } peut également importer des profils d’autres profileurs, dont
+ Linux perf, Android SimplePerf, le
+ Panneau de performances Chrome, Android Studio, ou
+ tout fichier utilisant le format dhat. Apprenez à écrire votre
+ propre importateur.
## IdleSearchField
## The component that is used for all the search inputs in the application.
@@ -633,8 +645,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = opérations depuis l’
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -650,38 +666,44 @@ TrackPower--tooltip-power-milliwatt = { $value } mW
# watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-range-watthour = { $value } Wh ({ $carbonValue } g eqCO₂)
.label = Énergie consommée dans l’intervalle visible
# This is used in the tooltip when the energy used in the current range uses the
# milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour = { $value } mWh ({ $carbonValue } mg eqCO₂)
.label = Énergie consommée dans l’intervalle visible
# This is used in the tooltip when the energy used in the current range uses the
# microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-microwatthour = { $value } µWh ({ $carbonValue } mg eqCO₂)
.label = Énergie consommée dans l’intervalle visible
# This is used in the tooltip when the energy used in the current preview
# selection uses the watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh
- .label = Énergie consommée dans la sélection actuelle
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-preview-watthour = { $value } Wh ({ $carbonValue } g eqCO₂)
+ .label = Énergie consommée dans la sélection courante
# This is used in the tooltip when the energy used in the current preview
# selection uses the milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh
- .label = Énergie consommée dans la sélection actuelle
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-milliwatthour = { $value } mWh ({ $carbonValue } mg eqCO₂)
+ .label = Énergie consommée dans la sélection courante
# This is used in the tooltip when the energy used in the current preview
# selection uses the microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh
- .label = Énergie consommée dans la sélection actuelle
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-microwatthour = { $value } µWh ({ $carbonValue } mg eqCO₂)
+ .label = Énergie consommée dans la sélection courante
## TrackSearchField
## The component that is used for the search input in the track context menu.
diff --git a/locales/fy-NL/app.ftl b/locales/fy-NL/app.ftl
index 05b3327de9..fb55cd6d9e 100644
--- a/locales/fy-NL/app.ftl
+++ b/locales/fy-NL/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox foar Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -243,6 +244,10 @@ Home--menu-button = Menuknop { -profiler-brand-name } ynskeakelje
Home--menu-button-instructions =
Skeakelje de menuknop Profiler yn om te begjinnen mei it opnimmen fan in
prestaasjeprofyl yn { -firefox-brand-name }, analysearje dit en diel it mei profiler.firefox.com.
+Home--profile-firefox-android-instructions =
+ Jo kinne { -firefox-android-brand-name } ek profilearje. Foar mear
+ ynformaasje, lês dizze dokumintaasje:
+ { -firefox-android-brand-name } daliks op apparaat profilearje.
# The word WebChannel should not be translated.
# This message can be seen on https://main--perf-html.netlify.app/ in the tooltip
# of the "Enable Firefox Profiler menu button" button.
@@ -260,7 +265,6 @@ Home--record-instructions =
Klik om te starten mei it meitsjen fan in profyl op de profylknop of brûk de
fluchtoetsen. It piktogram is blau as der in profyl opnommen wurdt.
Klik op Fêstlizze om de gegevens yn profiler.firefox.com te laden.
-Home--instructions-title = Profilen besjen en opnimme
Home--instructions-content =
It opnimmen fan prestaasjeprofilen fereasket { -firefox-brand-name }.
Besteande profilen kinne echter besjoen wurde yn elke moderne browser.
@@ -271,6 +275,14 @@ Home--additional-content-title = Besteande profilen lade
Home--additional-content-content = Jo kinne in profylbestân hjirhinne fersleepje om it te laden, of:
Home--compare-recordings-info = Jo kinne ek opnamen fergelykje. De fergelikingsinterface iepenje.
Home--your-recent-uploaded-recordings-title = Jo resint opladen opnamen
+# We replace the elements such as and with links to the
+# documentation to use these tools.
+Home--load-files-from-other-tools =
+ De { -profiler-brand-name } kin ek profilen fan oare profilers ymportearje, lykas
+ Linux-perf, Android SimplePerf, it
+ Chrome-prestaasjespaniel, Android Studio, of
+ elk bestân mei it dhat-formaat. Lês hoe’t jo jo
+ eigen ymportearder skriuwe.
## IdleSearchField
## The component that is used for all the search inputs in the application.
@@ -700,8 +712,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = bewurkingen sûnt de fo
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -717,38 +733,44 @@ TrackPower--tooltip-power-milliwatt = { $value } mW
# watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh
- .label = Ferbrûkte enerzjy yn it sichtbere berik
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-range-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
+ .label = Enerzjy brûkt yn it sichtbere berik
# This is used in the tooltip when the energy used in the current range uses the
# milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh
- .label = Ferbrûkte enerzjy yn it sichtbere berik
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
+ .label = Enerzjy brûkt yn it sichtbere berik
# This is used in the tooltip when the energy used in the current range uses the
# microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh
- .label = Ferbrûkte enerzjy yn it sichtbere berik
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
+ .label = Enerzjy brûkt yn it sichtbere berik
# This is used in the tooltip when the energy used in the current preview
# selection uses the watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh
- .label = Ferbrûkte enerzjy yn de aktuele seleksje
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-preview-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
+ .label = Enerzjy brûkt yn de aktuele seleksje
# This is used in the tooltip when the energy used in the current preview
# selection uses the milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh
- .label = Ferbrûkte enerzjy yn de aktuele seleksje
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
+ .label = Enerzjy brûkt yn de aktuele seleksje
# This is used in the tooltip when the energy used in the current preview
# selection uses the microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh
- .label = Ferbrûkte enerzjy yn de aktuele seleksje
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
+ .label = Enerzjy brûkt yn de aktuele seleksje
## TrackSearchField
## The component that is used for the search input in the track context menu.
diff --git a/locales/ia/app.ftl b/locales/ia/app.ftl
index af9d59b2f6..75a41448d5 100644
--- a/locales/ia/app.ftl
+++ b/locales/ia/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox pro Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -245,6 +246,10 @@ Home--menu-button = Activar le button { -profiler-brand-name } del menu
Home--menu-button-instructions =
Activa le button de menu profilator pro initiar registrar un profilo de
prestation in { -firefox-brand-name }, pois analysa lo e comparti lo con profiler.firefox.com.
+Home--profile-firefox-android-instructions =
+ Tu pote equalmente profilar { -firefox-android-brand-name }.
+ Pro saper plus, consulta iste documentation:
+ Profilage de { -firefox-android-brand-name } directemente sur le apparato.
# The word WebChannel should not be translated.
# This message can be seen on https://main--perf-html.netlify.app/ in the tooltip
# of the "Enable Firefox Profiler menu button" button.
@@ -257,7 +262,6 @@ Home--record-instructions =
Pro initiar profilar, clicca sur le button profila o usa le
vias breve de claviero. Le icone es blau quando un profilo es in registration.
Pulsa Capturar pro cargar le datos in profiler.firefox.com.
-Home--instructions-title = Como vider e registrar profilos
Home--instructions-content =
Registrar profilos de prestation require { -firefox-brand-name }.
Totevia, le profilos existente pote esser vidite in ulle moderne navigator.
@@ -268,6 +272,14 @@ Home--additional-content-title = Cargar profilos existente
Home--additional-content-content = Tu pote traher e deponer hic un file profilo pro cargar lo, o:
Home--compare-recordings-info = Tu pote alsi comparar registrationes. Aperir le interfacie de comparation.
Home--your-recent-uploaded-recordings-title = Tu registrationes incargate recentemente
+# We replace the elements such as and with links to the
+# documentation to use these tools.
+Home--load-files-from-other-tools =
+ { -profiler-brand-name } pote alsi importar profilos de altere profilatores, tal como
+ Linux perf, Android SimplePerf,
+ Chrome performance panel, Android Studio, o
+ ulle file que usa le formato dhat. Apprende a scriber tu
+ proprie importator.
## IdleSearchField
## The component that is used for all the search inputs in the application.
@@ -695,8 +707,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = operationes depost le p
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -712,37 +728,43 @@ TrackPower--tooltip-power-milliwatt = { $value } mW
# watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-range-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
.label = Energia usate in le campo visibile
# This is used in the tooltip when the energy used in the current range uses the
# milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
.label = Energia usate in le campo visibile
# This is used in the tooltip when the energy used in the current range uses the
# microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
.label = Energia usate in le campo visibile
# This is used in the tooltip when the energy used in the current preview
# selection uses the watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh
- .label = Energia usate in le campo visibile
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-preview-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
+ .label = Energia usate in le selection actual
# This is used in the tooltip when the energy used in the current preview
# selection uses the milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh
- .label = Energia usate in le campo visibile
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
+ .label = Energia usate in le selection actual
# This is used in the tooltip when the energy used in the current preview
# selection uses the microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
.label = Energia usate in le selection actual
## TrackSearchField
diff --git a/locales/it/app.ftl b/locales/it/app.ftl
index 052137329e..052287be0e 100644
--- a/locales/it/app.ftl
+++ b/locales/it/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox per Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -184,6 +185,7 @@ Home--load-from-url-submit-button =
Home--documentation-button = Documentazione
Home--menu-button = Attiva il pulsante { -profiler-brand-name } nel menu
Home--menu-button-instructions = Attiva il pulsante Profiler nel menu per avviare la registrazione di un profilo delle prestazioni di { -firefox-brand-name }, poi analizzalo e condividilo su profiler.firefox.com.
+Home--profile-firefox-android-instructions = È anche possibile creare profili per { -firefox-android-brand-name }. Per ulteriori informazioni, consultare la documentazione Creare un profilo di { -firefox-android-brand-name } direttamente sul dispositivo.
# The word WebChannel should not be translated.
# This message can be seen on https://main--perf-html.netlify.app/ in the tooltip
# of the "Enable Firefox Profiler menu button" button.
@@ -193,7 +195,6 @@ Home--enable-button-unavailable =
# This message can be seen on https://main--perf-html.netlify.app/ .
Home--web-channel-unavailable = Questa istanza del profiler non è stata in grado di connettersi al WebChannel. Normalmente significa che è in esecuzione su un host diverso da quello specificato nell’impostazione devtools.performance.recording.ui-base-url. Se vuoi catturare nuovi profili con questa istanza e assegnarle il controllo programmatico del pulsante del menu del profiler, apri about:config e modifica questa impostazione.
Home--record-instructions = Per avviare la profilazione, fai clic sul pulsante per avviare la registrazione oppure utilizza le scorciatoie da tastiera. L’icona diventa blu quando è attiva la registrazione di un profilo. Premi Cattura per caricare i dati su profiler.firefox.com.
-Home--instructions-title = Come visualizzare e registrare profili
Home--instructions-content = La registrazione dei profili è possibile solo con { -firefox-brand-name }. I profili esistenti possono essere visualizzati con qualsiasi browser.
Home--record-instructions-start-stop = Interrompi e avvia la profilatura
Home--record-instructions-capture-load = Cattura e carica profilo
@@ -202,6 +203,11 @@ Home--additional-content-title = Carica profili esistenti
Home--additional-content-content = È possibile trascinare e rilasciare qui un profilo per caricarlo, oppure:
Home--compare-recordings-info = È anche possibile confrontare diverse registrazioni. Apri l’interfaccia per il confronto.
Home--your-recent-uploaded-recordings-title = Le tue registrazioni caricate di recente
+# We replace the elements such as and with links to the
+# documentation to use these tools.
+Home--load-files-from-other-tools =
+ { -profiler-brand-name } può anche importare profili da altri profiler, come Linux perf, Android SimplePerf, il
+ pannello prestazioni di Chrome, Android Studio o qualsiasi file che utilizzi il formato dhat. Scopri come creare uno strumento di importazione.
## IdleSearchField
## The component that is used for all the search inputs in the application.
@@ -631,8 +637,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = operazioni dal campione
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -648,37 +658,43 @@ TrackPower--tooltip-power-milliwatt = { $value } mW
# watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh
- .label = Energia utilizzata nell’intervallo visualizzato
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-range-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
+ .label = Energia utilizzata nell’intervallo visibile
# This is used in the tooltip when the energy used in the current range uses the
# milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh
- .label = Energia utilizzata nell’intervallo visualizzato
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
+ .label = Energia utilizzata nell’intervallo visibile
# This is used in the tooltip when the energy used in the current range uses the
# microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh
- .label = Energia utilizzata nell’intervallo visualizzato
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
+ .label = Energia utilizzata nell’intervallo visibile
# This is used in the tooltip when the energy used in the current preview
# selection uses the watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-preview-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
.label = Energia utilizzata nella selezione corrente
# This is used in the tooltip when the energy used in the current preview
# selection uses the milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
.label = Energia utilizzata nella selezione corrente
# This is used in the tooltip when the energy used in the current preview
# selection uses the microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
.label = Energia utilizzata nella selezione corrente
## TrackSearchField
diff --git a/locales/kab/app.ftl b/locales/kab/app.ftl
index e33701e6aa..0fc58bc2b5 100644
--- a/locales/kab/app.ftl
+++ b/locales/kab/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox i Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -83,6 +84,8 @@ CallTreeSidebar--select-a-node = Fren takerrist i uskan n talɣut fell-as.
CompareHome--instruction-title = Sekcem URLs n umaɣnu i tebɣiḍ ad tsenmehleḍ
CompareHome--form-label-profile1 = Amaɣnu 1:
CompareHome--form-label-profile2 = Amaɣnu 2:
+CompareHome--submit-button =
+ .value = Err-d imaɣunen
## DebugWarning
## This is displayed at the top of the analysis page when the loaded profile is
@@ -129,13 +132,13 @@ Home--load-from-url-submit-button =
.value = Sali
Home--documentation-button = Tasemlit
Home--menu-button = Rmed taqeffalt n wumuɣ { -profiler-brand-name }
-Home--instructions-title = Amek ara twaliḍ akked ad teskelseḍ imuɣna
Home--record-instructions-start-stop = Seḥbes neɣ bdu timeɣna
Home--record-instructions-capture-load = Ṭṭef neɣ sali amaɣnu
Home--profiler-motto = Ṭṭef amaɣnu n temlellit. Sleḍ-it. Bḍu-t. Err web d arurad.
Home--additional-content-title = Sali imuɣna yellan
Home--additional-content-content = Tzemreḍ ad tzuɣreḍ syen sers afaylu n umaɣnu da i usali-ines, neɣ:
Home--compare-recordings-info = Tzemreḍ daɣen ad tsenmehleḍ iseklasen. Ldi agrudem n usnemhel.
+Home--your-recent-uploaded-recordings-title = Iseklasen-ik·im i d-yulin melmi kan
## IdleSearchField
## The component that is used for all the search inputs in the application.
@@ -226,7 +229,8 @@ MenuButtons--index--profile-info-uploaded-label = Yuli-d:
MenuButtons--index--profile-info-uploaded-actions = Kkes
MenuButtons--index--metaInfo-subtitle = Talɣut n umaɣnu
MenuButtons--metaInfo--symbols = Izamulen:
-MenuButtons--metaInfo--cpu = CPU:
+MenuButtons--index--show-moreInfo-button = Sken ugar
+MenuButtons--index--hide-moreInfo-button = Sken drus
MenuButtons--metaInfo--main-process-started = Asesfer agejdan yebda:
MenuButtons--metaInfo--main-process-ended = Asesfer agejdan yekfa:
MenuButtons--metaInfo--interval = Azilal:
@@ -429,7 +433,7 @@ TrackNameButton--hide-process =
TrackMemoryGraph--relative-memory-at-this-time = takatut tamassaɣt deg wakud-a
-## TrackPowerGraph
+## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
## It's not displayed by default in the UI, but an example can be found at
@@ -480,11 +484,6 @@ TransformNavigator--merge-function = Smezdi: { $item }
# Variables:
# $item (String) - Name of the function that transform applied to.
TransformNavigator--drop-function = Sers: { $item }
-# "Collapse direct recursion" transform.
-# See: https://profiler.firefox.com/docs/#/./guide-filtering-call-trees?id=collapse
-# Variables:
-# $item (String) - Name of the function that transform applied to.
-TransformNavigator--collapse-direct-recursion = Fneẓ asniles: { $item }
## Source code view in a box at the bottom of the UI.
diff --git a/locales/nl/app.ftl b/locales/nl/app.ftl
index 1257cdd784..252490ef84 100644
--- a/locales/nl/app.ftl
+++ b/locales/nl/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox voor Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -243,6 +244,10 @@ Home--menu-button = Menuknop { -profiler-brand-name } inschakelen
Home--menu-button-instructions =
Schakel de menuknop Profiler in om te beginnen met het opnemen van een
prestatieprofiel in { -firefox-brand-name }, analyseer dit en deel het met profiler.firefox.com.
+Home--profile-firefox-android-instructions =
+ U kunt ook { -firefox-android-brand-name } profileren. Voor meer
+ informatie kunt u deze documentatie raadplegen:
+ { -firefox-android-brand-name } rechtstreeks op apparaat profileren.
# The word WebChannel should not be translated.
# This message can be seen on https://main--perf-html.netlify.app/ in the tooltip
# of the "Enable Firefox Profiler menu button" button.
@@ -260,7 +265,6 @@ Home--record-instructions =
Klik om te starten met het maken van een profiel op de profielknop of gebruik de
sneltoetsen. Het pictogram is blauw als er een profiel wordt opgenomen.
Klik op Vastleggen om de gegevens in profiler.firefox.com te laden.
-Home--instructions-title = Profielen bekijken en opnemen
Home--instructions-content =
Het opnemen van prestatieprofielen vereist { -firefox-brand-name }.
Bestaande profielen kunnen echter bekeken worden in elke moderne browser.
@@ -271,6 +275,14 @@ Home--additional-content-title = Bestaande profielen laden
Home--additional-content-content = U kunt een profielbestand hierheen verslepen om het te laden, of:
Home--compare-recordings-info = U kunt ook opnamen vergelijken. De vergelijkingsinterface openen.
Home--your-recent-uploaded-recordings-title = Uw onlangs geüploade opnamen
+# We replace the elements such as and with links to the
+# documentation to use these tools.
+Home--load-files-from-other-tools =
+ De { -profiler-brand-name } kan ook profielen van andere profilers importeren, zoals
+ Linux perf, Android SimplePerf, het
+ Chrome-prestatiepaneel, Android Studio of
+ elk bestand dat de dhat-indeling gebruikt. Ontdek hoe u uw
+ eigen importroutine schrijft.
## IdleSearchField
## The component that is used for all the search inputs in the application.
@@ -700,8 +712,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = bewerkingen sinds de vo
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -717,38 +733,44 @@ TrackPower--tooltip-power-milliwatt = { $value } mW
# watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh
- .label = Verbruikte energie in het zichtbare bereik
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-range-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
+ .label = Energie gebruikt in het zichtbare bereik
# This is used in the tooltip when the energy used in the current range uses the
# milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh
- .label = Verbruikte energie in het zichtbare bereik
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
+ .label = Energie gebruikt in het zichtbare bereik
# This is used in the tooltip when the energy used in the current range uses the
# microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh
- .label = Verbruikte energie in het zichtbare bereik
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
+ .label = Energie gebruikt in het zichtbare bereik
# This is used in the tooltip when the energy used in the current preview
# selection uses the watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh
- .label = Verbruikte energie in de huidige selectie
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-preview-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
+ .label = Energie gebruikt in de huidige selectie
# This is used in the tooltip when the energy used in the current preview
# selection uses the milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh
- .label = Verbruikte energie in de huidige selectie
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
+ .label = Energie gebruikt in de huidige selectie
# This is used in the tooltip when the energy used in the current preview
# selection uses the microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh
- .label = Verbruikte energie in de huidige selectie
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
+ .label = Energie gebruikt in de huidige selectie
## TrackSearchField
## The component that is used for the search input in the track context menu.
diff --git a/locales/pt-BR/app.ftl b/locales/pt-BR/app.ftl
index 61d60a9e3f..b38d9c54c2 100644
--- a/locales/pt-BR/app.ftl
+++ b/locales/pt-BR/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox para Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -188,6 +189,10 @@ Home--menu-button = Ativar botão de menu do { -profiler-brand-name }
Home--menu-button-instructions =
Ative o botão de menu do profiler para iniciar a gravação de um profile de desempenho
no { -firefox-brand-name }, depois analisar e compartilhar com profiler.firefox.com.
+Home--profile-firefox-android-instructions =
+ Você também pode criar profile pelo { -firefox-android-brand-name }.
+ Para mais informações, consulte esta documentação:
+ Como criar profile do { -firefox-android-brand-name } diretamente no dispositivo.
# The word WebChannel should not be translated.
# This message can be seen on https://main--perf-html.netlify.app/ in the tooltip
# of the "Enable Firefox Profiler menu button" button.
@@ -197,7 +202,6 @@ Home--enable-button-unavailable =
# This message can be seen on https://main--perf-html.netlify.app/ .
Home--web-channel-unavailable = Esta instância do profiler não conseguiu se conectar ao WebChannel. Isso geralmente significa que está sendo executado em um host diferente daquele especificado na preferência devtools.performance.recording.ui-base-url. Se você quiser capturar novos profiles com esta instância e dar a ela controle programático do botão de menu do profiler, pode ir em about: config e alterar a preferência.
Home--record-instructions = Para iniciar a gravação de um profile, clique no botão de gravação de profile ou use os atalhos de teclado. O ícone fica azul quando um profile está sendo gravado. Use Capturar para carregar os dados no profiler.firefox.com.
-Home--instructions-title = Como ver e gravar profiles
Home--instructions-content =
A gravação de profiles de desempenho requer o { -firefox-brand-name }.
No entanto, profiles existentes podem ser vistos em qualquer navegador moderno.
@@ -208,6 +212,9 @@ Home--additional-content-title = Carregar profiles existentes
Home--additional-content-content = Você pode arrastar e soltar aqui um arquivo de profile para carregar, ou:
Home--compare-recordings-info = Você também pode comparar gravações. Abra a interface de comparação.
Home--your-recent-uploaded-recordings-title = Suas gravações enviadas recentemente
+# We replace the elements such as and with links to the
+# documentation to use these tools.
+Home--load-files-from-other-tools = O { -profiler-brand-name } também pode importar profiles de outros criadores de profile, como o Linux perf, o Android SimplePerf, o painel de desempenho do Chrome, o Android Studio ou qualquer arquivo no formato dhat. Saiba como escrever seu próprio importador.
## IdleSearchField
## The component that is used for all the search inputs in the application.
@@ -637,8 +644,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = operações desde a amo
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -654,37 +665,43 @@ TrackPower--tooltip-power-milliwatt = { $value } mW
# watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh
- .label = Energia usada na escala visível
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-range-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
+ .label = Energia usada no intervalo visível
# This is used in the tooltip when the energy used in the current range uses the
# milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh
- .label = Energia usada na escala visível
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
+ .label = Energia usada no intervalo visível
# This is used in the tooltip when the energy used in the current range uses the
# microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh
- .label = Energia usada na escala visível
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
+ .label = Energia usada no intervalo visível
# This is used in the tooltip when the energy used in the current preview
# selection uses the watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-preview-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
.label = Energia usada na seleção atual
# This is used in the tooltip when the energy used in the current preview
# selection uses the milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
.label = Energia usada na seleção atual
# This is used in the tooltip when the energy used in the current preview
# selection uses the microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
.label = Energia usada na seleção atual
## TrackSearchField
diff --git a/locales/sv-SE/app.ftl b/locales/sv-SE/app.ftl
index f059ddb6f6..51ac118bae 100644
--- a/locales/sv-SE/app.ftl
+++ b/locales/sv-SE/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox för Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -242,6 +243,10 @@ Home--menu-button = Aktivera { -profiler-brand-name } menyknapp
Home--menu-button-instructions =
Aktivera profil-menyknappen för att börja spela in en prestandaprofil
i { -firefox-brand-name }, analysera den och dela den med profiler.firefox.com.
+Home--profile-firefox-android-instructions =
+ Du kan också profilera { -firefox-android-brand-name }. För mer
+ information, se denna dokumentation:
+ Profilering av { -firefox-android-brand-name } direkt på enheten.
# The word WebChannel should not be translated.
# This message can be seen on https://main--perf-html.netlify.app/ in the tooltip
# of the "Enable Firefox Profiler menu button" button.
@@ -259,7 +264,6 @@ Home--record-instructions =
För att starta profilering, klicka på profileringsknappen eller använd
kortkommandona. Ikonen är blå när en profil spelas in. Tryck på
Fånga för att ladda data till profiler.firefox.com.
-Home--instructions-title = Hur man visar och spelar in profiler
Home--instructions-content =
För att spela in prestandaprofiler krävs { -firefox-brand-name }.
Befintliga profiler kan dock visas i vilken modern webbläsare som helst.
@@ -270,6 +274,14 @@ Home--additional-content-title = Ladda befintliga profiler
Home--additional-content-content = Du kan dra och släppa en profilfil här för att ladda den, eller:
Home--compare-recordings-info = Du kan också jämföra inspelningar.Öppna gränssnitt för att jämföra.
Home--your-recent-uploaded-recordings-title = Dina senaste uppladdade inspelningar
+# We replace the elements such as and with links to the
+# documentation to use these tools.
+Home--load-files-from-other-tools =
+ { -profiler-brand-name } kan också importera profiler från andra profilerare, t.ex
+ Linux perf, Android SimplePerf,
+ Chrome prestandapanel, Android Studio eller
+ vilken fil som helst som använder dhat-formatet. Lär dig hur du skriver din
+ egen importör.
## IdleSearchField
## The component that is used for all the search inputs in the application.
@@ -699,8 +711,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = operationer sedan före
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -716,37 +732,43 @@ TrackPower--tooltip-power-milliwatt = { $value } mW
# watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-range-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
.label = Energi som används i det synliga området
# This is used in the tooltip when the energy used in the current range uses the
# milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
.label = Energi som används i det synliga området
# This is used in the tooltip when the energy used in the current range uses the
# microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
.label = Energi som används i det synliga området
# This is used in the tooltip when the energy used in the current preview
# selection uses the watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-preview-watthour = { $value } Wh ({ $carbonValue } g CO₂e)
.label = Energi som används i det aktuella urvalet
# This is used in the tooltip when the energy used in the current preview
# selection uses the milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-milliwatthour = { $value } mWh ({ $carbonValue } mg CO₂e)
.label = Energi som används i det aktuella urvalet
# This is used in the tooltip when the energy used in the current preview
# selection uses the microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-microwatthour = { $value } µWh ({ $carbonValue } mg CO₂e)
.label = Energi som används i det aktuella urvalet
## TrackSearchField
diff --git a/locales/uk/app.ftl b/locales/uk/app.ftl
index 8d0082293c..3bcc12735e 100644
--- a/locales/uk/app.ftl
+++ b/locales/uk/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox для Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -239,6 +240,10 @@ Home--menu-button = Увімкнути кнопку меню { -profiler-brand-n
Home--menu-button-instructions =
Увімкніть кнопку меню профайлера, щоб почати запис швидкодії профілю у
{ -firefox-brand-name }, потім аналізуйте його та оприлюдніть на profiler.firefox.com.
+Home--profile-firefox-android-instructions =
+ Ви також можете створити профіль { -firefox-android-brand-name }. За
+ подробицями зверніться до цієї документації:
+ Профілювання { -firefox-android-brand-name } безпосередньо на пристрої.
# The word WebChannel should not be translated.
# This message can be seen on https://main--perf-html.netlify.app/ in the tooltip
# of the "Enable Firefox Profiler menu button" button.
@@ -256,7 +261,6 @@ Home--record-instructions =
Щоб розпочати запис профілю, натисніть кнопку запису або скористайтеся
комбінацією клавіш. Під час запису профілю піктограма стає синього кольору.
Натисніть Захопити, щоб завантажити дані на profiler.firefox.com.
-Home--instructions-title = Як переглядати та записувати профілі
Home--instructions-content =
Для запису профілів швидкодії потрібен { -firefox-brand-name }.
Однак, наявні профілі можна переглядати в будь-якому сучасному браузері.
@@ -267,6 +271,14 @@ Home--additional-content-title = Завантажити наявні профі
Home--additional-content-content = Ви можете перетягнути файл профілю сюди, щоб завантажити його, або:
Home--compare-recordings-info = Ви також можете порівняти записи. Відкрити інтерфейс порівняння.
Home--your-recent-uploaded-recordings-title = Ваші недавно вивантажені записи
+# We replace the elements such as and with links to the
+# documentation to use these tools.
+Home--load-files-from-other-tools =
+ { -profiler-brand-name } також може імпортувати профілі з інших профайлерів, наприклад
+ Linux perf, Android SimplePerf,
+ Панель швидкодії Chrome, Android Studio або
+ будь-який файл у форматі dhat. Навчіться записувати свій
+ власний імпортер.
## IdleSearchField
## The component that is used for all the search inputs in the application.
@@ -702,8 +714,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = операції, по
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -719,38 +735,44 @@ TrackPower--tooltip-power-milliwatt = { $value } мВт
# watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Вт·год
- .label = Використовувана у видимому діапазоні енергія
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-range-watthour = { $value } Вт·год ({ $carbonValue } г CO₂e)
+ .label = Спожита у видимому діапазоні енергія
# This is used in the tooltip when the energy used in the current range uses the
# milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } мВт·год
- .label = Використовувана у видимому діапазоні енергія
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour = { $value } мВт·год ({ $carbonValue } мг CO₂е)
+ .label = Спожита у видимому діапазоні енергія
# This is used in the tooltip when the energy used in the current range uses the
# microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µВт·год
- .label = Використовувана енергія у видимому діапазоні
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-microwatthour = { $value } мкВт·год ({ $carbonValue } мг CO₂e)
+ .label = Спожита у видимому діапазоні енергія
# This is used in the tooltip when the energy used in the current preview
# selection uses the watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Вт·год
- .label = Використовувана в поточному виборі енергія
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-preview-watthour = { $value } Вт·год ({ $carbonValue } г CO₂e)
+ .label = Спожита у поточній вибірці енергія
# This is used in the tooltip when the energy used in the current preview
# selection uses the milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } Вт·год
- .label = Використовувана в поточному виборі енергія
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-milliwatthour = { $value } мВт·год ({ $carbonValue } мг CO₂е)
+ .label = Спожита у поточній вибірці енергія
# This is used in the tooltip when the energy used in the current preview
# selection uses the microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µВт·год
- .label = Використана в поточній вибірці енергія
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-microwatthour = { $value } мкВт·год ({ $carbonValue } мг CO₂e)
+ .label = Спожита у поточній вибірці енергія
## TrackSearchField
## The component that is used for the search input in the track context menu.
diff --git a/locales/zh-CN/app.ftl b/locales/zh-CN/app.ftl
index b9eba98c38..f448c1d117 100644
--- a/locales/zh-CN/app.ftl
+++ b/locales/zh-CN/app.ftl
@@ -38,8 +38,8 @@ AppViewRouter--error-compare = 无法获取分析记录。
# This error message is displayed when a Safari-specific error state is encountered.
# Importing profiles from URLs such as http://127.0.0.1:someport/ is not possible in Safari.
# https://profiler.firefox.com/from-url/http%3A%2F%2F127.0.0.1%3A3000%2Fprofile.json/
-AppViewRouter--error-from-localhost-url-safari = 由于 Safari 的特殊限制,{ -profiler-brand-name } 无法使用此浏览器从本地导入分析记录。请在 { -firefox-brand-name } 或 Chrome 中打开此页面。
- .title = Safari 无法导入本地性能分析记录
+AppViewRouter--error-from-localhost-url-safari = 由于 Safari 浏览器的特殊限制,{ -profiler-brand-name } 无法使用此浏览器从本地导入分析记录。请在 { -firefox-brand-name } 或 Chrome 中打开此页面。
+ .title = Safari 浏览器无法导入本地性能分析记录
AppViewRouter--route-not-found--home =
.specialMessage = 无法识别您尝试访问的 URL。
@@ -184,7 +184,6 @@ Home--enable-button-unavailable =
# This message can be seen on https://main--perf-html.netlify.app/ .
Home--web-channel-unavailable = 此分析器无法连接至 WebChannel。通常是因为运行分析器的主机与 devtools.performance.recording.ui-base-url 首选项中指定的主机不同。若您想要使用此分析器捕捉新的性能分析记录,并可程序化控制分析器菜单按钮,可到 about:config 调整该首选项。
Home--record-instructions = 要进行分析,请点击“分析”按钮,或使用键盘快捷键。在性能记录时,此图标将会变为蓝色。按下捕捉即可将数据加载至 profiler.firefox.com。
-Home--instructions-title = 如何查看并记录分析结果
Home--instructions-content = 需使用 { -firefox-brand-name } 记录性能分析信息。但可以使用任何现代浏览器查看现有分析记录。
Home--record-instructions-start-stop = 停止并开始分析
Home--record-instructions-capture-load = 捕捉并加载分析记录
@@ -615,8 +614,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = 自前一次采样以
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -628,42 +631,6 @@ TrackPower--tooltip-power-watt = { $value } W
# $value (String) - the power value at this location
TrackPower--tooltip-power-milliwatt = { $value } mW
.label = 功率
-# This is used in the tooltip when the energy used in the current range uses the
-# watt-hour unit.
-# Variables:
-# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh
- .label = 可见范围内的功耗
-# This is used in the tooltip when the energy used in the current range uses the
-# milliwatt-hour unit.
-# Variables:
-# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh
- .label = 可见范围内的功耗
-# This is used in the tooltip when the energy used in the current range uses the
-# microwatt-hour unit.
-# Variables:
-# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh
- .label = 可见范围内的功耗
-# This is used in the tooltip when the energy used in the current preview
-# selection uses the watt-hour unit.
-# Variables:
-# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh
- .label = 当前选择范围内的功耗
-# This is used in the tooltip when the energy used in the current preview
-# selection uses the milliwatt-hour unit.
-# Variables:
-# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh
- .label = 当前选择范围内的功耗
-# This is used in the tooltip when the energy used in the current preview
-# selection uses the microwatt-hour unit.
-# Variables:
-# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh
- .label = 当前选择范围内的功耗
## TrackSearchField
## The component that is used for the search input in the track context menu.
diff --git a/locales/zh-TW/app.ftl b/locales/zh-TW/app.ftl
index edf537c239..0f9fe12cc1 100644
--- a/locales/zh-TW/app.ftl
+++ b/locales/zh-TW/app.ftl
@@ -15,6 +15,7 @@
## The following feature names must be treated as a brand. They cannot be translated.
-firefox-brand-name = Firefox
+-firefox-android-brand-name = Firefox for Android
-profiler-brand-name = Firefox Profiler
-profiler-brand-short-name = Profiler
-firefox-nightly-brand-name = Firefox Nightly
@@ -184,6 +185,7 @@ Home--load-from-url-submit-button =
Home--documentation-button = 文件
Home--menu-button = 開啟 { -profiler-brand-name } 選單按鈕
Home--menu-button-instructions = 開啟 { -firefox-brand-name } 當中的檢測器選單按鈕開始紀錄效能,然後進行分析並分享到 profiler.firefox.com。
+Home--profile-firefox-android-instructions = 您也可以對 { -firefox-android-brand-name } 進行效能檢測。若需更多資訊請參考下列文件:直接於裝置上檢測 { -firefox-android-brand-name } 效能。
# The word WebChannel should not be translated.
# This message can be seen on https://main--perf-html.netlify.app/ in the tooltip
# of the "Enable Firefox Profiler menu button" button.
@@ -193,7 +195,6 @@ Home--enable-button-unavailable =
# This message can be seen on https://main--perf-html.netlify.app/ .
Home--web-channel-unavailable = 此檢測器無法連線到 WebChannel。通常是因為執行檢測器的主機與 devtools.performance.recording.ui-base-url 偏好設定當中指定的主機不同。若您想要使用此檢測器捕捉新的效能檢測檔,並可程式化控制檢測器選單按鈕,可到 about:config 調整該偏好設定。
Home--record-instructions = 請點擊檢測按鈕或按下鍵盤快速鍵即可開始進行檢測。進行效能紀錄時,此圖示將會顯示成藍色。按下捕捉即可將資料載入到 profiler.firefox.com。
-Home--instructions-title = 如何檢視並記錄檢測檔
Home--instructions-content = 需要使用 { -firefox-brand-name } 紀錄效能檢測檔。但可以使用任何現代瀏覽器檢視現有的檢測檔。
Home--record-instructions-start-stop = 停止並開始檢測
Home--record-instructions-capture-load = 捕捉並載入檢測檔
@@ -202,6 +203,9 @@ Home--additional-content-title = 載入現有檢測檔
Home--additional-content-content = 您可以將效能檢測檔拖曳到此處,或:
Home--compare-recordings-info = 您也可以比較紀錄內容。開啟比較介面。
Home--your-recent-uploaded-recordings-title = 您近期上傳的紀錄
+# We replace the elements such as and with links to the
+# documentation to use these tools.
+Home--load-files-from-other-tools = { -profiler-brand-name } 也可以匯入其他效能檢測器,例如 Linux perf、Android SimplePerf、Chrome 效能面板、Android Studio 所產生的效能檢測檔,或任何使用 dhat 格式儲存的效能檢測檔。點擊此處了解如何撰寫您自己的匯入程式。
## IdleSearchField
## The component that is used for all the search inputs in the application.
@@ -625,8 +629,12 @@ TrackMemoryGraph--operations-since-the-previous-sample = 自前一次取樣以
## TrackPower
## This is used to show the power used by the CPU and other chips in a computer,
## graphed over time.
-## It's not displayed by default in the UI, but an example can be found at
+## It's not always displayed in the UI, but an example can be found at
## https://share.firefox.dev/3a1fiT7.
+## For the strings in this group, the carbon dioxide equivalent is computed from
+## the used energy, using the carbon dioxide equivalent for electricity
+## consumption. The carbon dioxide equivalent represents the equivalent amount
+## of CO₂ to achieve the same level of global warming potential.
# This is used in the tooltip when the power value uses the watt unit.
# Variables:
@@ -642,38 +650,44 @@ TrackPower--tooltip-power-milliwatt = { $value } mW
# watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh
- .label = 可見範圍內消耗的能源
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-range-watthour = { $value } Wh({ $carbonValue } g CO₂e)
+ .label = 可見範圍中消耗的能源
# This is used in the tooltip when the energy used in the current range uses the
# milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh
- .label = 可見範圍內消耗的能源
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour = { $value } mWh({ $carbonValue } mg CO₂e)
+ .label = 可見範圍中消耗的能源
# This is used in the tooltip when the energy used in the current range uses the
# microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh
- .label = 目前選擇範圍內消耗的能源
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-range-microwatthour = { $value } µWh({ $carbonValue } mg CO₂e)
+ .label = 可見範圍中消耗的能源
# This is used in the tooltip when the energy used in the current preview
# selection uses the watt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh
- .label = 目前選擇範圍內消耗的能源
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (grams)
+TrackPower--tooltip-energy-carbon-used-in-preview-watthour = { $value } Wh({ $carbonValue } g CO₂e)
+ .label = 目前選擇範圍中消耗的能源
# This is used in the tooltip when the energy used in the current preview
# selection uses the milliwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh
- .label = 目前選擇範圍內消耗的能源
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-milliwatthour = { $value } mWh({ $carbonValue } mg CO₂e)
+ .label = 目前選擇範圍中消耗的能源
# This is used in the tooltip when the energy used in the current preview
# selection uses the microwatt-hour unit.
# Variables:
# $value (String) - the energy value for this range
-TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh
- .label = 目前選擇範圍內消耗的能源
+# $carbonValue (string) - the carbon dioxide equivalent (CO₂e) value (milligrams)
+TrackPower--tooltip-energy-carbon-used-in-preview-microwatthour = { $value } µWh({ $carbonValue } mg CO₂e)
+ .label = 目前選擇範圍中消耗的能源
## TrackSearchField
## The component that is used for the search input in the track context menu.
diff --git a/package.json b/package.json
index 800feffa99..8a1b8abc96 100644
--- a/package.json
+++ b/package.json
@@ -49,23 +49,24 @@
},
"dependencies": {
"@codemirror/lang-cpp": "^6.0.2",
- "@codemirror/lang-javascript": "^6.1.1",
+ "@codemirror/lang-javascript": "^6.1.2",
"@codemirror/lang-rust": "^6.0.1",
- "@codemirror/language": "^6.3.1",
- "@codemirror/state": "^6.1.4",
- "@codemirror/view": "^6.5.1",
+ "@codemirror/language": "^6.3.2",
+ "@codemirror/state": "^6.2.0",
+ "@codemirror/view": "^6.7.1",
"@firefox-devtools/react-contextmenu": "^5.1.0",
"@fluent/bundle": "^0.17.1",
"@fluent/langneg": "^0.6.2",
"@fluent/react": "^0.14.1",
"@lezer/highlight": "^1.1.3",
+ "@tgwf/co2": "^0.11.4",
"array-move": "^3.0.1",
"array-range": "^1.0.1",
"clamp": "^1.0.1",
"classnames": "^2.3.2",
"common-tags": "^1.8.2",
"copy-to-clipboard": "^3.3.3",
- "core-js": "^3.26.1",
+ "core-js": "^3.27.1",
"escape-string-regexp": "^4.0.0",
"gecko-profiler-demangle": "^0.3.3",
"idb": "^7.1.1",
@@ -75,7 +76,7 @@
"mixedtuplemap": "^1.0.0",
"namedtuplemap": "^1.0.0",
"photon-colors": "^3.3.2",
- "query-string": "^7.1.3",
+ "query-string": "^8.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-intersection-observer": "^9.4.1",
@@ -91,15 +92,15 @@
"workbox-window": "^6.5.4"
},
"devDependencies": {
- "@babel/cli": "^7.19.3",
- "@babel/core": "^7.20.5",
+ "@babel/cli": "^7.20.7",
+ "@babel/core": "^7.20.7",
"@babel/eslint-parser": "^7.19.1",
"@babel/eslint-plugin": "^7.19.1",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-env": "^7.20.2",
"@babel/preset-flow": "^7.18.6",
"@babel/preset-react": "^7.18.6",
- "@testing-library/dom": "^8.19.0",
+ "@testing-library/dom": "^8.19.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"alex": "^11.0.0",
@@ -108,26 +109,26 @@
"babel-loader": "^9.1.0",
"babel-plugin-module-resolver": "^4.1.0",
"browserslist": "^4.21.4",
- "caniuse-lite": "^1.0.30001431",
+ "caniuse-lite": "^1.0.30001439",
"circular-dependency-plugin": "^5.2.1",
"codecov": "^3.8.3",
"copy-webpack-plugin": "^11.0.0",
"cross-env": "^7.0.3",
- "css-loader": "^6.7.2",
+ "css-loader": "^6.7.3",
"cssnano": "^5.1.14",
"devtools-license-check": "^0.9.0",
- "eslint": "^8.27.0",
+ "eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.26.0",
- "eslint-plugin-jest": "^27.1.5",
+ "eslint-plugin-jest": "^27.1.7",
"eslint-plugin-jest-dom": "^4.0.3",
"eslint-plugin-jest-formatting": "^3.1.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.11",
"eslint-plugin-testing-library": "^5.9.1",
- "fake-indexeddb": "^4.0.0",
+ "fake-indexeddb": "^4.0.1",
"fetch-mock-jest": "^1.5.1",
"file-loader": "^6.2.0",
"flow-bin": "^0.96.0",
@@ -141,23 +142,23 @@
"jest-extended": "^3.2.0",
"json-loader": "^0.5.7",
"local-web-server": "^5.2.1",
- "lockfile-lint": "^4.9.6",
+ "lockfile-lint": "^4.10.0",
"mkdirp": "^1.0.4",
"node-fetch": "^2.6.7",
"npm-run-all": "^4.1.5",
- "postcss": "^8.4.16",
- "postcss-loader": "^7.0.1",
- "prettier": "^2.8.0",
+ "postcss": "^8.4.20",
+ "postcss-loader": "^7.0.2",
+ "prettier": "^2.8.1",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"style-loader": "^3.3.1",
- "stylelint": "^14.15.0",
+ "stylelint": "^14.16.1",
"stylelint-config-idiomatic-order": "^9.0.0",
"stylelint-config-prettier": "^9.0.4",
"stylelint-config-standard": "^29.0.0",
"stylelint-prettier": "^2.0.0",
"webpack": "^5.75.0",
- "webpack-cli": "^5.0.0",
+ "webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1",
"workbox-webpack-plugin": "^6.5.4"
},
@@ -171,8 +172,8 @@
"js",
"jsx"
],
- "moduleDirectories": [
- "node_modules"
+ "transformIgnorePatterns": [
+ "/node_modules/(?!(query-string|decode-uri-component|split-on-first|filter-obj)/)"
],
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|ftl)$": "/src/test/fixtures/mocks/file-mock.js",
diff --git a/src/actions/profile-view.js b/src/actions/profile-view.js
index 81e77f36fc..9d78b10029 100644
--- a/src/actions/profile-view.js
+++ b/src/actions/profile-view.js
@@ -36,12 +36,7 @@ import {
getInvertCallstack,
getHash,
} from 'firefox-profiler/selectors/url-state';
-import {
- getCallNodePathFromIndex,
- getSampleIndexToCallNodeIndex,
- getSampleCategories,
- findBestAncestorCallNode,
-} from 'firefox-profiler/profile-logic/profile-data';
+import { getCallNodePathFromIndex } from 'firefox-profiler/profile-logic/profile-data';
import {
assertExhaustiveCheck,
getFirstItemFromSet,
@@ -78,6 +73,8 @@ import type {
Tid,
GlobalTrack,
KeyboardModifiers,
+ TableViewOptions,
+ SelectionContext,
} from 'firefox-profiler/types';
import {
funcHasDirectRecursiveCall,
@@ -103,6 +100,7 @@ import { intersectSets } from 'firefox-profiler/utils/set';
export function changeSelectedCallNode(
threadsKey: ThreadsKey,
selectedCallNodePath: CallNodePath,
+ context: SelectionContext = { source: 'auto' },
optionalExpandedToCallNodePath?: CallNodePath
): Action {
if (optionalExpandedToCallNodePath) {
@@ -123,6 +121,7 @@ export function changeSelectedCallNode(
selectedCallNodePath,
optionalExpandedToCallNodePath,
threadsKey,
+ context,
};
}
@@ -148,20 +147,24 @@ export function changeRightClickedCallNode(
*/
export function selectLeafCallNode(
threadsKey: ThreadsKey,
- sampleIndex: IndexIntoSamplesTable
+ sampleIndex: IndexIntoSamplesTable | null
): ThunkAction {
return (dispatch, getState) => {
const threadSelectors = getThreadSelectorsFromThreadsKey(threadsKey);
const filteredThread = threadSelectors.getFilteredThread(getState());
const callNodeInfo = threadSelectors.getCallNodeInfo(getState());
- // The newSelectedStack could be undefined if there are 0 samples.
- const newSelectedStack = filteredThread.samples.stack[sampleIndex];
+ let newSelectedCallNode = -1;
+ if (sampleIndex !== null) {
+ // The newSelectedStack could be undefined if there are 0 samples.
+ const newSelectedStack = filteredThread.samples.stack[sampleIndex];
+
+ if (newSelectedStack !== null && newSelectedStack !== undefined) {
+ newSelectedCallNode =
+ callNodeInfo.stackIndexToCallNodeIndex[newSelectedStack];
+ }
+ }
- const newSelectedCallNode =
- newSelectedStack === null || newSelectedStack === undefined
- ? -1
- : callNodeInfo.stackIndexToCallNodeIndex[newSelectedStack];
dispatch(
changeSelectedCallNode(
threadsKey,
@@ -180,15 +183,20 @@ export function selectLeafCallNode(
*/
export function selectRootCallNode(
threadsKey: ThreadsKey,
- sampleIndex: IndexIntoSamplesTable
+ sampleIndex: IndexIntoSamplesTable | null
): ThunkAction {
return (dispatch, getState) => {
const threadSelectors = getThreadSelectorsFromThreadsKey(threadsKey);
const filteredThread = threadSelectors.getFilteredThread(getState());
const callNodeInfo = threadSelectors.getCallNodeInfo(getState());
+ if (sampleIndex === null) {
+ dispatch(changeSelectedCallNode(threadsKey, []));
+ return;
+ }
const newSelectedStack = filteredThread.samples.stack[sampleIndex];
if (newSelectedStack === null || newSelectedStack === undefined) {
+ dispatch(changeSelectedCallNode(threadsKey, []));
return;
}
const newSelectedCallNode =
@@ -200,70 +208,14 @@ export function selectRootCallNode(
);
const rootCallNodePath = [selectedCallNodePath[0]];
- dispatch(
- changeSelectedCallNode(threadsKey, rootCallNodePath, selectedCallNodePath)
- );
- };
-}
-
-/**
- * This function provides a different strategy for selecting call nodes. It selects
- * a "best" ancestor call node, but also expands out its children nodes to the
- * actual call node that was clicked. See findBestAncestorCallNode for more
- * on the "best" call node.
- */
-export function selectBestAncestorCallNodeAndExpandCallTree(
- threadsKey: ThreadsKey,
- sampleIndex: IndexIntoSamplesTable
-): ThunkAction {
- return (dispatch, getState) => {
- const threadSelectors = getThreadSelectorsFromThreadsKey(threadsKey);
- const fullThread = threadSelectors.getRangeFilteredThread(getState());
- const filteredThread = threadSelectors.getFilteredThread(getState());
- const unfilteredStack = fullThread.samples.stack[sampleIndex];
- const callNodeInfo = threadSelectors.getCallNodeInfo(getState());
-
- if (unfilteredStack === null) {
- return false;
- }
-
- const { callNodeTable, stackIndexToCallNodeIndex } = callNodeInfo;
- const sampleIndexToCallNodeIndex = getSampleIndexToCallNodeIndex(
- filteredThread.samples.stack,
- stackIndexToCallNodeIndex
- );
- const clickedCallNode = sampleIndexToCallNodeIndex[sampleIndex];
- const clickedCategory = fullThread.stackTable.category[unfilteredStack];
-
- if (clickedCallNode === null) {
- return false;
- }
-
- const sampleCategories = getSampleCategories(
- fullThread.samples,
- fullThread.stackTable
- );
- const bestAncestorCallNode = findBestAncestorCallNode(
- callNodeInfo,
- sampleIndexToCallNodeIndex,
- sampleCategories,
- clickedCallNode,
- clickedCategory
- );
-
- // In one dispatch, change the selected call node to the best ancestor call node, but
- // also expand out to the clicked call node.
dispatch(
changeSelectedCallNode(
threadsKey,
- // Select the best ancestor call node.
- getCallNodePathFromIndex(bestAncestorCallNode, callNodeTable),
- // Also expand the children nodes out further below it to what was actually
- // clicked.
- getCallNodePathFromIndex(clickedCallNode, callNodeTable)
+ rootCallNodePath,
+ { source: 'auto' },
+ selectedCallNodePath
)
);
- return true;
};
}
@@ -1691,22 +1643,26 @@ export function changeExpandedCallNodes(
export function changeSelectedMarker(
threadsKey: ThreadsKey,
- selectedMarker: MarkerIndex | null
+ selectedMarker: MarkerIndex | null,
+ context: SelectionContext = { source: 'auto' }
): Action {
return {
type: 'CHANGE_SELECTED_MARKER',
selectedMarker,
threadsKey,
+ context,
};
}
export function changeSelectedNetworkMarker(
threadsKey: ThreadsKey,
- selectedNetworkMarker: MarkerIndex | null
+ selectedNetworkMarker: MarkerIndex | null,
+ context: SelectionContext = { source: 'auto' }
): Action {
return {
type: 'CHANGE_SELECTED_NETWORK_MARKER',
selectedNetworkMarker,
threadsKey,
+ context,
};
}
@@ -1981,6 +1937,17 @@ export function changeMouseTimePosition(
};
}
+export function changeTableViewOptions(
+ tab: TabSlug,
+ tableViewOptions: TableViewOptions
+): Action {
+ return {
+ type: 'CHANGE_TABLE_VIEW_OPTIONS',
+ tab,
+ tableViewOptions,
+ };
+}
+
export function openSourceView(file: string, currentTab: TabSlug): Action {
return {
type: 'OPEN_SOURCE_VIEW',
diff --git a/src/components/app/Home.js b/src/components/app/Home.js
index 492c82e1ef..94f06dc81f 100644
--- a/src/components/app/Home.js
+++ b/src/components/app/Home.js
@@ -355,6 +355,20 @@ class HomeImpl extends React.PureComponent {
)}
+
+ ),
+ }}
+ >
+
+ You can also profile Firefox for Android. For more information,
+ please consult this documentation:{' '}
+ Profiling Firefox for Android directly on device.
+
+
{/* end of grid container */}
@@ -393,6 +407,20 @@ class HomeImpl extends React.PureComponent {
{this._renderShortcuts()}
+
+ ),
+ }}
+ >
+
+ You can also profile Firefox for Android. For more information,
+ please consult this documentation:{' '}
+ Profiling Firefox for Android directly on device.
+
+
{/* end of grid container */}
@@ -417,9 +445,6 @@ class HomeImpl extends React.PureComponent {
{/* Right column: instructions */}
{/* end of grid container */}
@@ -514,6 +553,37 @@ class HomeImpl extends React.PureComponent {
}
/>
+
+ ),
+ simpleperf: (
+
+ ),
+ androidstudio: (
+
+ ),
+ dhat: (
+
+ ),
+ write: (
+
+ ),
+ }}
+ >
+
+ The Firefox Profiler can also import profiles from other
+ profilers, such as Linux perf,
+ Android SimplePerf, the Chrome
+ performance panel,{' '}
+ Android Studio, or any file
+ using the dhat format.{' '}
+ Learn how to write your own importer.
+
+
+
{
+
Marker Table
{
rowHeight={30}
indentWidth={15}
onEnterKey={this._onEnterKey}
+ viewOptions={defaultTableViewOptions}
/>
diff --git a/src/components/calltree/CallTree.css b/src/components/calltree/CallTree.css
index 7134a4daa8..3f5702c346 100644
--- a/src/components/calltree/CallTree.css
+++ b/src/components/calltree/CallTree.css
@@ -7,20 +7,6 @@
text-align: right;
}
-.treeViewFixedColumn.total {
- width: 70px;
-}
-
-/* The header for the total column spans both totalPercent and total columns */
-.treeViewHeaderColumn.total {
- width: 120px;
-}
-
-.treeViewFixedColumn.totalPercent {
- width: 50px;
- border-right: none;
-}
-
/* The header for the totalPercent column is not visible */
.treeViewHeaderColumn.totalPercent {
display: none;
@@ -32,7 +18,6 @@
.treeViewFixedColumn.icon {
display: flex;
- width: 19px;
flex-flow: column nowrap;
align-items: center;
}
diff --git a/src/components/calltree/CallTree.js b/src/components/calltree/CallTree.js
index cdfbda64eb..6191a7cc09 100644
--- a/src/components/calltree/CallTree.js
+++ b/src/components/calltree/CallTree.js
@@ -20,6 +20,8 @@ import {
getScrollToSelectionGeneration,
getFocusCallTreeGeneration,
getPreviewSelection,
+ getCategories,
+ getCurrentTableViewOptions,
} from 'firefox-profiler/selectors/profile';
import { selectedThreadSelectors } from 'firefox-profiler/selectors/per-thread';
import {
@@ -29,6 +31,7 @@ import {
addTransformToStack,
handleCallNodeTransformShortcut,
openSourceView,
+ changeTableViewOptions,
} from 'firefox-profiler/actions/profile-view';
import { assertExhaustiveCheck } from 'firefox-profiler/utils/flow';
@@ -37,13 +40,19 @@ import type {
ImplementationFilter,
ThreadsKey,
CallNodeInfo,
+ CategoryList,
IndexIntoCallNodeTable,
CallNodeDisplayData,
WeightType,
+ TableViewOptions,
+ SelectionContext,
} from 'firefox-profiler/types';
import type { CallTree as CallTreeType } from 'firefox-profiler/profile-logic/call-tree';
-import type { Column } from 'firefox-profiler/components/shared/TreeView';
+import type {
+ Column,
+ MaybeResizableColumn,
+} from 'firefox-profiler/components/shared/TreeView';
import type { ConnectedProps } from 'firefox-profiler/utils/connect';
import './CallTree.css';
@@ -54,6 +63,7 @@ type StateProps = {|
+focusCallTreeGeneration: number,
+tree: CallTreeType,
+callNodeInfo: CallNodeInfo,
+ +categories: CategoryList,
+selectedCallNodeIndex: IndexIntoCallNodeTable | null,
+rightClickedCallNodeIndex: IndexIntoCallNodeTable | null,
+expandedCallNodeIndexes: Array,
@@ -63,6 +73,7 @@ type StateProps = {|
+implementationFilter: ImplementationFilter,
+callNodeMaxDepth: number,
+weightType: WeightType,
+ +tableViewOptions: TableViewOptions,
|};
type DispatchProps = {|
@@ -72,6 +83,7 @@ type DispatchProps = {|
+addTransformToStack: typeof addTransformToStack,
+handleCallNodeTransformShortcut: typeof handleCallNodeTransformShortcut,
+openSourceView: typeof openSourceView,
+ +onTableViewOptionsChange: (TableViewOptions) => any,
|};
type Props = ConnectedProps<{||}, StateProps, DispatchProps>;
@@ -93,46 +105,97 @@ class CallTreeImpl extends PureComponent {
* appropriate labels for the call tree based on this weight.
*/
_weightTypeToColumns = memoize(
- (weightType: WeightType): Column[] => {
+ (weightType: WeightType): MaybeResizableColumn[] => {
switch (weightType) {
case 'tracing-ms':
return [
- { propName: 'totalPercent', titleL10nId: '' },
+ {
+ propName: 'totalPercent',
+ titleL10nId: '',
+ initialWidth: 50,
+ hideDividerAfter: true,
+ },
{
propName: 'total',
titleL10nId: 'CallTree--tracing-ms-total',
+ minWidth: 30,
+ initialWidth: 70,
+ resizable: true,
+ headerWidthAdjustment: 50,
},
{
propName: 'self',
titleL10nId: 'CallTree--tracing-ms-self',
+ minWidth: 30,
+ initialWidth: 70,
+ resizable: true,
+ },
+ {
+ propName: 'icon',
+ titleL10nId: '',
+ component: Icon,
+ initialWidth: 10,
},
- { propName: 'icon', titleL10nId: '', component: Icon },
];
case 'samples':
return [
- { propName: 'totalPercent', titleL10nId: '' },
+ {
+ propName: 'totalPercent',
+ titleL10nId: '',
+ initialWidth: 50,
+ hideDividerAfter: true,
+ },
{
propName: 'total',
titleL10nId: 'CallTree--samples-total',
+ minWidth: 30,
+ initialWidth: 70,
+ resizable: true,
+ headerWidthAdjustment: 50,
},
{
propName: 'self',
titleL10nId: 'CallTree--samples-self',
+ minWidth: 30,
+ initialWidth: 70,
+ resizable: true,
+ },
+ {
+ propName: 'icon',
+ titleL10nId: '',
+ component: Icon,
+ initialWidth: 10,
},
- { propName: 'icon', titleL10nId: '', component: Icon },
];
case 'bytes':
return [
- { propName: 'totalPercent', titleL10nId: '' },
+ {
+ propName: 'totalPercent',
+ titleL10nId: '',
+ initialWidth: 50,
+ hideDividerAfter: true,
+ },
{
propName: 'total',
titleL10nId: 'CallTree--bytes-total',
+ minWidth: 30,
+ initialWidth: 140,
+ resizable: true,
+ headerWidthAdjustment: 50,
},
{
propName: 'self',
titleL10nId: 'CallTree--bytes-self',
+ minWidth: 30,
+ initialWidth: 90,
+ resizable: true,
+ },
+ {
+ propName: 'icon',
+ titleL10nId: '',
+ component: Icon,
+ initialWidth: 10,
},
- { propName: 'icon', titleL10nId: '', component: Icon },
];
default:
throw assertExhaustiveCheck(weightType, 'Unhandled WeightType.');
@@ -144,27 +207,29 @@ class CallTreeImpl extends PureComponent {
componentDidMount() {
this.focus();
- if (this.props.selectedCallNodeIndex === null) {
- this.procureInterestingInitialSelection();
- } else if (this._treeView) {
+ this.maybeProcureInterestingInitialSelection();
+
+ if (this.props.selectedCallNodeIndex === null && this._treeView) {
this._treeView.scrollSelectionIntoView();
}
}
componentDidUpdate(prevProps) {
if (
- this.props.scrollToSelectionGeneration >
- prevProps.scrollToSelectionGeneration
+ this.props.focusCallTreeGeneration > prevProps.focusCallTreeGeneration
) {
- if (this._treeView) {
- this._treeView.scrollSelectionIntoView();
- }
+ this.focus();
}
+ this.maybeProcureInterestingInitialSelection();
+
if (
- this.props.focusCallTreeGeneration > prevProps.focusCallTreeGeneration
+ this.props.selectedCallNodeIndex !== null &&
+ this.props.scrollToSelectionGeneration >
+ prevProps.scrollToSelectionGeneration &&
+ this._treeView
) {
- this.focus();
+ this._treeView.scrollSelectionIntoView();
}
}
@@ -174,11 +239,15 @@ class CallTreeImpl extends PureComponent {
}
}
- _onSelectedCallNodeChange = (newSelectedCallNode: IndexIntoCallNodeTable) => {
+ _onSelectedCallNodeChange = (
+ newSelectedCallNode: IndexIntoCallNodeTable,
+ context: SelectionContext
+ ) => {
const { callNodeInfo, threadsKey, changeSelectedCallNode } = this.props;
changeSelectedCallNode(
threadsKey,
- getCallNodePathFromIndex(newSelectedCallNode, callNodeInfo.callNodeTable)
+ getCallNodePathFromIndex(newSelectedCallNode, callNodeInfo.callNodeTable),
+ context
);
};
@@ -228,10 +297,26 @@ class CallTreeImpl extends PureComponent {
openSourceView(file, 'calltree');
};
- procureInterestingInitialSelection() {
+ maybeProcureInterestingInitialSelection() {
// Expand the heaviest callstack up to a certain depth and select the frame
// at that depth.
- const { tree, expandedCallNodeIndexes } = this.props;
+ const {
+ tree,
+ expandedCallNodeIndexes,
+ selectedCallNodeIndex,
+ callNodeInfo: { callNodeTable },
+ categories,
+ } = this.props;
+
+ if (selectedCallNodeIndex !== null || expandedCallNodeIndexes.length > 0) {
+ // Let's not change some existing state.
+ return;
+ }
+
+ const idleCategoryIndex = categories.findIndex(
+ (category) => category.name === 'Idle'
+ );
+
const newExpandedCallNodeIndexes = expandedCallNodeIndexes.slice();
const maxInterestingDepth = 17; // scientifically determined
let currentCallNodeIndex = tree.getRoots()[0];
@@ -245,18 +330,27 @@ class CallTreeImpl extends PureComponent {
if (children.length === 0) {
break;
}
- currentCallNodeIndex = children[0];
+
+ // Let's find if there's a non idle children.
+ const firstNonIdleNode = children.find(
+ (nodeIndex) => callNodeTable.category[nodeIndex] !== idleCategoryIndex
+ );
+
+ // If there's a non idle children, use it; otherwise use the first
+ // children (that will be idle).
+ currentCallNodeIndex =
+ firstNonIdleNode !== undefined ? firstNonIdleNode : children[0];
newExpandedCallNodeIndexes.push(currentCallNodeIndex);
}
this._onExpandedCallNodesChange(newExpandedCallNodeIndexes);
- const category = tree.getDisplayData(currentCallNodeIndex).categoryName;
- if (category !== 'Idle') {
+ const categoryIndex = callNodeTable.category[currentCallNodeIndex];
+ if (categoryIndex !== idleCategoryIndex) {
// If we selected the call node with a "idle" category, we'd have a
// completely dimmed activity graph because idle stacks are not drawn in
// this graph. Because this isn't probably what the average user wants we
// do it only when the category is something different.
- this._onSelectedCallNodeChange(currentCallNodeIndex);
+ this._onSelectedCallNodeChange(currentCallNodeIndex, { source: 'auto' });
}
}
@@ -270,6 +364,8 @@ class CallTreeImpl extends PureComponent {
disableOverscan,
callNodeMaxDepth,
weightType,
+ tableViewOptions,
+ onTableViewOptionsChange,
} = this.props;
if (tree.getRoots().length === 0) {
return ;
@@ -296,6 +392,8 @@ class CallTreeImpl extends PureComponent {
onKeyDown={this._onKeyDown}
onEnterKey={this._onEnterOrDoubleClick}
onDoubleClick={this._onEnterOrDoubleClick}
+ viewOptions={tableViewOptions}
+ onViewOptionsChange={onTableViewOptionsChange}
/>
);
}
@@ -308,6 +406,7 @@ export const CallTree = explicitConnect<{||}, StateProps, DispatchProps>({
focusCallTreeGeneration: getFocusCallTreeGeneration(state),
tree: selectedThreadSelectors.getCallTree(state),
callNodeInfo: selectedThreadSelectors.getCallNodeInfo(state),
+ categories: getCategories(state),
selectedCallNodeIndex:
selectedThreadSelectors.getSelectedCallNodeIndex(state),
rightClickedCallNodeIndex:
@@ -324,6 +423,7 @@ export const CallTree = explicitConnect<{||}, StateProps, DispatchProps>({
callNodeMaxDepth:
selectedThreadSelectors.getFilteredCallNodeMaxDepth(state),
weightType: selectedThreadSelectors.getWeightTypeForCallTree(state),
+ tableViewOptions: getCurrentTableViewOptions(state),
}),
mapDispatchToProps: {
changeSelectedCallNode,
@@ -332,6 +432,8 @@ export const CallTree = explicitConnect<{||}, StateProps, DispatchProps>({
addTransformToStack,
handleCallNodeTransformShortcut,
openSourceView,
+ onTableViewOptionsChange: (options: TableViewOptions) =>
+ changeTableViewOptions('calltree', options),
},
component: CallTreeImpl,
});
diff --git a/src/components/flame-graph/FlameGraph.js b/src/components/flame-graph/FlameGraph.js
index af3fd081f1..91dac18d33 100644
--- a/src/components/flame-graph/FlameGraph.js
+++ b/src/components/flame-graph/FlameGraph.js
@@ -103,6 +103,14 @@ type Props = ConnectedProps<{||}, StateProps, DispatchProps>;
class FlameGraphImpl extends React.PureComponent {
_viewport: HTMLDivElement | null = null;
+ componentDidMount() {
+ document.addEventListener('copy', this._onCopy, false);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('copy', this._onCopy, false);
+ }
+
_onSelectedCallNodeChange = (
callNodeIndex: IndexIntoCallNodeTable | null
) => {
@@ -301,6 +309,24 @@ class FlameGraphImpl extends React.PureComponent {
}
};
+ _onCopy = (event: ClipboardEvent) => {
+ if (document.activeElement === this._viewport) {
+ event.preventDefault();
+ const {
+ callNodeInfo: { callNodeTable },
+ selectedCallNodeIndex,
+ thread,
+ } = this.props;
+ if (selectedCallNodeIndex !== null) {
+ const funcIndex = callNodeTable.func[selectedCallNodeIndex];
+ const funcName = thread.stringTable.getString(
+ thread.funcTable.name[funcIndex]
+ );
+ event.clipboardData.setData('text/plain', funcName);
+ }
+ }
+ };
+
render() {
const {
thread,
diff --git a/src/components/marker-table/index.js b/src/components/marker-table/index.js
index 6928167fd2..eb6fbfc2ab 100644
--- a/src/components/marker-table/index.js
+++ b/src/components/marker-table/index.js
@@ -14,12 +14,14 @@ import {
getZeroAt,
getScrollToSelectionGeneration,
getMarkerSchemaByName,
+ getCurrentTableViewOptions,
} from '../../selectors/profile';
import { selectedThreadSelectors } from '../../selectors/per-thread';
import { getSelectedThreadsKey } from '../../selectors/url-state';
import {
changeSelectedMarker,
changeRightClickedMarker,
+ changeTableViewOptions,
} from '../../actions/profile-view';
import { MarkerSettings } from '../shared/MarkerSettings';
import { formatSeconds, formatTimestamp } from '../../utils/format-numbers';
@@ -32,6 +34,8 @@ import type {
MarkerIndex,
Milliseconds,
MarkerSchemaByName,
+ TableViewOptions,
+ SelectionContext,
} from 'firefox-profiler/types';
import type { ConnectedProps } from '../../utils/connect';
@@ -123,7 +127,11 @@ class MarkerTree {
start: _formatStart(marker.start, this._zeroAt),
duration,
name,
- type: getMarkerSchemaName(this._markerSchemaByName, marker),
+ type: getMarkerSchemaName(
+ this._markerSchemaByName,
+ marker.name,
+ marker.data
+ ),
};
this._displayDataByIndex.set(markerIndex, displayData);
}
@@ -145,20 +153,40 @@ type StateProps = {|
+scrollToSelectionGeneration: number,
+markerSchemaByName: MarkerSchemaByName,
+getMarkerLabel: (MarkerIndex) => string,
+ +tableViewOptions: TableViewOptions,
|};
type DispatchProps = {|
+changeSelectedMarker: typeof changeSelectedMarker,
+changeRightClickedMarker: typeof changeRightClickedMarker,
+ +onTableViewOptionsChange: (TableViewOptions) => any,
|};
type Props = ConnectedProps<{||}, StateProps, DispatchProps>;
class MarkerTableImpl extends PureComponent {
_fixedColumns = [
- { propName: 'start', titleL10nId: 'MarkerTable--start' },
- { propName: 'duration', titleL10nId: 'MarkerTable--duration' },
- { propName: 'type', titleL10nId: 'MarkerTable--type' },
+ {
+ propName: 'start',
+ titleL10nId: 'MarkerTable--start',
+ minWidth: 30,
+ initialWidth: 90,
+ resizable: true,
+ },
+ {
+ propName: 'duration',
+ titleL10nId: 'MarkerTable--duration',
+ minWidth: 30,
+ initialWidth: 80,
+ resizable: true,
+ },
+ {
+ propName: 'type',
+ titleL10nId: 'MarkerTable--type',
+ minWidth: 30,
+ initialWidth: 150,
+ resizable: true,
+ },
];
_mainColumn = { propName: 'name', titleL10nId: 'MarkerTable--description' };
_expandedNodeIds: Array = [];
@@ -190,9 +218,12 @@ class MarkerTableImpl extends PureComponent {
}
}
- _onSelectionChange = (selectedMarker: MarkerIndex) => {
+ _onSelectionChange = (
+ selectedMarker: MarkerIndex,
+ context: SelectionContext
+ ) => {
const { threadsKey, changeSelectedMarker } = this.props;
- changeSelectedMarker(threadsKey, selectedMarker);
+ changeSelectedMarker(threadsKey, selectedMarker, context);
};
_onRightClickSelection = (selectedMarker: MarkerIndex) => {
@@ -243,6 +274,8 @@ class MarkerTableImpl extends PureComponent {
contextMenuId="MarkerContextMenu"
rowHeight={16}
indentWidth={10}
+ viewOptions={this.props.tableViewOptions}
+ onViewOptionsChange={this.props.onTableViewOptionsChange}
/>
)}
@@ -262,7 +295,13 @@ export const MarkerTable = explicitConnect<{||}, StateProps, DispatchProps>({
zeroAt: getZeroAt(state),
markerSchemaByName: getMarkerSchemaByName(state),
getMarkerLabel: selectedThreadSelectors.getMarkerTableLabelGetter(state),
+ tableViewOptions: getCurrentTableViewOptions(state),
}),
- mapDispatchToProps: { changeSelectedMarker, changeRightClickedMarker },
+ mapDispatchToProps: {
+ changeSelectedMarker,
+ changeRightClickedMarker,
+ onTableViewOptionsChange: (tableViewOptions) =>
+ changeTableViewOptions('marker-table', tableViewOptions),
+ },
component: MarkerTableImpl,
});
diff --git a/src/components/network-chart/NetworkChartRow.js b/src/components/network-chart/NetworkChartRow.js
index 88e2f44123..a26d0d114c 100644
--- a/src/components/network-chart/NetworkChartRow.js
+++ b/src/components/network-chart/NetworkChartRow.js
@@ -324,7 +324,6 @@ type NetworkChartRowProps = {|
+isRightClicked: boolean,
+isSelected: boolean,
+isHoveredFromState: boolean,
- +select: (MarkerIndex) => mixed,
+onLeftClick?: (MarkerIndex) => mixed,
+onRightClick?: (MarkerIndex) => mixed,
+onHover?: (MarkerIndex | null) => mixed,
diff --git a/src/components/network-chart/index.js b/src/components/network-chart/index.js
index 7d81de1919..f9f3e4860c 100644
--- a/src/components/network-chart/index.js
+++ b/src/components/network-chart/index.js
@@ -34,6 +34,7 @@ import type {
MarkerIndex,
StartEndRange,
ThreadsKey,
+ SelectionContext,
} from 'firefox-profiler/types';
import type { ConnectedProps } from '../../utils/connect';
@@ -91,6 +92,7 @@ class NetworkChartImpl extends React.PureComponent {
componentDidMount() {
this.focus();
+ this.scrollSelectionIntoView();
}
componentDidUpdate(prevProps) {
@@ -161,7 +163,7 @@ class NetworkChartImpl extends React.PureComponent {
if (selected === null || selectedRowIndex === -1) {
// the first condition is redundant, but it makes flow happy
- this._select(allRows[0]);
+ this._selectWithKeyboard(allRows[0]);
return;
}
if (isNavigationKey) {
@@ -169,30 +171,30 @@ class NetworkChartImpl extends React.PureComponent {
case 'ArrowUp': {
if (event.metaKey) {
// On MacOS this is a common shortcut for the Home gesture
- this._select(allRows[0]);
+ this._selectWithKeyboard(allRows[0]);
break;
}
if (selectedRowIndex > 0) {
- this._select(allRows[selectedRowIndex - 1]);
+ this._selectWithKeyboard(allRows[selectedRowIndex - 1]);
}
break;
}
case 'ArrowDown': {
if (event.metaKey) {
// On MacOS this is a common shortcut for the End gesture
- this._select(allRows[allRows.length - 1]);
+ this._selectWithKeyboard(allRows[allRows.length - 1]);
break;
}
if (selectedRowIndex < allRows.length - 1) {
- this._select(allRows[selectedRowIndex + 1]);
+ this._selectWithKeyboard(allRows[selectedRowIndex + 1]);
}
break;
}
case 'PageUp': {
if (selectedRowIndex > 0) {
const nextRow = Math.max(0, selectedRowIndex - ROW_HEIGHT);
- this._select(allRows[nextRow]);
+ this._selectWithKeyboard(allRows[nextRow]);
}
break;
}
@@ -202,16 +204,16 @@ class NetworkChartImpl extends React.PureComponent {
allRows.length - 1,
selectedRowIndex + ROW_HEIGHT
);
- this._select(allRows[nextRow]);
+ this._selectWithKeyboard(allRows[nextRow]);
}
break;
}
case 'Home': {
- this._select(allRows[0]);
+ this._selectWithKeyboard(allRows[0]);
break;
}
case 'End': {
- this._select(allRows[allRows.length - 1]);
+ this._selectWithKeyboard(allRows[allRows.length - 1]);
break;
}
default:
@@ -226,16 +228,23 @@ class NetworkChartImpl extends React.PureComponent {
};
_onLeftClick = (selectedNetworkMarkerIndex: MarkerIndex) => {
- this._onSelectionChange(selectedNetworkMarkerIndex);
+ this._onSelectionChange(selectedNetworkMarkerIndex, { source: 'pointer' });
};
- _select(selectedNetworkMarkerIndex: MarkerIndex) {
- this._onSelectionChange(selectedNetworkMarkerIndex);
+ _selectWithKeyboard(selectedNetworkMarkerIndex: MarkerIndex) {
+ this._onSelectionChange(selectedNetworkMarkerIndex, { source: 'keyboard' });
}
- _onSelectionChange = (selectedNetworkMarkerIndex: MarkerIndex) => {
+ _onSelectionChange = (
+ selectedNetworkMarkerIndex: MarkerIndex,
+ context: SelectionContext
+ ) => {
const { threadsKey, changeSelectedNetworkMarker } = this.props;
- changeSelectedNetworkMarker(threadsKey, selectedNetworkMarkerIndex);
+ changeSelectedNetworkMarker(
+ threadsKey,
+ selectedNetworkMarkerIndex,
+ context
+ );
};
_onRowHovered = (hoveredMarkerIndex: MarkerIndex | null) => {
@@ -288,7 +297,6 @@ class NetworkChartImpl extends React.PureComponent {
isHoveredFromState={hoveredMarkerIndexFromState === markerIndex}
onRightClick={this._onRightClick}
isSelected={selectedNetworkMarkerIndex === markerIndex}
- select={this._select}
onLeftClick={this._onLeftClick}
onHover={this._onRowHovered}
/>
diff --git a/src/components/shared/Reorderable.js b/src/components/shared/Reorderable.js
index 752bb88482..309a2acbd2 100644
--- a/src/components/shared/Reorderable.js
+++ b/src/components/shared/Reorderable.js
@@ -27,6 +27,9 @@ type Props = {|
// See https://flow.org/en/docs/react/children/ for more information.
// Be careful: children need to handle a `style` property.
children: React.ChildrenArray>,
+ // If present, this will be attached to the container added for these
+ // children. As a reminder, the container will use the tagName defined above.
+ innerElementRef?: React.Ref,
|};
type State = {|
@@ -240,7 +243,7 @@ export class Reorderable extends React.PureComponent {
}
render() {
- const { className, order } = this.props;
+ const { className, order, innerElementRef } = this.props;
const children = React.Children.toArray(this.props.children);
const orderedChildren = order.map((childIndex) => children[childIndex]);
const TagName = this.props.tagName;
@@ -248,7 +251,11 @@ export class Reorderable extends React.PureComponent {
if (this.state.phase === 'RESTING') {
return (
-
+
{orderedChildren}
);
@@ -261,8 +268,9 @@ export class Reorderable extends React.PureComponent {
adjustPrecedingBy,
adjustSucceedingBy,
} = this.state;
+
return (
-
+
{orderedChildren.map((child, childIndex) => {
const style = {
transition: '200ms ease-in-out transform',
diff --git a/src/components/shared/TreeView.css b/src/components/shared/TreeView.css
index dda999b6ad..684def6524 100644
--- a/src/components/shared/TreeView.css
+++ b/src/components/shared/TreeView.css
@@ -77,29 +77,45 @@
.treeViewHeaderColumn {
position: relative;
- box-sizing: border-box;
- padding: 0 5px;
line-height: 15px;
white-space: nowrap;
}
.treeViewFixedColumn {
overflow: hidden;
- padding: 0 5px;
text-overflow: ellipsis;
}
+.treeViewColumnDivider {
+ display: flex;
+ width: 20px;
+ flex: none;
+ align-items: stretch;
+ justify-content: center;
+ margin-right: -5px;
+ margin-left: -5px;
+}
+
+.treeViewColumnDivider.isResizable,
+.treeView.isResizingColumns {
+ cursor: col-resize;
+}
+
+.treeViewColumnDivider::before {
+ border-right: 1px solid var(--grey-30);
+ content: '';
+}
+
+.treeViewColumnDivider.isResizable::before {
+ width: 1px;
+ border-left: 1px solid var(--grey-30);
+}
+
.treeViewHeaderColumn.treeViewFixedColumn {
/* The fixed columns in the row don't shrink because they're positioning using
* position: sticky, therefore we prevent shrinking for the columns in the
* header too. */
flex: none;
- border-right: 1px solid #e2e2e2;
-}
-
-.treeViewRowColumn.treeViewFixedColumn {
- box-sizing: border-box;
- border-right: 1px solid var(--grey-30);
}
.treeBadge {
diff --git a/src/components/shared/TreeView.js b/src/components/shared/TreeView.js
index e8e01d8021..cee9fd73bf 100644
--- a/src/components/shared/TreeView.js
+++ b/src/components/shared/TreeView.js
@@ -16,7 +16,7 @@ import { VirtualList } from './VirtualList';
import { ContextMenuTrigger } from './ContextMenuTrigger';
-import type { CssPixels } from 'firefox-profiler/types';
+import type { CssPixels, TableViewOptions } from 'firefox-profiler/types';
import './TreeView.css';
@@ -44,6 +44,9 @@ function PermissiveLocalized(props: React.ElementConfig) {
// See https://github.com/facebook/flow/issues/4099
type RegExpResult = null | ({ index: number, input: string } & string[]);
type NodeIndex = number;
+type TableViewOptionsWithDefault = {|
+ fixedColumnWidths: Array,
+|};
export type Column = {|
+propName: string,
@@ -53,40 +56,95 @@ export type Column = {|
|}>,
|};
+export type MaybeResizableColumn = {|
+ ...Column,
+ /** defaults to initialWidth */
+ +minWidth?: CssPixels,
+ /** This is the initial width, this can be changed in resizable columns */
+ +initialWidth: CssPixels,
+ /** found width + adjustment = width of header column */
+ +headerWidthAdjustment?: CssPixels,
+ // false by default
+ +resizable?: boolean,
+ // is the divider after the column hidden? false by default
+ +hideDividerAfter?: boolean,
+|};
+
type TreeViewHeaderProps = {|
- +fixedColumns: Column[],
+ +fixedColumns: MaybeResizableColumn[],
+mainColumn: Column,
+ +viewOptions: TableViewOptionsWithDefault,
+ // called when the users moves the divider right of the column,
+ // passes the column index and the start x coordinate
+ +onColumnWidthChangeStart: (number, CssPixels) => void,
+ +onColumnWidthReset: (number) => void,
|};
-const TreeViewHeader = ({
- fixedColumns,
- mainColumn,
-}: TreeViewHeaderProps) => {
- if (fixedColumns.length === 0 && !mainColumn.titleL10nId) {
- // If there is nothing to display in the header, do not render it.
- return null;
- }
- return (
-
- {fixedColumns.map((col) => (
+class TreeViewHeader
extends React.PureComponent<
+ TreeViewHeaderProps
+> {
+ _onDividerMouseDown = (event: SyntheticMouseEvent) => {
+ this.props.onColumnWidthChangeStart(
+ Number(event.currentTarget.dataset.columnIndex),
+ event.clientX
+ );
+ };
+
+ _onDividerDoubleClick = (event: SyntheticMouseEvent) => {
+ this.props.onColumnWidthReset(
+ Number(event.currentTarget.dataset.columnIndex)
+ );
+ };
+
+ render() {
+ const { fixedColumns, mainColumn, viewOptions } = this.props;
+ const columnWidths = viewOptions.fixedColumnWidths;
+ if (fixedColumns.length === 0 && !mainColumn.titleL10nId) {
+ // If there is nothing to display in the header, do not render it.
+ return null;
+ }
+ return (
+
+ {fixedColumns.map((col, i) => {
+ const width = columnWidths[i] + (col.headerWidthAdjustment || 0);
+ return (
+
+
+
+
+ {col.hideDividerAfter !== true ? (
+
+ ) : null}
+
+ );
+ })}
- ))}
-
-
-
-
- );
-};
+
+ );
+ }
+}
function reactStringWithHighlightedSubstrings(
string: string,
@@ -123,13 +181,14 @@ function reactStringWithHighlightedSubstrings(
type TreeViewRowFixedColumnsProps = {|
+displayData: DisplayData,
+nodeId: NodeIndex,
- +columns: Column[],
+ +columns: MaybeResizableColumn[],
+index: number,
+isSelected: boolean,
+isRightClicked: boolean,
+onClick: (NodeIndex, SyntheticMouseEvent<>) => mixed,
+highlightRegExp: RegExp | null,
+rowHeightStyle: { height: CssPixels, lineHeight: string },
+ +viewOptions: TableViewOptionsWithDefault,
|};
class TreeViewRowFixedColumns extends React.PureComponent<
@@ -144,12 +203,14 @@ class TreeViewRowFixedColumns extends React.PureComponent<
const {
displayData,
columns,
+ viewOptions,
index,
isSelected,
isRightClicked,
highlightRegExp,
rowHeightStyle,
} = this.props;
+ const columnWidths = viewOptions.fixedColumnWidths;
return (
extends React.PureComponent<
style={rowHeightStyle}
onMouseDown={this._onClick}
>
- {columns.map((col) => {
+ {columns.map((col, i) => {
const RenderComponent = col.component;
const text = displayData[col.propName] || '';
-
return (
-
- {RenderComponent ? (
-
- ) : (
- reactStringWithHighlightedSubstrings(
- text,
- highlightRegExp,
- 'treeViewHighlighting'
- )
- )}
-
+
+
+ {RenderComponent ? (
+
+ ) : (
+ reactStringWithHighlightedSubstrings(
+ text,
+ highlightRegExp,
+ 'treeViewHighlighting'
+ )
+ )}
+
+ {col.hideDividerAfter !== true ? (
+
+ ) : null}
+
);
})}
@@ -373,7 +438,7 @@ interface Tree {
}
type TreeViewProps = {|
- +fixedColumns: Column[],
+ +fixedColumns: MaybeResizableColumn[],
+mainColumn: Column,
+tree: Tree,
+expandedNodeIds: Array,
@@ -386,21 +451,51 @@ type TreeViewProps = {|
+contextMenu?: React.Element,
+contextMenuId?: string,
+maxNodeDepth: number,
- +onSelectionChange: (NodeIndex) => mixed,
+ +onSelectionChange: (
+ NodeIndex,
+ {| source: 'keyboard' | 'pointer' |}
+ ) => mixed,
+onRightClickSelection?: (NodeIndex) => mixed,
+onEnterKey?: (NodeIndex) => mixed,
+onDoubleClick?: (NodeIndex) => mixed,
+rowHeight: CssPixels,
+indentWidth: CssPixels,
+onKeyDown?: (SyntheticKeyboardEvent<>) => void,
+ +viewOptions: TableViewOptions,
+ +onViewOptionsChange?: (TableViewOptions) => void,
+|};
+
+type TreeViewState = {|
+ +fixedColumnWidths: Array | null,
+ +isResizingColumns: boolean,
|};
export class TreeView extends React.PureComponent<
- TreeViewProps
+ TreeViewProps,
+ TreeViewState
> {
_list: VirtualList | null = null;
_takeListRef = (list: VirtualList | null) => (this._list = list);
+ // This contains the information about the current column resizing happening currently.
+ _currentMovedColumnState: {|
+ columnIndex: number,
+ startX: CssPixels,
+ initialWidth: CssPixels,
+ |} | null = null;
+
+ state = {
+ // This contains the current widths, while or after the user resizes them.
+ fixedColumnWidths: null,
+
+ // This is true when the user is currently resizing a column.
+ isResizingColumns: false,
+ };
+
+ // This is incremented when a column changed its size. We use this to force a
+ // rerender of the VirtualList component.
+ _columnSizeChangedCounter: number = 0;
+
// The tuple `specialItems` always contains 2 elements: the first element is
// the selected node id (if any), and the second element is the right clicked
// id (if any).
@@ -421,6 +516,100 @@ export class TreeView extends React.PureComponent<
{ limit: 1 }
);
+ _computeInitialColumnWidthsMemoized = memoize(
+ (fixedColumns: Array>): CssPixels[] =>
+ fixedColumns.map((c) => c.initialWidth)
+ );
+
+ // This returns the column widths from several possible sources, in this order:
+ // * the current state (this means the user changed them recently, or is
+ // currently changing them)
+ // * the view options (this comes from the redux state, this means the user
+ // changed them in the past)
+ // * or finally the initial values from the fixedColumns information.
+ _getCurrentFixedColumnWidths = (): Array => {
+ return (
+ this.state.fixedColumnWidths ||
+ this.props.viewOptions.fixedColumnWidths ||
+ this._computeInitialColumnWidthsMemoized(this.props.fixedColumns)
+ );
+ };
+
+ _getCurrentViewOptions = (): TableViewOptionsWithDefault => {
+ return {
+ fixedColumnWidths: this._getCurrentFixedColumnWidths(),
+ };
+ };
+
+ _onColumnWidthChangeStart = (columnIndex: number, startX: CssPixels) => {
+ this._currentMovedColumnState = {
+ columnIndex,
+ startX,
+ initialWidth: this._getCurrentFixedColumnWidths()[columnIndex],
+ };
+ this.setState({ isResizingColumns: true });
+ window.addEventListener('mousemove', this._onColumnWidthChangeMouseMove);
+ window.addEventListener('mouseup', this._onColumnWidthChangeMouseUp);
+ };
+
+ _cleanUpMouseHandlers = () => {
+ window.removeEventListener('mousemove', this._onColumnWidthChangeMouseMove);
+ window.removeEventListener('mouseup', this._onColumnWidthChangeMouseUp);
+ };
+
+ _onColumnWidthChangeMouseMove = (event: MouseEvent) => {
+ const columnState = this._currentMovedColumnState;
+ if (columnState !== null) {
+ const { columnIndex, startX, initialWidth } = columnState;
+ const column = this.props.fixedColumns[columnIndex];
+ const fixedColumnWidths = this._getCurrentFixedColumnWidths();
+ const diff = event.clientX - startX;
+ const newWidth = Math.max(initialWidth + diff, column.minWidth || 10);
+ this.setState((prevState) => {
+ this._columnSizeChangedCounter++;
+ const newFixedColumnWidths = (
+ prevState.fixedColumnWidths || fixedColumnWidths
+ ).slice();
+ newFixedColumnWidths[columnIndex] = newWidth;
+ return {
+ fixedColumnWidths: newFixedColumnWidths,
+ };
+ });
+ }
+ };
+
+ _onColumnWidthChangeMouseUp = () => {
+ this.setState({ isResizingColumns: false });
+ this._cleanUpMouseHandlers();
+ this._currentMovedColumnState = null;
+ this._propagateColumnWidthChange(this._getCurrentFixedColumnWidths());
+ };
+
+ componentWillUnmount = () => {
+ this._cleanUpMouseHandlers();
+ };
+
+ _onColumnWidthReset = (columnIndex: number) => {
+ const column = this.props.fixedColumns[columnIndex];
+ const fixedColumnWidths = this._getCurrentFixedColumnWidths();
+ const newFixedColumnWidths = fixedColumnWidths.slice();
+ newFixedColumnWidths[columnIndex] = column.initialWidth || 10;
+ this._columnSizeChangedCounter++;
+ this.setState({ fixedColumnWidths: newFixedColumnWidths });
+ this._propagateColumnWidthChange(newFixedColumnWidths);
+ };
+
+ // triggers a re-render
+ _propagateColumnWidthChange = (fixedColumnWidths: Array) => {
+ const { onViewOptionsChange, viewOptions } = this.props;
+ if (onViewOptionsChange) {
+ onViewOptionsChange({
+ ...viewOptions,
+ fixedColumnWidths,
+ });
+ }
+ };
+
_computeAllVisibleRowsMemoized = memoize(
(tree: Tree, expandedNodes: Set) => {
function _addVisibleRowsFromNode(tree, expandedNodes, arr, nodeId) {
@@ -448,11 +637,15 @@ export class TreeView extends React.PureComponent<
/* eslint-disable-next-line react/no-unused-class-component-methods */
scrollSelectionIntoView() {
const { selectedNodeId, tree } = this.props;
- if (this._list && selectedNodeId !== null) {
- const list = this._list; // this temp variable so that flow knows that it's non-null
- const rowIndex = this._getAllVisibleRows().indexOf(selectedNodeId);
- const depth = tree.getDepth(selectedNodeId);
- list.scrollItemIntoView(rowIndex, depth * 10);
+ const list = this._list; // this temp variable so that flow knows that it's non-null later
+ if (list) {
+ if (selectedNodeId === null) {
+ list.scrollItemIntoView(0, 0);
+ } else {
+ const rowIndex = this._getAllVisibleRows().indexOf(selectedNodeId);
+ const depth = tree.getDepth(selectedNodeId);
+ list.scrollItemIntoView(rowIndex, depth * 10);
+ }
}
}
@@ -478,6 +671,7 @@ export class TreeView extends React.PureComponent<
extends React.PureComponent<
this._toggle(nodeId, newExpanded, true);
}
- _select(nodeId: NodeIndex) {
- this.props.onSelectionChange(nodeId);
+ _selectWithMouse(nodeId: NodeIndex) {
+ this.props.onSelectionChange(nodeId, { source: 'pointer' });
}
_rightClickSelect(nodeId: NodeIndex) {
if (this.props.onRightClickSelection) {
this.props.onRightClickSelection(nodeId);
} else {
- this._select(nodeId);
+ this._selectWithMouse(nodeId);
}
}
_onRowClicked = (nodeId: NodeIndex, event: SyntheticMouseEvent<>) => {
if (event.button === 0) {
- this._select(nodeId);
+ this._selectWithMouse(nodeId);
} else if (event.button === 2) {
this._rightClickSelect(nodeId);
}
@@ -599,6 +793,10 @@ export class TreeView extends React.PureComponent<
}
};
+ _selectWithKeyboard(nodeId: NodeIndex) {
+ this.props.onSelectionChange(nodeId, { source: 'keyboard' });
+ }
+
_onKeyDown = (event: SyntheticKeyboardEvent<>) => {
if (this.props.onKeyDown) {
this.props.onKeyDown(event);
@@ -628,7 +826,7 @@ export class TreeView extends React.PureComponent<
if (selected === null || selectedRowIndex === -1) {
// the first condition is redundant, but it makes flow happy
- this._select(visibleRows[0]);
+ this._selectWithKeyboard(visibleRows[0]);
return;
}
@@ -637,31 +835,31 @@ export class TreeView extends React.PureComponent<
case 'ArrowUp': {
if (event.metaKey) {
// On MacOS this is a common shortcut for the Home gesture
- this._select(visibleRows[0]);
+ this._selectWithKeyboard(visibleRows[0]);
break;
}
if (selectedRowIndex > 0) {
- this._select(visibleRows[selectedRowIndex - 1]);
+ this._selectWithKeyboard(visibleRows[selectedRowIndex - 1]);
}
break;
}
case 'ArrowDown': {
if (event.metaKey) {
// On MacOS this is a common shortcut for the End gesture
- this._select(visibleRows[visibleRows.length - 1]);
+ this._selectWithKeyboard(visibleRows[visibleRows.length - 1]);
break;
}
if (selectedRowIndex < visibleRows.length - 1) {
- this._select(visibleRows[selectedRowIndex + 1]);
+ this._selectWithKeyboard(visibleRows[selectedRowIndex + 1]);
}
break;
}
case 'PageUp': {
if (selectedRowIndex > 0) {
const nextRow = Math.max(0, selectedRowIndex - PAGE_KEYS_DELTA);
- this._select(visibleRows[nextRow]);
+ this._selectWithKeyboard(visibleRows[nextRow]);
}
break;
}
@@ -671,16 +869,16 @@ export class TreeView extends React.PureComponent<
visibleRows.length - 1,
selectedRowIndex + PAGE_KEYS_DELTA
);
- this._select(visibleRows[nextRow]);
+ this._selectWithKeyboard(visibleRows[nextRow]);
}
break;
}
case 'Home': {
- this._select(visibleRows[0]);
+ this._selectWithKeyboard(visibleRows[0]);
break;
}
case 'End': {
- this._select(visibleRows[visibleRows.length - 1]);
+ this._selectWithKeyboard(visibleRows[visibleRows.length - 1]);
break;
}
case 'ArrowLeft': {
@@ -690,7 +888,7 @@ export class TreeView extends React.PureComponent<
} else {
const parent = this.props.tree.getParent(selected);
if (parent !== -1) {
- this._select(parent);
+ this._selectWithKeyboard(parent);
}
}
break;
@@ -702,7 +900,9 @@ export class TreeView extends React.PureComponent<
} else {
// Do KEY_DOWN only if the next element is a child
if (this.props.tree.hasChildren(selected)) {
- this._select(this.props.tree.getChildren(selected)[0]);
+ this._selectWithKeyboard(
+ this.props.tree.getChildren(selected)[0]
+ );
}
}
break;
@@ -743,9 +943,16 @@ export class TreeView extends React.PureComponent<
rowHeight,
selectedNodeId,
} = this.props;
+ const { isResizingColumns } = this.state;
return (
-
-
+
+
{contextMenu}
diff --git a/src/components/shared/thread/ActivityGraph.js b/src/components/shared/thread/ActivityGraph.js
index e9ab1cd886..b0794fbb6d 100644
--- a/src/components/shared/thread/ActivityGraph.js
+++ b/src/components/shared/thread/ActivityGraph.js
@@ -44,7 +44,7 @@ export type Props = {|
+sampleIndexOffset: number,
+onSampleClick: (
event: SyntheticMouseEvent<>,
- sampleIndex: IndexIntoSamplesTable
+ sampleIndex: IndexIntoSamplesTable | null
) => void,
+categories: CategoryList,
+samplesSelectedStates: null | SelectedState[],
@@ -145,9 +145,7 @@ class ThreadActivityGraphImpl extends React.PureComponent
{
_onClick = (event: SyntheticMouseEvent) => {
const sampleState = this._getSampleAtMouseEvent(event);
- if (sampleState !== null) {
- this.props.onSampleClick(event, sampleState.sample);
- }
+ this.props.onSampleClick(event, sampleState ? sampleState.sample : null);
};
_takeContainerRef = (el: HTMLElement | null) => {
diff --git a/src/components/shared/thread/ActivityGraphFills.js b/src/components/shared/thread/ActivityGraphFills.js
index 6f0f785deb..82e52a2acc 100644
--- a/src/components/shared/thread/ActivityGraphFills.js
+++ b/src/components/shared/thread/ActivityGraphFills.js
@@ -405,7 +405,6 @@ export class ActivityFillGraphQuerier {
): HoveredPixelState | null {
const {
rangeFilteredThread: { samples, stackTable },
- greyCategoryIndex,
} = this.renderedComponentSettings;
const { devicePixelRatio } = window;
const deviceX = Math.round(cssX * devicePixelRatio);
@@ -437,10 +436,13 @@ export class ActivityFillGraphQuerier {
// iteration - in the first iteration, yPercentage can be == categoryLowerEdge.)
for (const { sample, contribution } of candidateSamples) {
const stackIndex = samples.stack[sample];
- const sampleCategory =
- stackIndex !== null
- ? stackTable.category[stackIndex]
- : greyCategoryIndex;
+ if (stackIndex === null) {
+ console.error(
+ `Stack index was null for sample index ${sample}, this shouldn't happen normally, please fix your source of data.`
+ );
+ continue;
+ }
+ const sampleCategory = stackTable.category[stackIndex];
const upperEdgeOfThisSample = upperEdgeOfPreviousSample + contribution;
// Checking the sample category here because there are samples with different
// categories that has y percentage is lower than the upperEdgeOfThisSample.
diff --git a/src/components/stack-chart/index.js b/src/components/stack-chart/index.js
index c5d6637ade..4b1d7e5e9c 100644
--- a/src/components/stack-chart/index.js
+++ b/src/components/stack-chart/index.js
@@ -158,10 +158,33 @@ class StackChartImpl extends React.PureComponent {
handleCallNodeTransformShortcut(event, threadsKey, nodeIndex);
};
+ _onCopy = (event: ClipboardEvent) => {
+ if (document.activeElement === this._viewport) {
+ event.preventDefault();
+ const {
+ callNodeInfo: { callNodeTable },
+ selectedCallNodeIndex,
+ thread,
+ } = this.props;
+ if (selectedCallNodeIndex !== null) {
+ const funcIndex = callNodeTable.func[selectedCallNodeIndex];
+ const funcName = thread.stringTable.getString(
+ thread.funcTable.name[funcIndex]
+ );
+ event.clipboardData.setData('text/plain', funcName);
+ }
+ }
+ };
+
componentDidMount() {
+ document.addEventListener('copy', this._onCopy, false);
this._focusViewport();
}
+ componentWillUnmount() {
+ document.removeEventListener('copy', this._onCopy, false);
+ }
+
render() {
const {
thread,
@@ -192,7 +215,6 @@ class StackChartImpl extends React.PureComponent {
id="stack-chart-tab"
role="tabpanel"
aria-labelledby="stack-chart-tab-button"
- onKeyDown={this._handleKeyDown}
>
@@ -205,7 +227,7 @@ class StackChartImpl extends React.PureComponent {
className: 'treeViewContextMenu',
}}
>
-
+
,
+|};
+
type StateProps = {|
+committedRange: StartEndRange,
+globalTracks: ActiveTabGlobalTrack[],
@@ -43,7 +48,7 @@ type StateProps = {|
type Props = {|
...SizeProps,
- ...ConnectedProps<{||}, StateProps, {||}>,
+ ...ConnectedProps,
|};
type State = {|
@@ -85,6 +90,7 @@ class ActiveTabTimelineImpl extends React.PureComponent {
panelLayoutGeneration,
globalTracks,
globalTrackReferences,
+ innerElementRef,
} = this.props;
return (
@@ -102,7 +108,7 @@ class ActiveTabTimelineImpl extends React.PureComponent {
initialSelected={this.state.initialSelected}
forceLayoutGeneration={this.state.forceLayoutGeneration}
>
-
+
{globalTracks.map((globalTrack, trackIndex) => (
{
}
}
-export const ActiveTabTimeline = explicitConnect<{||}, StateProps, {||}>({
+export const ActiveTabTimeline = explicitConnect({
mapStateToProps: (state) => ({
globalTracks: getActiveTabGlobalTracks(state),
globalTrackReferences: getActiveTabGlobalTrackReferences(state),
diff --git a/src/components/timeline/FullTimeline.js b/src/components/timeline/FullTimeline.js
index afe545fe90..f9ee35a5a0 100644
--- a/src/components/timeline/FullTimeline.js
+++ b/src/components/timeline/FullTimeline.js
@@ -47,6 +47,11 @@ import type {
import type { ConnectedProps } from 'firefox-profiler/utils/connect';
+type OwnProps = {|
+ // This ref will be added to the inner container.
+ +innerElementRef?: React.Ref,
+|};
+
type StateProps = {|
+committedRange: StartEndRange,
+globalTracks: GlobalTrack[],
@@ -64,7 +69,7 @@ type DispatchProps = {|
type Props = {|
...SizeProps,
- ...ConnectedProps<{||}, StateProps, DispatchProps>,
+ ...ConnectedProps,
|};
type State = {|
@@ -144,6 +149,7 @@ class FullTimelineImpl extends React.PureComponent {
panelLayoutGeneration,
hiddenTrackCount,
changeRightClickedTrack,
+ innerElementRef,
} = this.props;
return (
@@ -173,6 +179,7 @@ class FullTimelineImpl extends React.PureComponent {
order={globalTrackOrder}
orient="vertical"
onChangeOrder={changeGlobalTrackOrder}
+ innerElementRef={innerElementRef}
>
{globalTracks.map((globalTrack, trackIndex) => (
{
}
}
-export const FullTimeline = explicitConnect<{||}, StateProps, DispatchProps>({
+export const FullTimeline = explicitConnect<
+ OwnProps,
+ StateProps,
+ DispatchProps
+>({
mapStateToProps: (state) => ({
globalTracks: getGlobalTracks(state),
globalTrackOrder: getGlobalTrackOrder(state),
diff --git a/src/components/timeline/OriginsTimeline.js b/src/components/timeline/OriginsTimeline.js
index b87d6f954f..fe3cba7c36 100644
--- a/src/components/timeline/OriginsTimeline.js
+++ b/src/components/timeline/OriginsTimeline.js
@@ -37,6 +37,11 @@ import type { ConnectedProps } from 'firefox-profiler/utils/connect';
import './OriginsTimeline.css';
+type OwnProps = {|
+ // This ref will be added to the inner container.
+ +innerElementRef?: React.Ref,
+|};
+
type StateProps = {|
+committedRange: StartEndRange,
+panelLayoutGeneration: number,
@@ -51,7 +56,7 @@ type DispatchProps = {|
type Props = {|
...SizeProps,
- ...ConnectedProps<{||}, StateProps, DispatchProps>,
+ ...ConnectedProps,
|};
type State = {|
@@ -145,6 +150,7 @@ class OriginsTimelineView extends React.PureComponent {
width,
panelLayoutGeneration,
originsTimeline,
+ innerElementRef,
} = this.props;
return (
@@ -161,7 +167,7 @@ class OriginsTimelineView extends React.PureComponent {
panelLayoutGeneration={panelLayoutGeneration}
initialSelected={this.state.initialSelected}
>
-
+
{originsTimeline.map(this.renderTrack)}
@@ -171,18 +177,20 @@ class OriginsTimelineView extends React.PureComponent {
}
}
-export const TimelineOrigins = explicitConnect<{||}, StateProps, DispatchProps>(
- {
- mapStateToProps: (state) => ({
- threads: getThreads(state),
- committedRange: getCommittedRange(state),
- zeroAt: getZeroAt(state),
- panelLayoutGeneration: getPanelLayoutGeneration(state),
- originsTimeline: getOriginsTimeline(state),
- }),
- mapDispatchToProps: {
- changeSelectedThreads,
- },
- component: withSize(OriginsTimelineView),
- }
-);
+export const TimelineOrigins = explicitConnect<
+ OwnProps,
+ StateProps,
+ DispatchProps
+>({
+ mapStateToProps: (state) => ({
+ threads: getThreads(state),
+ committedRange: getCommittedRange(state),
+ zeroAt: getZeroAt(state),
+ panelLayoutGeneration: getPanelLayoutGeneration(state),
+ originsTimeline: getOriginsTimeline(state),
+ }),
+ mapDispatchToProps: {
+ changeSelectedThreads,
+ },
+ component: withSize(OriginsTimelineView),
+});
diff --git a/src/components/timeline/Selection.js b/src/components/timeline/Selection.js
index e04a815660..44c447f836 100644
--- a/src/components/timeline/Selection.js
+++ b/src/components/timeline/Selection.js
@@ -11,10 +11,12 @@ import {
getPreviewSelection,
getCommittedRange,
getZeroAt,
+ getMouseTimePosition,
} from 'firefox-profiler/selectors/profile';
import {
updatePreviewSelection,
commitRange,
+ changeMouseTimePosition,
} from 'firefox-profiler/actions/profile-view';
import explicitConnect from 'firefox-profiler/utils/connect';
import classNames from 'classnames';
@@ -43,20 +45,18 @@ type StateProps = {|
+previewSelection: PreviewSelection,
+committedRange: StartEndRange,
+zeroAt: Milliseconds,
+ +mouseTimePosition: Milliseconds | null,
|};
type DispatchProps = {|
+commitRange: typeof commitRange,
+updatePreviewSelection: typeof updatePreviewSelection,
+ +changeMouseTimePosition: typeof changeMouseTimePosition,
|};
type Props = ConnectedProps;
-type State = {|
- hoverLocation: null | CssPixels,
-|};
-
-class TimelineRulerAndSelection extends React.PureComponent {
+class TimelineRulerAndSelection extends React.PureComponent {
_handlers: ?{|
mouseMoveHandler: MouseHandler,
mouseClickHandler: MouseHandler,
@@ -64,10 +64,6 @@ class TimelineRulerAndSelection extends React.PureComponent {
_container: ?HTMLElement;
- state = {
- hoverLocation: null,
- };
-
_containerCreated = (element: HTMLElement | null) => {
this._container = element;
};
@@ -246,6 +242,7 @@ class TimelineRulerAndSelection extends React.PureComponent {
if (!this._container) {
return;
}
+ const { width, committedRange, changeMouseTimePosition } = this.props;
const rect = getContentRect(this._container);
if (
@@ -254,9 +251,15 @@ class TimelineRulerAndSelection extends React.PureComponent {
event.pageY < rect.top ||
event.pageY >= rect.bottom
) {
- this.setState({ hoverLocation: null });
+ changeMouseTimePosition(null);
} else {
- this.setState({ hoverLocation: event.pageX - rect.left });
+ const hoverPositionInPixels = event.pageX - rect.left;
+ const pixelsToMouseTimePosition = Math.round(
+ ((committedRange.end - committedRange.start) * hoverPositionInPixels) /
+ width +
+ committedRange.start
+ );
+ changeMouseTimePosition(pixelsToMouseTimePosition);
}
};
@@ -386,8 +389,23 @@ class TimelineRulerAndSelection extends React.PureComponent {
}
render() {
- const { children, previewSelection, className } = this.props;
- const { hoverLocation } = this.state;
+ const {
+ children,
+ previewSelection,
+ className,
+ mouseTimePosition,
+ width,
+ committedRange,
+ } = this.props;
+
+ let hoverLocation = null;
+
+ if (mouseTimePosition !== null) {
+ // If the mouseTimePosition exists, convert it to CssPixels.
+ hoverLocation =
+ (width * (mouseTimePosition - committedRange.start)) /
+ (committedRange.end - committedRange.start);
+ }
return (
{
*/
_onSampleClick = (
event: SyntheticMouseEvent<>,
- sampleIndex: IndexIntoSamplesTable
+ sampleIndex: IndexIntoSamplesTable | null
) => {
const modifiers = getTrackSelectionModifiers(event);
if (modifiers.ctrlOrMeta || modifiers.shift) {
diff --git a/src/components/timeline/TrackVisualProgressGraph.js b/src/components/timeline/TrackVisualProgressGraph.js
index decbb0422c..41f6cde3d0 100644
--- a/src/components/timeline/TrackVisualProgressGraph.js
+++ b/src/components/timeline/TrackVisualProgressGraph.js
@@ -102,7 +102,7 @@ class TrackVisualProgressCanvas extends React.PureComponent
{
// Create a path for the top of the chart. This is the line that will have
// a stroke applied to it.
x =
- (deviceWidth * (progressGraphData[i].timestamp - rangeStart)) /
+ (deviceWidth * ((progressGraphData[i].timestamp ?? 0) - rangeStart)) /
rangeLength;
// Add on half the stroke's line width so that it won't be cut off the edge
// of the graph.
@@ -133,7 +133,7 @@ class TrackVisualProgressCanvas extends React.PureComponent {
// of the canvas.
ctx.lineTo(x + interval, deviceHeight);
ctx.lineTo(
- (deviceWidth * (progressGraphData[0].timestamp - rangeStart)) /
+ (deviceWidth * ((progressGraphData[0].timestamp ?? 0) - rangeStart)) /
rangeLength +
interval,
deviceHeight
@@ -221,15 +221,16 @@ class TrackVisualProgressGraphImpl extends React.PureComponent {
const rangeLength = rangeEnd - rangeStart;
const timeAtMouse = rangeStart + ((mouseX - left) / width) * rangeLength;
if (
- timeAtMouse < progressGraphData[0].timestamp ||
+ timeAtMouse < (progressGraphData[0].timestamp ?? 0) ||
timeAtMouse >
- progressGraphData[progressGraphData.length - 1].timestamp + interval
+ (progressGraphData[progressGraphData.length - 1].timestamp ?? 0) +
+ interval
) {
// We are outside the range of the samples, do not display hover information.
this.setState({ hoveredVisualProgress: null });
} else {
const graphTimestamps = progressGraphData.map(
- ({ timestamp }) => timestamp
+ ({ timestamp }) => timestamp ?? 0
);
let hoveredVisualProgress = bisectionRight(graphTimestamps, timeAtMouse);
if (hoveredVisualProgress === progressGraphData.length) {
@@ -277,7 +278,8 @@ class TrackVisualProgressGraphImpl extends React.PureComponent {
} = this.props;
const rangeLength = rangeEnd - rangeStart;
const left =
- (width * (progressGraphData[graphDataIndex].timestamp - rangeStart)) /
+ (width *
+ ((progressGraphData[graphDataIndex].timestamp ?? 0) - rangeStart)) /
rangeLength;
const unitSampleCount = progressGraphData[graphDataIndex].percent / 100;
diff --git a/src/components/timeline/index.js b/src/components/timeline/index.js
index 19965139d9..3f342538d5 100644
--- a/src/components/timeline/index.js
+++ b/src/components/timeline/index.js
@@ -22,15 +22,76 @@ type StateProps = {|
type Props = ConnectedProps<{||}, StateProps, {||}>;
class TimelineImpl extends React.PureComponent {
+ // This may contain a function that's called whenever we want to remove the
+ // "wheel" listener.
+ _removeWheelListener: null | (() => mixed) = null;
+
+ // This effectively disable the pinch-to-zoom as well as ctrl+mousewheel
+ // gestures. Indeed in the timeline it is confusing. In the future we'll want
+ // to couple this with the preview selection like in the Viewport HOC.
+ preventPinchToZoom(e: WheelEvent) {
+ if (e.ctrlKey) {
+ e.preventDefault();
+ }
+ }
+
+ // This will be registered as a ref property to the DOM element displaying the
+ // tracks. We use this solution of registering the wheel event in this ref
+ // listener because:
+ // * we can't use React's event handling, because it doesn't allow us to use
+ // the "passive: false" way of registering the event handler. But we need this
+ // if we want to be able to prevent the default action of page zooming.
+ // * we want to be sure to register it whenever the element changes.
+ _onTimelineMountWithRef = (ref: HTMLElement | null) => {
+ if (this._removeWheelListener) {
+ this._removeWheelListener();
+ this._removeWheelListener = null;
+ }
+
+ if (!ref) {
+ return;
+ }
+
+ // without pinning to a const variable, Flow isn't sure that we don't change
+ // the `ref` variable in some of the function calls below, and therefore
+ // that it won't be null.
+ const existingRef = ref;
+
+ // Disable pinch-to-zoom and ctrl + wheel otherwise, on the timeline.
+ // Indeed the users are used to this gesture to zoom in in our charts, and
+ // may use the same gesture elsewhere because of their habits, however this
+ // doesn't do what they expect and instead zooms in the page, which is distracting.
+ existingRef.addEventListener('wheel', this.preventPinchToZoom, {
+ passive: false,
+ });
+
+ this._removeWheelListener = () => {
+ existingRef.removeEventListener('wheel', this.preventPinchToZoom, {
+ passive: false,
+ });
+ };
+ };
+
+ componentWillUnmount() {
+ if (this._removeWheelListener) {
+ this._removeWheelListener();
+ this._removeWheelListener = null;
+ }
+ }
+
render() {
const { timelineTrackOrganization } = this.props;
switch (timelineTrackOrganization.type) {
case 'full':
- return ;
+ return ;
case 'active-tab':
- return ;
+ return (
+
+ );
case 'origins':
- return ;
+ return (
+
+ );
default:
throw assertExhaustiveCheck(
timelineTrackOrganization,
diff --git a/src/components/tooltip/Marker.js b/src/components/tooltip/Marker.js
index 672a5cb391..bf8f4a5850 100644
--- a/src/components/tooltip/Marker.js
+++ b/src/components/tooltip/Marker.js
@@ -230,7 +230,11 @@ class MarkerTooltipContents extends React.PureComponent {
if (data) {
// Add the details for the markers based on their Marker schema.
- const schema = getSchemaFromMarker(markerSchemaByName, marker);
+ const schema = getSchemaFromMarker(
+ markerSchemaByName,
+ marker.name,
+ marker.data
+ );
if (schema) {
for (const schemaData of schema.data) {
// Check for a schema that is looking up and formatting a value from
@@ -410,7 +414,7 @@ class MarkerTooltipContents extends React.PureComponent {
const { data, start } = marker;
if (data && 'cause' in data && data.cause) {
const { cause } = data;
- const causeAge = start - cause.time;
+ const causeAge = cause.time !== undefined ? start - cause.time : 0;
return [
,
diff --git a/src/components/tooltip/TrackPower.js b/src/components/tooltip/TrackPower.js
index cf7ee8fe71..27bbdb3537 100644
--- a/src/components/tooltip/TrackPower.js
+++ b/src/components/tooltip/TrackPower.js
@@ -8,6 +8,8 @@ import * as React from 'react';
import { Localized } from '@fluent/react';
import memoize from 'memoize-one';
+import { averageIntensity } from '@tgwf/co2';
+
import explicitConnect from 'firefox-profiler/utils/connect';
import { formatNumber } from 'firefox-profiler/utils/format-numbers';
import {
@@ -63,6 +65,13 @@ class TooltipTrackPowerImpl extends React.PureComponent {
return sum * 1e-12;
}
+ _computeCO2eFromPower(power: number): number {
+ // total energy Wh to kWh
+ const energy = power / 1000;
+ const { WORLD } = averageIntensity.data;
+ return energy * WORLD;
+ }
+
_computePowerSumForCommittedRange = memoize(
({ start, end }: StartEndRange): number =>
this._computePowerSumForRange(start, end)
@@ -85,23 +94,34 @@ class TooltipTrackPowerImpl extends React.PureComponent {
l10nIdMilliUnit,
l10nIdMicroUnit
): Localized {
- let value, l10nId;
+ let value, l10nId, carbonValue;
+ const carbon = this._computeCO2eFromPower(power);
if (power > 1) {
value = formatNumber(power, 3);
+ carbonValue = formatNumber(carbon, 3);
l10nId = l10nIdUnit;
} else if (power === 0) {
value = 0;
+ carbonValue = 0;
l10nId = l10nIdUnit;
} else if (power < 0.001 && l10nIdMicroUnit) {
value = formatNumber(power * 1000000);
+ // Note: even though the power value is expressed in µWh, the carbon value
+ // is still expressed in mg.
+ carbonValue = formatNumber(carbon * 1000);
l10nId = l10nIdMicroUnit;
} else {
value = formatNumber(power * 1000);
+ carbonValue = formatNumber(carbon * 1000);
l10nId = l10nIdMilliUnit;
}
return (
-
+
{value}
);
@@ -139,16 +159,16 @@ class TooltipTrackPowerImpl extends React.PureComponent {
{previewSelection.hasSelection
? this._formatPowerValue(
this._computePowerSumForPreviewRange(previewSelection),
- 'TrackPower--tooltip-energy-used-in-preview-watthour',
- 'TrackPower--tooltip-energy-used-in-preview-milliwatthour',
- 'TrackPower--tooltip-energy-used-in-preview-microwatthour'
+ 'TrackPower--tooltip-energy-carbon-used-in-preview-watthour',
+ 'TrackPower--tooltip-energy-carbon-used-in-preview-milliwatthour',
+ 'TrackPower--tooltip-energy-carbon-used-in-preview-microwatthour'
)
: null}
{this._formatPowerValue(
this._computePowerSumForCommittedRange(committedRange),
- 'TrackPower--tooltip-energy-used-in-range-watthour',
- 'TrackPower--tooltip-energy-used-in-range-milliwatthour',
- 'TrackPower--tooltip-energy-used-in-range-microwatthour'
+ 'TrackPower--tooltip-energy-carbon-used-in-range-watthour',
+ 'TrackPower--tooltip-energy-carbon-used-in-range-milliwatthour',
+ 'TrackPower--tooltip-energy-carbon-used-in-range-microwatthour'
)}
diff --git a/src/profile-logic/import/chrome.js b/src/profile-logic/import/chrome.js
index f9bcc6e735..92b874ae31 100644
--- a/src/profile-logic/import/chrome.js
+++ b/src/profile-logic/import/chrome.js
@@ -571,9 +571,20 @@ async function processTracingEvents(
if (lineNumber === -1) {
lineNumber = undefined;
}
+
if (columnNumber === -1) {
columnNumber = undefined;
}
+
+ // Line and column number are zero-based in chrome profiles, but
+ // 1-based in the firefox profiler.
+ if (lineNumber !== undefined) {
+ lineNumber++;
+ }
+ if (columnNumber !== undefined) {
+ columnNumber++;
+ }
+
if (url === '') {
url = undefined;
}
diff --git a/src/profile-logic/import/linux-perf.js b/src/profile-logic/import/linux-perf.js
index 89f28f01dd..13d6cf6401 100644
--- a/src/profile-logic/import/linux-perf.js
+++ b/src/profile-logic/import/linux-perf.js
@@ -304,7 +304,9 @@ export function convertPerfScriptProfile(
}
// 23fe921 _ZN7mozilla3ipc14MessageChannel11MessageTask3RunEv (/home/mstange/Desktop/firefox/libxul.so)
- const stackFrameMatch = /^\s*(\w+)\s*(.+) \((\S*)\)/.exec(stackFrameLine);
+ const stackFrameMatch = /^\s*(\w+)\s*(.+) \(([^)]*)\)/.exec(
+ stackFrameLine
+ );
if (stackFrameMatch) {
// const pc = stackFrameMatch[1];
let rawFunc = stackFrameMatch[2];
diff --git a/src/profile-logic/marker-data.js b/src/profile-logic/marker-data.js
index 7887d3628a..4ea8a869f6 100644
--- a/src/profile-logic/marker-data.js
+++ b/src/profile-logic/marker-data.js
@@ -35,7 +35,6 @@ import type {
IPCMarkerPayload,
NetworkPayload,
PrefMarkerPayload,
- FileIoPayload,
TextMarkerPayload,
StartEndRange,
IndexedArray,
@@ -161,7 +160,11 @@ export function getSearchFilteredMarkerIndexes(
}
// Now check the schema for the marker payload for searchable
- const markerSchema = getSchemaFromMarker(markerSchemaByName, marker);
+ const markerSchema = getSchemaFromMarker(
+ markerSchemaByName,
+ marker.name,
+ marker.data
+ );
if (
markerSchema &&
markerPayloadMatchesSearch(markerSchema, marker, regExp)
@@ -496,9 +499,9 @@ export function deriveMarkersFromRawMarkerTable(
// In the case of separate markers for the start and end of an interval,
// merge the payloads together, with the end data overriding the start.
function mergeIntervalData(
- startData: MarkerPayload,
- endData: MarkerPayload
- ): MarkerPayload {
+ startData: MarkerPayload | null,
+ endData: MarkerPayload | null
+ ): MarkerPayload | null {
if (!startData && !endData) {
return null;
}
@@ -1264,22 +1267,6 @@ export function removePrefMarkerPreferenceValues(
return { ...payload, prefValue: '' };
}
-/**
- * Sanitize FileIO marker's filename property if it's non-empty.
- */
-export function sanitizeFileIOMarkerFilenamePath(
- payload: FileIoPayload
-): FileIoPayload {
- if (!payload.filename) {
- return payload;
- }
-
- return {
- ...payload,
- filename: removeFilePath(payload.filename),
- };
-}
-
/**
* Sanitize Text marker's name property for potential URLs.
*/
@@ -1316,6 +1303,41 @@ export function sanitizeExtensionTextMarker(
return payload;
}
+export function sanitizeFromMarkerSchema(
+ markerSchema: MarkerSchema,
+ markerPayload: MarkerPayload
+): MarkerPayload {
+ for (const propertyDescription of markerSchema.data) {
+ if (
+ propertyDescription.key !== undefined &&
+ propertyDescription.format !== undefined
+ ) {
+ const key = propertyDescription.key;
+ const format = propertyDescription.format;
+ if (!(key in markerPayload)) {
+ continue;
+ }
+
+ // We're typing the result of the sanitization with `any` because Flow
+ // doesn't like much our enormous enum of non-exact objects that's used as
+ // MarkerPayload type, and this code is too generic for Flow in this context.
+ if (format === 'url') {
+ markerPayload = ({
+ ...markerPayload,
+ [key]: removeURLs(markerPayload[key]),
+ }: any);
+ } else if (format === 'file-path') {
+ markerPayload = ({
+ ...markerPayload,
+ [key]: removeFilePath(markerPayload[key]),
+ }: any);
+ }
+ }
+ }
+
+ return markerPayload;
+}
+
/**
* Markers can be filtered by display area using the marker schema. Get a list of
* marker "types" (the type field in the Payload) for a specific location.
@@ -1362,7 +1384,9 @@ export function filterMarkerByDisplayLocation(
return additionalResult;
}
- return markerTypes.has(getMarkerSchemaName(markerSchemaByName, marker));
+ return markerTypes.has(
+ getMarkerSchemaName(markerSchemaByName, marker.name, marker.data)
+ );
});
}
diff --git a/src/profile-logic/marker-schema.js b/src/profile-logic/marker-schema.js
index f8c8d7c029..01c8d499f4 100644
--- a/src/profile-logic/marker-schema.js
+++ b/src/profile-logic/marker-schema.js
@@ -22,6 +22,7 @@ import type {
MarkerSchemaByName,
Marker,
MarkerIndex,
+ MarkerPayload,
} from 'firefox-profiler/types';
/**
@@ -86,34 +87,33 @@ export const markerSchemaFrontEndOnly: MarkerSchema[] = [
*/
export function getMarkerSchemaName(
markerSchemaByName: MarkerSchemaByName,
- marker: Marker
+ markerName: string,
+ markerData: MarkerPayload | null
): string {
- const { data, name } = marker;
- // Fall back to using the name if no payload exists.
-
- if (data) {
- const { type } = data;
- if (type === 'tracing' && data.category) {
+ if (markerData) {
+ const { type } = markerData;
+ if (type === 'tracing' && markerData.category) {
// TODO - Tracing markers have a duplicate "category" field.
// See issue #2749
// Does a marker schema for the "category" exist?
- return markerSchemaByName[data.category] === undefined
+ return markerSchemaByName[markerData.category] === undefined
? // If not, default back to tracing
'tracing'
: // If so, use the category as the schema name.
- data.category;
+ markerData.category;
}
if (type === 'Text') {
// Text markers are a cheap and easy way to create markers with
// a category. Check for schema if it exists, if not, fallback to
// a Text type marker.
- return markerSchemaByName[name] === undefined ? 'Text' : name;
+ return markerSchemaByName[markerName] === undefined ? 'Text' : markerName;
}
- return data.type;
+ return markerData.type;
}
- return name;
+ // Fall back to using the name if no payload exists.
+ return markerName;
}
/**
@@ -122,10 +122,13 @@ export function getMarkerSchemaName(
*/
export function getSchemaFromMarker(
markerSchemaByName: MarkerSchemaByName,
- marker: Marker
+ markerName: string,
+ markerData: MarkerPayload | null
): MarkerSchema | null {
return (
- markerSchemaByName[getMarkerSchemaName(markerSchemaByName, marker)] || null
+ markerSchemaByName[
+ getMarkerSchemaName(markerSchemaByName, markerName, markerData)
+ ] || null
);
}
@@ -349,7 +352,11 @@ export function getLabelGetter(
// No label exists, it will have to be generated for the first time.
if (label === undefined) {
const marker = getMarker(markerIndex);
- const schemaName = getMarkerSchemaName(markerSchemaByName, marker);
+ const schemaName = getMarkerSchemaName(
+ markerSchemaByName,
+ marker.name,
+ marker.data
+ );
const applyLabel = labelFns.get(schemaName);
label = applyLabel
diff --git a/src/profile-logic/merge-compare.js b/src/profile-logic/merge-compare.js
index 2355652599..90c34b3bce 100644
--- a/src/profile-logic/merge-compare.js
+++ b/src/profile-logic/merge-compare.js
@@ -265,6 +265,12 @@ export function mergeProfilesForDiffing(
);
}
+ // In merged profiles, we don't want to hide any threads: either they've been
+ // explicitely selected by the user, or it's the diffing track.
+ resultProfile.meta.initialVisibleThreads = resultProfile.threads.map(
+ (_, i) => i
+ );
+
return { profile: resultProfile, implementationFilters, transformStacks };
}
diff --git a/src/profile-logic/process-profile.js b/src/profile-logic/process-profile.js
index 53add8a524..3a57062606 100644
--- a/src/profile-logic/process-profile.js
+++ b/src/profile-logic/process-profile.js
@@ -28,6 +28,7 @@ import { isArtTraceFormat, convertArtTraceProfile } from './import/art-trace';
import {
PROCESSED_PROFILE_VERSION,
INTERVAL,
+ INTERVAL_END,
INSTANT,
} from '../app-logic/constants';
import {
@@ -87,6 +88,7 @@ import type {
PageList,
ThreadIndex,
BrowsertimeMarkerPayload,
+ MarkerPhase,
} from 'firefox-profiler/types';
type RegExpResult = null | string[];
@@ -661,13 +663,16 @@ function _processStackTable(
* synchronous stack. Otherwise, if it happened before, it was an async stack, and is
* most likely some event that happened in the past that triggered the marker.
*/
-function _convertStackToCause(data: any): any {
+function _convertStackToCause(data: MarkerPayload_Gecko) {
if ('stack' in data && data.stack && data.stack.samples.data.length > 0) {
const { stack, ...newData } = data;
const stackIndex = stack.samples.data[0][stack.samples.schema.stack];
const time = stack.samples.data[0][stack.samples.schema.time];
if (stackIndex !== null) {
- newData.cause = { tid: stack.tid, time, stack: stackIndex };
+ return {
+ ...newData,
+ cause: { tid: stack.tid, time, stack: stackIndex },
+ };
}
return newData;
}
@@ -679,7 +684,7 @@ function _convertStackToCause(data: any): any {
* from a gecko payload.
*/
function _convertPayloadStackToIndex(
- data: MarkerPayload_Gecko
+ data: MarkerPayload_Gecko | null
): IndexIntoStackTable | null {
if (!data) {
return null;
@@ -714,7 +719,7 @@ function _processMarkers(geckoMarkers: GeckoMarkerStruct): {|
let hasMemoryAddresses;
for (let markerIndex = 0; markerIndex < geckoMarkers.length; markerIndex++) {
- const geckoPayload: MarkerPayload_Gecko = geckoMarkers.data[markerIndex];
+ const geckoPayload = geckoMarkers.data[markerIndex];
if (geckoPayload) {
switch (geckoPayload.type) {
@@ -844,8 +849,8 @@ function convertPhaseTimes(
* the GC information.
*/
function _processMarkerPayload(
- geckoPayload: MarkerPayload_Gecko
-): MarkerPayload {
+ geckoPayload: MarkerPayload_Gecko | null
+): MarkerPayload | null {
if (!geckoPayload) {
return null;
}
@@ -904,10 +909,14 @@ function _processMarkerPayload(
}
}
default:
- // Coerce the payload into a MarkerPayload. This doesn't really provide
- // any more type safety, but it shows the intent of going from an object
- // without much type safety, to a specific type definition.
- return (payload: MarkerPayload);
+ // `payload` is currently typed as the result of _convertStackToCause, which
+ // is MarkerPayload_Gecko where `stack` has been replaced with `cause`. This
+ // should be reasonably close to `MarkerPayload`, but Flow doesn't work well
+ // with our MarkerPayload type. So we're coerce this return value to `any`
+ // here, and then to `MarkerPayload` as the return value for this function.
+ // This doesn't provide type safety but it shows the intent of going from an
+ // object without much type safety, to a specific type definition.
+ return (payload: any);
}
}
@@ -1278,7 +1287,7 @@ export function adjustMarkerTimestamps(
if (typeof newData.endTime === 'number') {
newData.endTime += delta;
}
- if (newData.cause) {
+ if (newData.cause && newData.cause.time !== undefined) {
newData.cause.time += delta;
}
if (newData.type === 'Network') {
@@ -1729,18 +1738,30 @@ export function processVisualMetrics(
(cat) => cat.name === 'Test'
);
- function addMetricMarker(
+ function maybeAddMetricMarker(
thread: Thread,
name: string,
- startTime: number,
- endTime?: number,
+ phase: MarkerPhase,
+ startTime: number | null,
+ endTime: number | null,
payload?: BrowsertimeMarkerPayload
) {
+ if (
+ // All phases except INTERVAL_END should have a start time.
+ (phase !== INTERVAL_END && startTime === null) ||
+ // Only INTERVAL and INTERVAL_END should have an end time.
+ ((phase === INTERVAL || phase === INTERVAL_END) && endTime === null)
+ ) {
+ // Do not add if some timestamps we expect are missing.
+ // This should ideally never happen but timestamps could be null due to
+ // browsertime bug here: https://github.com/sitespeedio/browsertime/issues/1746.
+ return;
+ }
// Add the marker to the given thread.
thread.markers.name.push(thread.stringTable.indexForString(name));
thread.markers.startTime.push(startTime);
- thread.markers.endTime.push(endTime ? endTime : 0);
- thread.markers.phase.push(endTime ? INTERVAL : INSTANT);
+ thread.markers.endTime.push(endTime);
+ thread.markers.phase.push(phase);
thread.markers.category.push(testingCategoryIdx);
thread.markers.data.push(payload ?? null);
thread.markers.length++;
@@ -1776,9 +1797,9 @@ export function processVisualMetrics(
// Add the progress marker to the parent process main thread.
const markerName = `${metricName} Progress`;
- addMetricMarker(mainThread, markerName, startTime, endTime);
+ maybeAddMetricMarker(mainThread, markerName, INTERVAL, startTime, endTime);
// Add the progress marker to the tab process main thread.
- addMetricMarker(tabThread, markerName, startTime, endTime);
+ maybeAddMetricMarker(tabThread, markerName, INTERVAL, startTime, endTime);
// Add progress markers for every visual progress change for more fine grained information.
const progressMarkerSchema = {
@@ -1798,19 +1819,21 @@ export function processVisualMetrics(
};
// Add it to the parent process main thread.
- addMetricMarker(
+ maybeAddMetricMarker(
mainThread,
changeMarkerName,
+ INSTANT,
timestamp,
- undefined, // endTime
+ null, // endTime
payload
);
// Add it to the tab process main thread.
- addMetricMarker(
+ maybeAddMetricMarker(
tabThread,
changeMarkerName,
+ INSTANT,
timestamp,
- undefined, // endTime
+ null, // endTime
payload
);
}
diff --git a/src/profile-logic/processed-profile-versioning.js b/src/profile-logic/processed-profile-versioning.js
index bfc5f0c397..9caf9be846 100644
--- a/src/profile-logic/processed-profile-versioning.js
+++ b/src/profile-logic/processed-profile-versioning.js
@@ -1715,7 +1715,8 @@ const _upgraders = {
data &&
data.type !== 'tracing' &&
data.type !== 'Styles' &&
- data.cause
+ data.cause &&
+ data.cause.time !== undefined
) {
data.cause.time += delta;
}
diff --git a/src/profile-logic/profile-data.js b/src/profile-logic/profile-data.js
index 992b8f0a3c..78001b7e8b 100644
--- a/src/profile-logic/profile-data.js
+++ b/src/profile-logic/profile-data.js
@@ -2447,124 +2447,6 @@ export function getTreeOrderComparator(
};
}
-/**
- * This is the root-most call node for which, if selected, only the clicked category
- * is highlighted in the thread activity graph. In other words, it's the root-most call
- * node which only 'contains' samples whose sample category is the clicked category.
- */
-export function findBestAncestorCallNode(
- callNodeInfo: CallNodeInfo,
- sampleCallNodes: Array,
- sampleCategories: Array,
- clickedCallNode: IndexIntoCallNodeTable,
- clickedCategory: IndexIntoCategoryList
-): IndexIntoCallNodeTable {
- const { callNodeTable } = callNodeInfo;
- if (callNodeTable.category[clickedCallNode] !== clickedCategory) {
- return clickedCallNode;
- }
-
- // Compute the callNodesOnSameCategoryPath.
- // Given a call node path with some arbitrary categories, e.g. A, B, C
- //
- // Categories: A -> A -> B -> B -> C -> C -> C
- // Node Indexes: 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6
-
- // This loop will select the leaf-most call nodes that match the leaf call-node's
- // category. Running the above path through this loop would produce the list:
- //
- // Categories: [C, C, C]
- // Node Indexes: [6, 5, 4] (note the reverse order)
- const callNodesOnSameCategoryPath = [clickedCallNode];
- let callNode = clickedCallNode;
- while (true) {
- const parentCallNode = callNodeTable.prefix[callNode];
- if (parentCallNode === -1) {
- // The entire call path is just clickedCategory.
- return clickedCallNode; // TODO: is this a useful behavior?
- }
- if (callNodeTable.category[parentCallNode] !== clickedCategory) {
- break;
- }
- callNodesOnSameCategoryPath.push(parentCallNode);
- callNode = parentCallNode;
- }
-
- // Now find the callNode in callNodesOnSameCategoryPath with the lowest depth
- // such that selecting it will not highlight any samples whose unfiltered
- // category is different from clickedCategory. If no such callNode exists,
- // return clickedCallNode.
-
- const clickedDepth = callNodeTable.depth[clickedCallNode];
- // The handledCallNodes is used as a Map.
- const handledCallNodes = new Uint8Array(callNodeTable.length);
-
- function limitSameCategoryPathToCommonAncestor(callNode) {
- // The callNode argument is the leaf call node of a sample whose sample category is a
- // different category than clickedCategory. If callNode's ancestor path crosses
- // callNodesOnSameCategoryPath, that implies that callNode would be highlighted
- // if we were to select the root-most node in callNodesOnSameCategoryPath.
- // If that is the case, we need to truncate callNodesOnSameCategoryPath in such
- // a way that the root-most node in that list is no longer an ancestor of callNode.
- const walkUpToDepth =
- clickedDepth - (callNodesOnSameCategoryPath.length - 1);
- let depth = callNodeTable.depth[callNode];
-
- // Go from leaf to root in the call nodes.
- while (depth >= walkUpToDepth) {
- if (handledCallNodes[callNode]) {
- // This call node was already handled. Stop checking.
- return;
- }
- handledCallNodes[callNode] = 1;
- if (depth <= clickedDepth) {
- // This call node's depth is less than the clicked depth, it needs to be
- // checked to see if the call node is in the callNodesOnSameCategoryPath.
- if (callNode === callNodesOnSameCategoryPath[clickedDepth - depth]) {
- // Remove some of the call nodes, as they are not on the same path.
- // This is done by shortening the array length. Keep in mind that this
- // array is in the opposite order of a CallNodePath, with the leaf-most
- // nodes first, and the root-most last.
- callNodesOnSameCategoryPath.length = clickedDepth - depth;
- return;
- }
- }
- callNode = callNodeTable.prefix[callNode];
- depth--;
- }
- }
-
- // Go through every sample and look at each sample's call node.
- for (let sample = 0; sample < sampleCallNodes.length; sample++) {
- if (
- sampleCategories[sample] !== clickedCategory &&
- sampleCallNodes[sample] !== null
- ) {
- // This sample's category is a different one than the one clicked. Make
- // sure to limit the callNodesOnSameCategoryPath to just the call nodes
- // that share the same common ancestor.
- limitSameCategoryPathToCommonAncestor(sampleCallNodes[sample]);
- }
- }
-
- if (callNodesOnSameCategoryPath.length > 0) {
- // The last call node in this list will be the root-most call node that has
- // the same category on the path as the clicked call node.
- return callNodesOnSameCategoryPath[callNodesOnSameCategoryPath.length - 1];
- }
- return clickedCallNode;
-}
-
-/**
- * Look at the leaf-most stack for every sample, and take its category.
- */
-export function getSampleCategories(
- samples: SamplesTable,
- stackTable: StackTable
-): Array {
- return samples.stack.map((s) => (s !== null ? stackTable.category[s] : null));
-}
-
export function getFriendlyStackTypeName(
implementation: StackImplementation
): string {
@@ -3091,8 +2973,8 @@ export function replaceStackReferences(
// Markers
function replaceStackReferenceInMarkerPayload(
- oldData: MarkerPayload
- ): MarkerPayload {
+ oldData: MarkerPayload | null
+ ): MarkerPayload | null {
if (oldData && 'cause' in oldData && oldData.cause) {
// Replace the cause with the right stack index.
// Use (...: any) because our current version of Flow has trouble with
diff --git a/src/profile-logic/sanitize.js b/src/profile-logic/sanitize.js
index 7b0f93e42a..e19f85b770 100644
--- a/src/profile-logic/sanitize.js
+++ b/src/profile-logic/sanitize.js
@@ -14,11 +14,12 @@ import { removeURLs } from '../utils/string';
import {
removeNetworkMarkerURLs,
removePrefMarkerPreferenceValues,
- sanitizeFileIOMarkerFilenamePath,
filterRawMarkerTableToRangeWithMarkersToDelete,
sanitizeExtensionTextMarker,
sanitizeTextMarker,
+ sanitizeFromMarkerSchema,
} from './marker-data';
+import { getSchemaFromMarker } from './marker-schema';
import { filterThreadSamplesToRange } from './profile-data';
import type {
Profile,
@@ -30,6 +31,7 @@ import type {
IndexIntoFrameTable,
IndexIntoFuncTable,
InnerWindowID,
+ MarkerSchemaByName,
} from 'firefox-profiler/types';
export type SanitizeProfileResult = {|
@@ -47,7 +49,8 @@ export type SanitizeProfileResult = {|
export function sanitizePII(
profile: Profile,
derivedMarkerInfoForAllThreads: DerivedMarkerInfo[],
- maybePIIToBeRemoved: RemoveProfileInformation | null
+ maybePIIToBeRemoved: RemoveProfileInformation | null,
+ markerSchemaByName: MarkerSchemaByName
): SanitizeProfileResult {
if (maybePIIToBeRemoved === null) {
// Nothing is sanitized.
@@ -133,7 +136,8 @@ export function sanitizePII(
threadIndex,
PIIToBeRemoved,
windowIdFromPrivateBrowsing,
- windowIdFromActiveTab
+ windowIdFromActiveTab,
+ markerSchemaByName
);
// Filtering out the current thread if it's null.
@@ -227,7 +231,8 @@ function sanitizeThreadPII(
threadIndex: number,
PIIToBeRemoved: RemoveProfileInformation,
windowIdFromPrivateBrowsing: Set,
- windowIdFromActiveTab: Set
+ windowIdFromActiveTab: Set,
+ markerSchemaByName: MarkerSchemaByName
): Thread | null {
if (PIIToBeRemoved.shouldRemoveThreads.has(threadIndex)) {
// If this is a hidden thread, remove the thread immediately.
@@ -274,40 +279,42 @@ function sanitizeThreadPII(
markerTable.data[i] = removePrefMarkerPreferenceValues(currentMarker);
}
- // Remove the all network URLs if user wants to remove them.
- if (
- PIIToBeRemoved.shouldRemoveUrls &&
- currentMarker &&
- currentMarker.type === 'Network'
- ) {
- // Remove the URI fields from marker payload.
- markerTable.data[i] = removeNetworkMarkerURLs(currentMarker);
+ if (currentMarker && PIIToBeRemoved.shouldRemoveUrls) {
+ // Use the schema to find some properties that need to be sanitized.
+ const markerNameIndex = markerTable.name[i];
+ const markerName = thread.stringTable.getString(markerNameIndex);
+ const markerSchema = getSchemaFromMarker(
+ markerSchemaByName,
+ markerName,
+ currentMarker
+ );
+ if (markerSchema) {
+ currentMarker = markerTable.data[i] = sanitizeFromMarkerSchema(
+ markerSchema,
+ currentMarker
+ );
+ }
- // Strip the URL from the marker name
- const stringIndex = markerTable.name[i];
- stringArray[stringIndex] = stringArray[stringIndex].replace(/:.*/, '');
- }
+ // Remove the network URLs if user wants to remove them.
+ if (currentMarker.type === 'Network') {
+ // Remove the URI fields from marker payload.
+ markerTable.data[i] = removeNetworkMarkerURLs(currentMarker);
- // Remove the all OS paths from FileIO markers if user wants to remove them.
- if (
- PIIToBeRemoved.shouldRemoveUrls &&
- currentMarker &&
- currentMarker.type === 'FileIO'
- ) {
- // Remove the filename path from marker payload.
- markerTable.data[i] = sanitizeFileIOMarkerFilenamePath(currentMarker);
- }
+ // Strip the URL from the marker name
+ const stringIndex = markerTable.name[i];
+ stringArray[stringIndex] = stringArray[stringIndex].replace(
+ /:.*/,
+ ''
+ );
+ }
- if (
- PIIToBeRemoved.shouldRemoveUrls &&
- currentMarker &&
- currentMarker.type === 'Text'
- ) {
- // Sanitize all the name fields of text markers in case they contain URLs.
- markerTable.data[i] = sanitizeTextMarker(currentMarker);
- // Re-assign the value of currentMarker as the marker may be
- // sanitized again to remove extension ids.
- currentMarker = markerTable.data[i];
+ if (currentMarker.type === 'Text') {
+ // Sanitize all the name fields of text markers in case they contain URLs.
+ markerTable.data[i] = sanitizeTextMarker(currentMarker);
+ // Re-assign the value of currentMarker as the marker may be
+ // sanitized again to remove extension ids.
+ currentMarker = markerTable.data[i];
+ }
}
if (
diff --git a/src/profile-logic/tracks.js b/src/profile-logic/tracks.js
index 687dc1b9c1..cb6d41f262 100644
--- a/src/profile-logic/tracks.js
+++ b/src/profile-logic/tracks.js
@@ -993,7 +993,7 @@ function _computeThreadSampleScore(
{ samples, stackTable }: Thread,
maxCpuDeltaPerInterval: number | null
): number {
- if (samples.threadCPUDelta) {
+ if (meta.sampleUnits && samples.threadCPUDelta) {
// Sum up all CPU deltas in this thread, to compute a total
// CPU time for this thread (or a total CPU cycle count).
return samples.threadCPUDelta.reduce(
diff --git a/src/reducers/profile-view.js b/src/reducers/profile-view.js
index 6474fcd0f1..a2d2ee89b3 100644
--- a/src/reducers/profile-view.js
+++ b/src/reducers/profile-view.js
@@ -24,17 +24,20 @@ import type {
SymbolicationStatus,
ThreadViewOptions,
ThreadViewOptionsPerThreads,
+ TableViewOptionsPerTab,
RightClickedCallNode,
MarkerReference,
ActiveTabTimeline,
CallNodePath,
ThreadsKey,
Milliseconds,
+ TableViewOptions,
} from 'firefox-profiler/types';
import {
applyFuncSubstitutionToCallPath,
applyFuncSubstitutionToPathSetAndIncludeNewAncestors,
} from '../profile-logic/symbolication';
+import type { TabSlug } from '../app-logic/tabs-handling';
import { objectMap } from '../utils/flow';
@@ -405,6 +408,39 @@ const viewOptionsPerThread: Reducer = (
}
};
+export const defaultTableViewOptions: TableViewOptions = {
+ fixedColumnWidths: null,
+};
+
+function _updateTableViewOptions(
+ state: TableViewOptionsPerTab,
+ tab: TabSlug,
+ updates: $Shape
+): TableViewOptionsPerTab {
+ const newState = { ...state };
+ newState[tab] = {
+ ...(state[tab] ?? defaultTableViewOptions),
+ ...updates,
+ };
+ return newState;
+}
+
+const tableViewOptionsPerTab: Reducer = (
+ state = ({}: TableViewOptionsPerTab),
+ action
+): TableViewOptionsPerTab => {
+ switch (action.type) {
+ case 'CHANGE_TABLE_VIEW_OPTIONS':
+ return _updateTableViewOptions(
+ state,
+ action.tab,
+ action.tableViewOptions
+ );
+ default:
+ return state;
+ }
+};
+
const waitingForLibs: Reducer> = (
state = new Set(),
action
@@ -449,18 +485,25 @@ const previewSelection: Reducer = (
const scrollToSelectionGeneration: Reducer = (state = 0, action) => {
switch (action.type) {
case 'CHANGE_INVERT_CALLSTACK':
- case 'CHANGE_SELECTED_CALL_NODE':
case 'CHANGE_SELECTED_THREAD':
case 'SELECT_TRACK':
case 'HIDE_GLOBAL_TRACK':
case 'HIDE_LOCAL_TRACK':
case 'HIDE_PROVIDED_TRACKS':
- case 'CHANGE_SELECTED_MARKER':
- case 'CHANGE_SELECTED_NETWORK_MARKER':
case 'CHANGE_CALL_TREE_SEARCH_STRING':
case 'CHANGE_MARKER_SEARCH_STRING':
case 'CHANGE_NETWORK_SEARCH_STRING':
return state + 1;
+ case 'CHANGE_SELECTED_CALL_NODE':
+ case 'CHANGE_SELECTED_MARKER':
+ case 'CHANGE_SELECTED_NETWORK_MARKER':
+ if (action.context.source === 'pointer') {
+ // If the call node was changed as a result of a pointer click, do not
+ // scroll the table. Indeed this is disturbing and prevents double
+ // clicks.
+ return state;
+ }
+ return state + 1;
default:
return state;
}
@@ -721,6 +764,7 @@ const profileViewReducer: Reducer = wrapReducerInResetter(
rightClickedMarker,
hoveredMarker,
mouseTimePosition,
+ perTab: tableViewOptionsPerTab,
}),
profile,
full: combineReducers({
diff --git a/src/selectors/profile.js b/src/selectors/profile.js
index 5b819ca7ab..1d6614acd9 100644
--- a/src/selectors/profile.js
+++ b/src/selectors/profile.js
@@ -21,6 +21,8 @@ import {
} from '../profile-logic/marker-data';
import { markerSchemaFrontEndOnly } from '../profile-logic/marker-schema';
import { getDefaultCategories } from 'firefox-profiler/profile-logic/data-structures';
+import { defaultTableViewOptions } from '../reducers/profile-view';
+import type { TabSlug } from '../app-logic/tabs-handling';
import type {
Profile,
@@ -75,6 +77,7 @@ import type {
SampleUnits,
IndexIntoSamplesTable,
ExtraProfileInfoSection,
+ TableViewOptions,
} from 'firefox-profiler/types';
export const getProfileView: Selector = (state) =>
@@ -93,6 +96,9 @@ export const getOriginsProfileView: Selector = (state) =>
export const getProfileViewOptions: Selector<
$PropertyType
> = (state) => getProfileView(state).viewOptions;
+export const getCurrentTableViewOptions: Selector = (state) =>
+ getProfileViewOptions(state).perTab[UrlState.getSelectedTab(state)] ||
+ defaultTableViewOptions;
export const getProfileRootRange: Selector = (state) =>
getProfileViewOptions(state).rootRange;
export const getSymbolicationStatus: Selector = (state) =>
@@ -122,6 +128,12 @@ export const getCommittedRange: Selector = createSelector(
export const getMouseTimePosition: Selector = (state) =>
getProfileViewOptions(state).mouseTimePosition;
+export const getTableViewOptionSelectors: (TabSlug) => Selector =
+ (tab) => (state) => {
+ const options = getProfileViewOptions(state).perTab[tab];
+ return options || defaultTableViewOptions;
+ };
+
export const getPreviewSelection: Selector = (state) =>
getProfileViewOptions(state).previewSelection;
diff --git a/src/selectors/publish.js b/src/selectors/publish.js
index 23d9ad0dd5..914b222679 100644
--- a/src/selectors/publish.js
+++ b/src/selectors/publish.js
@@ -16,6 +16,7 @@ import {
getContainsPrivateBrowsingInformation,
getThreads,
getActiveTabID,
+ getMarkerSchemaByName,
} from './profile';
import { compress } from '../utils/gz';
import { serializeProfile } from '../profile-logic/process-profile';
@@ -222,6 +223,7 @@ export const getSanitizedProfile: Selector =
getProfile,
getDerivedMarkerInfoForAllThreads,
getRemoveProfileInformation,
+ getMarkerSchemaByName,
sanitizePII
);
diff --git a/src/test/components/CallTreeSidebar.test.js b/src/test/components/CallTreeSidebar.test.js
index ce0c34341d..9154eb29d2 100644
--- a/src/test/components/CallTreeSidebar.test.js
+++ b/src/test/components/CallTreeSidebar.test.js
@@ -158,7 +158,7 @@ describe('CallTreeSidebar', function () {
getAllByText,
funcNamesDict: { A, B, D },
} = setup(
- getMergedProfileFromTextSamples(
+ getMergedProfileFromTextSamples([
`
A A A
B B C
@@ -168,8 +168,8 @@ describe('CallTreeSidebar', function () {
A A A
B B B
G I E
- `
- )
+ `,
+ ])
);
dispatch(changeSelectedThreads(new Set([2])));
diff --git a/src/test/components/MarkerTable.test.js b/src/test/components/MarkerTable.test.js
index ba67e6a2d7..76e9b450fb 100644
--- a/src/test/components/MarkerTable.test.js
+++ b/src/test/components/MarkerTable.test.js
@@ -9,7 +9,11 @@ import { stripIndent } from 'common-tags';
// This module is mocked.
import copy from 'copy-to-clipboard';
-import { render, screen } from 'firefox-profiler/test/fixtures/testing-library';
+import {
+ render,
+ screen,
+ fireEvent,
+} from 'firefox-profiler/test/fixtures/testing-library';
import { MarkerTable } from '../../components/marker-table';
import { MaybeMarkerContextMenu } from '../../components/shared/MarkerContextMenu';
import {
@@ -19,6 +23,7 @@ import {
hideLocalTrack,
selectTrackWithModifiers,
} from '../../actions/profile-view';
+import { changeSelectedTab } from 'firefox-profiler/actions/app';
import { ensureExists } from '../../utils/flow';
import { getEmptyThread } from 'firefox-profiler/profile-logic/data-structures';
@@ -37,6 +42,7 @@ import {
getHumanReadableTracks,
} from '../fixtures/profiles/tracks';
import * as UrlStateSelectors from '../../selectors/url-state';
+import { getScrollToSelectionGeneration } from 'firefox-profiler/selectors/profile';
import type { CauseBacktrace } from 'firefox-profiler/types';
@@ -104,14 +110,20 @@ describe('MarkerTable', function () {
});
it('selects a row when left clicking', () => {
- const { getByText, getRowElement } = setup();
+ const { getByText, getRowElement, getState } = setup();
+ const initialScrollGeneration = getScrollToSelectionGeneration(getState());
fireFullClick(getByText(/setTimeout/));
expect(getRowElement(/setTimeout/)).toHaveClass('isSelected');
fireFullClick(getByText('foobar'));
expect(getRowElement(/setTimeout/)).not.toHaveClass('isSelected');
expect(getRowElement('foobar')).toHaveClass('isSelected');
+
+ // The scroll generation hasn't moved.
+ expect(getScrollToSelectionGeneration(getState())).toEqual(
+ initialScrollGeneration
+ );
});
it('displays a context menu when right clicking', () => {
@@ -420,6 +432,58 @@ describe('MarkerTable', function () {
expect(screen.queryByText(/Select the/)).not.toBeInTheDocument();
});
});
+
+ describe('column resizing', () => {
+ it('can resize a column, then move it back to its initial size', () => {
+ const store = storeWithProfile(getMarkerTableProfile());
+ store.dispatch(changeSelectedTab('marker-table'));
+ const { unmount } = render(
+
+
+
+ );
+
+ let dividerForFirstColumn = ensureExists(
+ document.querySelector('.treeViewColumnDivider')
+ );
+ let firstColumn = screen.getByText('Start');
+ expect(firstColumn).toHaveStyle({ width: '90px' });
+ fireEvent.mouseDown(dividerForFirstColumn, { clientX: 90 });
+
+ const body = ensureExists(document.body);
+
+ // Move right
+ fireEvent.mouseMove(body, { clientX: 100 });
+ expect(firstColumn).toHaveStyle({ width: '100px' });
+
+ // Move left
+ fireEvent.mouseMove(body, { clientX: 80 });
+ expect(firstColumn).toHaveStyle({ width: '80px' });
+
+ // Release the mouse -> still the same style
+ fireEvent.mouseUp(body);
+ expect(firstColumn).toHaveStyle({ width: '80px' });
+
+ // Now we'll unmount and render again.
+ unmount();
+ render(
+
+
+
+ );
+
+ // Make sure the first column kept its width
+ firstColumn = screen.getByText('Start');
+ expect(firstColumn).toHaveStyle({ width: '80px' });
+
+ // Now double click to reset the style.
+ dividerForFirstColumn = ensureExists(
+ document.querySelector('.treeViewColumnDivider')
+ );
+ fireEvent.dblClick(dividerForFirstColumn, { detail: 2 });
+ expect(firstColumn).toHaveStyle({ width: '90px' });
+ });
+ });
});
function getReflowMarker(
diff --git a/src/test/components/NetworkChart.test.js b/src/test/components/NetworkChart.test.js
index 870438858f..d71a215813 100644
--- a/src/test/components/NetworkChart.test.js
+++ b/src/test/components/NetworkChart.test.js
@@ -25,6 +25,7 @@ import {
TIMELINE_MARGIN_RIGHT,
} from '../../app-logic/constants';
import { selectedThreadSelectors } from 'firefox-profiler/selectors/per-thread';
+import { getScrollToSelectionGeneration } from 'firefox-profiler/selectors/profile';
import { storeWithProfile } from '../fixtures/stores';
import {
@@ -679,12 +680,13 @@ describe('calltree/ProfileCallTreeView navigation keys', () => {
afterEach(removeRootOverlayElement);
function setup(markers) {
- const { container } = setupWithPayload(markers);
+ const { container, getState } = setupWithPayload(markers);
const renderedRows = container.querySelectorAll('.networkChartRowItem');
expect(renderedRows.length).toEqual(48);
return {
+ getState,
// take either a key as a string, or a full event if we need more
// information like modifier keys.
simulateKey: (param: string | { key: string }) => {
@@ -708,10 +710,16 @@ describe('calltree/ProfileCallTreeView navigation keys', () => {
}
it('selects row on left click', () => {
- const { rowItem } = setupWithPayload(getNetworkMarkers());
+ const { rowItem, getState } = setupWithPayload(getNetworkMarkers());
+ const initialScrollGeneration = getScrollToSelectionGeneration(getState());
fireFullClick(rowItem());
expect(rowItem()).toHaveClass('isSelected');
+
+ // The scroll generation hasn't moved.
+ expect(getScrollToSelectionGeneration(getState())).toEqual(
+ initialScrollGeneration
+ );
});
it('reacts properly to up/down navigation keys', () => {
@@ -732,7 +740,9 @@ describe('calltree/ProfileCallTreeView navigation keys', () => {
return [].concat(...arrayOfNetworkMarkers);
})();
- const { simulateKey, selectedText } = setup(markers);
+ const { simulateKey, selectedText, getState } = setup(markers);
+
+ const initialScrollGeneration = getScrollToSelectionGeneration(getState());
simulateKey('ArrowDown');
expect(selectedText()).toBe(`https://mozilla.org/1`);
@@ -752,5 +762,11 @@ describe('calltree/ProfileCallTreeView navigation keys', () => {
expect(selectedText()).toBe(`https://mozilla.org/48`);
simulateKey({ key: 'ArrowUp', metaKey: true });
expect(selectedText()).toBe(`https://mozilla.org/1`);
+
+ // Now we expect that the scroll generation increased, because scroll should
+ // be triggered with the keyboard navigation.
+ expect(getScrollToSelectionGeneration(getState())).toBeGreaterThan(
+ initialScrollGeneration
+ );
});
});
diff --git a/src/test/components/ProfileCallTreeView.test.js b/src/test/components/ProfileCallTreeView.test.js
index 9e9ed6cf3a..ead86c57dd 100644
--- a/src/test/components/ProfileCallTreeView.test.js
+++ b/src/test/components/ProfileCallTreeView.test.js
@@ -11,6 +11,7 @@ import copy from 'copy-to-clipboard';
import { render, screen } from 'firefox-profiler/test/fixtures/testing-library';
import { selectedThreadSelectors } from '../../selectors/per-thread';
+import { getScrollToSelectionGeneration } from 'firefox-profiler/selectors/profile';
import { ProfileCallTreeView } from '../../components/calltree/ProfileCallTreeView';
import { CallNodeContextMenu } from '../../components/shared/CallNodeContextMenu';
import { processGeckoProfile } from '../../profile-logic/process-profile';
@@ -25,6 +26,7 @@ import {
changeInvertCallstack,
commitRange,
addTransformToStack,
+ selectTrackWithModifiers,
} from '../../actions/profile-view';
import { autoMockCanvasContext } from '../fixtures/mocks/canvas-context';
@@ -74,11 +76,12 @@ describe('calltree/ProfileCallTreeView', function () {
);
const { container } = renderResult;
- const getRowElement = (functionName) =>
- ensureExists(
- screen.getByText(functionName).closest('.treeViewRow'),
- `Couldn't find the row for node ${functionName}.`
- );
+ const getRowElement = (functionName, modifiers = {}) =>
+ screen.getByRole('treeitem', {
+ name: new RegExp(`^${functionName}`),
+ ...modifiers,
+ });
+
const getContextMenu = () =>
ensureExists(
container.querySelector('.react-contextmenu'),
@@ -169,14 +172,20 @@ describe('calltree/ProfileCallTreeView', function () {
});
it('selects a node when left clicking', () => {
- const { getByText, getRowElement } = setup();
+ const { getByText, getRowElement, getState } = setup();
+ const initialScrollGeneration = getScrollToSelectionGeneration(getState());
fireFullClick(getByText('A'));
expect(getRowElement('A')).toHaveClass('isSelected');
fireFullClick(getByText('B'));
expect(getRowElement('A')).not.toHaveClass('isSelected');
expect(getRowElement('B')).toHaveClass('isSelected');
+
+ // The scroll generation hasn't moved.
+ expect(getScrollToSelectionGeneration(getState())).toEqual(
+ initialScrollGeneration
+ );
});
it('displays a context menu when right clicking', () => {
@@ -273,14 +282,38 @@ describe('calltree/ProfileCallTreeView', function () {
expect(getRowElement('A')).not.toHaveClass('isRightClicked');
});
- it('selects the heaviest stack if it is not idle', () => {
- const { profile } = getProfileFromTextSamples(`
- A A A A A
- B C C C D
- E E
- `);
- const { getRowElement } = setup(profile);
- expect(getRowElement('C')).toHaveClass('isSelected');
+ it('procures an interesting selection, also when switching threads', () => {
+ const { profile } = getProfileFromTextSamples(
+ `
+ A A A A A
+ B C C C D
+ E E
+ `,
+ `
+ G G G G G
+ H H I I I
+ J J K L
+ `
+ );
+ // Assign values so that these threads are considered global processes.
+ Object.assign(profile.threads[0], {
+ pid: 111,
+ tid: 111,
+ name: 'GeckoMain',
+ });
+ Object.assign(profile.threads[1], {
+ pid: 112,
+ tid: 112,
+ name: 'GeckoMain',
+ });
+ const { getRowElement, dispatch } = setup(profile);
+ expect(getRowElement('C', { selected: true })).toHaveClass('isSelected');
+ expect(getRowElement('A', { expanded: true })).toBeInTheDocument();
+
+ // now switch to the other thread
+ dispatch(selectTrackWithModifiers({ type: 'global', trackIndex: 1 }));
+ expect(getRowElement('K', { selected: true })).toHaveClass('isSelected');
+ expect(getRowElement('I', { expanded: true })).toBeInTheDocument();
});
it('does not select the heaviest stack if it is idle', () => {
@@ -289,8 +322,9 @@ describe('calltree/ProfileCallTreeView', function () {
B C[cat:Idle] C[cat:Idle] C[cat:Idle] D
E E
`);
- const { container } = setup(profile);
- expect(container.querySelector('.treeViewRow.isSelected')).toBeFalsy();
+ const { getRowElement } = setup(profile);
+ expect(getRowElement('E', { selected: true })).toHaveClass('isSelected');
+ expect(getRowElement('B', { expanded: true })).toBeInTheDocument();
});
});
@@ -352,6 +386,7 @@ describe('calltree/ProfileCallTreeView navigation keys', () => {
expect(renderedRows.length).toBe(expectedRowsLength);
return {
+ ...store,
// take either a key as a string, or a full event if we need more
// information like modifier keys.
simulateKey: (param: string | { key: string }) => {
@@ -388,7 +423,9 @@ describe('calltree/ProfileCallTreeView navigation keys', () => {
''
);
- const { simulateKey, selectedText } = setup(profileString, 100);
+ const { simulateKey, selectedText, getState } = setup(profileString, 100);
+
+ const initialScrollGeneration = getScrollToSelectionGeneration(getState());
expect(selectedText()).toBe('name1');
simulateKey('ArrowDown');
@@ -409,6 +446,12 @@ describe('calltree/ProfileCallTreeView navigation keys', () => {
expect(selectedText()).toBe('name100');
simulateKey({ key: 'ArrowUp', metaKey: true });
expect(selectedText()).toBe('name1');
+
+ // Now we expect that the scroll generation increased, because scroll should
+ // be triggered with the keyboard navigation.
+ expect(getScrollToSelectionGeneration(getState())).toBeGreaterThan(
+ initialScrollGeneration
+ );
});
});
diff --git a/src/test/components/SourceView.test.js b/src/test/components/SourceView.test.js
index 407649b1d2..cc9c337bc1 100644
--- a/src/test/components/SourceView.test.js
+++ b/src/test/components/SourceView.test.js
@@ -101,6 +101,7 @@ describe('SourceView', () => {
const { sourceView } = setup();
const frameElement = screen.getByRole('treeitem', { name: /^A/ });
+
fireFullClick(frameElement);
fireFullClick(frameElement, { detail: 2 });
expect(sourceView()).toBeInTheDocument();
diff --git a/src/test/components/ThreadActivityGraph.test.js b/src/test/components/ThreadActivityGraph.test.js
index d49dade982..3d984ad97e 100644
--- a/src/test/components/ThreadActivityGraph.test.js
+++ b/src/test/components/ThreadActivityGraph.test.js
@@ -255,6 +255,10 @@ describe('ThreadActivityGraph', function () {
// A -> B -> H -> I
clickActivityGraph(1, 0.8);
expect(getCallNodePath()).toEqual(['A', 'B', 'H', 'I']);
+
+ // There's no sample at this location.
+ clickActivityGraph(0, 1);
+ expect(getCallNodePath()).toEqual([]);
});
it('will redraw even when there are no samples in range', function () {
diff --git a/src/test/components/Timeline.test.js b/src/test/components/Timeline.test.js
index ae8f002e63..6021b17d88 100644
--- a/src/test/components/Timeline.test.js
+++ b/src/test/components/Timeline.test.js
@@ -6,13 +6,19 @@
import * as React from 'react';
import { Provider } from 'react-redux';
-import { render, screen } from 'firefox-profiler/test/fixtures/testing-library';
+import {
+ render,
+ fireEvent,
+ screen,
+} from 'firefox-profiler/test/fixtures/testing-library';
import { Timeline } from '../../components/timeline';
import {
selectedThreadSelectors,
getRightClickedTrack,
+ getMouseTimePosition,
} from 'firefox-profiler/selectors';
-import { ensureExists } from '../../utils/flow';
+import { FULL_TRACK_SCREENSHOT_HEIGHT } from 'firefox-profiler/app-logic/constants';
+import { ensureExists } from 'firefox-profiler/utils/flow';
import { storeWithProfile } from '../fixtures/stores';
import {
@@ -27,6 +33,7 @@ import {
getElementWithFixedSize,
} from '../fixtures/mocks/element-size';
import {
+ getMouseEvent,
fireFullClick,
fireFullKeyPress,
fireFullContextMenu,
@@ -41,12 +48,24 @@ import { autoMockIntersectionObserver } from '../fixtures/mocks/intersection-obs
import type { Profile, ThreadIndex } from 'firefox-profiler/types';
-describe('Timeline multiple thread selection', function () {
- autoMockDomRect();
- autoMockCanvasContext();
- autoMockElementSize({ width: 200, height: 300 });
- autoMockIntersectionObserver();
+// Mock out the element size to have a 400 pixel width and some left/top
+// positioning.
+const TRACK_WIDTH = 400;
+const LEFT = 100;
+const TOP = 7;
+const INITIAL_ELEMENT_SIZE = {
+ width: TRACK_WIDTH,
+ height: FULL_TRACK_SCREENSHOT_HEIGHT,
+ offsetX: LEFT,
+ offsetY: TOP,
+};
+
+autoMockDomRect();
+autoMockCanvasContext();
+autoMockElementSize(INITIAL_ELEMENT_SIZE);
+autoMockIntersectionObserver();
+describe('Timeline multiple thread selection', function () {
function setup(profile = getProfileWithNiceTracks()) {
const store = storeWithProfile(profile);
@@ -169,7 +188,10 @@ describe('Timeline multiple thread selection', function () {
null
);
- fireFullClick(activityGraph, { offsetX: 50, offsetY: 50 });
+ fireFullClick(activityGraph, {
+ offsetX: TRACK_WIDTH / 2,
+ offsetY: (FULL_TRACK_SCREENSHOT_HEIGHT * 3) / 4,
+ });
expect(getHumanReadableTracks(getState())).toEqual([
'show [thread GeckoMain default]',
@@ -1192,11 +1214,6 @@ function _getProfileWithDroppedSamples(): Profile {
}
describe('Timeline', function () {
- autoMockCanvasContext();
- autoMockElementSize({ width: 200, height: 300 });
- autoMockIntersectionObserver();
- autoMockDomRect();
-
beforeEach(() => {
jest
.spyOn(ReactDOM, 'findDOMNode')
@@ -1358,3 +1375,91 @@ describe('Timeline', function () {
});
});
});
+
+describe('TimelineSelection', () => {
+ function setup() {
+ const flushRafCalls = mockRaf();
+ const profileLength = 10;
+ // There are 10 samples in this profile.
+ const { profile } = getProfileFromTextSamples('A '.repeat(profileLength));
+
+ // getBoundingClientRect is already mocked by autoMockElementSize.
+ jest
+ .spyOn(HTMLElement.prototype, 'getClientRects')
+ .mockImplementation(() => {
+ const result = [
+ new DOMRect(LEFT, TOP, TRACK_WIDTH, FULL_TRACK_SCREENSHOT_HEIGHT),
+ ];
+ return result;
+ });
+
+ const store = storeWithProfile(profile);
+ render(
+
+
+
+ );
+
+ // This is necessary to make sure the sizing is correct.
+ flushRafCalls();
+
+ function moveMouseOnThreadCanvas(mouseEventOptions) {
+ const threadCanvas = ensureExists(
+ document.querySelector('.threadActivityGraphCanvas'),
+ 'Expected that a thread activity graph canvas is present.'
+ );
+
+ fireEvent(threadCanvas, getMouseEvent('mousemove', mouseEventOptions));
+ }
+
+ function getTimePositionLinePosition() {
+ const positionLine = ensureExists(
+ document.querySelector('.timelineSelectionHoverLine'),
+ 'Expected that the vertical line indicating the time position is present.'
+ );
+ return parseInt(positionLine.style.left);
+ }
+
+ return {
+ ...store,
+ profileLength,
+ flushRafCalls,
+ moveMouseOnThreadCanvas,
+ getTimePositionLinePosition,
+ };
+ }
+
+ it('renders the vertical line indicating the time position from the mouse cursor', () => {
+ const {
+ moveMouseOnThreadCanvas,
+ getState,
+ profileLength,
+ getTimePositionLinePosition,
+ } = setup();
+ let samplePosition = 3;
+
+ moveMouseOnThreadCanvas({
+ pageX: LEFT + (TRACK_WIDTH * samplePosition) / profileLength,
+ pageY: TOP + 1,
+ });
+
+ expect(getMouseTimePosition(getState())).toBe(samplePosition);
+ expect(getTimePositionLinePosition()).toBe(
+ (TRACK_WIDTH * samplePosition) / profileLength
+ );
+ expect(document.body).toMatchSnapshot();
+
+ // Move the mouse in another position.
+ samplePosition = 6;
+
+ moveMouseOnThreadCanvas({
+ pageX: LEFT + (TRACK_WIDTH * samplePosition) / profileLength,
+ pageY: TOP + 1,
+ });
+
+ expect(getMouseTimePosition(getState())).toBe(samplePosition);
+ expect(getTimePositionLinePosition()).toBe(
+ (TRACK_WIDTH * samplePosition) / profileLength
+ );
+ });
+});
diff --git a/src/test/components/__snapshots__/Home.test.js.snap b/src/test/components/__snapshots__/Home.test.js.snap
index ca6d33c35f..c59740b2dd 100644
--- a/src/test/components/__snapshots__/Home.test.js.snap
+++ b/src/test/components/__snapshots__/Home.test.js.snap
@@ -95,6 +95,17 @@ exports[`app/Home renders a button to enable the popup in Firefox 1`] = `
Enable the profiler menu button to start recording a performance
profile in Firefox, then analyze it and share it with profiler.firefox.com.
+
+ You can also profile Firefox for Android. For more
+information, please consult this documentation:
+
+
+ Profiling Firefox for Android directly on device
+
+ .
+
@@ -141,6 +152,43 @@ profile in Firefox, then analyze it and share it with profiler.firefox.com
+
+ The Firefox Profiler can also import profiles from other profilers, such as
+
+
+ Linux perf
+
+ ,
+
+ Android SimplePerf
+
+ , the
+Chrome performance panel,
+
+ Android Studio
+
+ , or
+any file using the
+
+ dhat format
+
+ .
+
+ Learn how to write your
+own importer
+
+ .
+
You can also compare recordings.
Documentation
-
- How to view and record profiles
-
Recording performance profiles requires
+
+ You can also profile Firefox for Android. For more
+information, please consult this documentation:
+
+
+ Profiling Firefox for Android directly on device
+
+ .
+
@@ -332,6 +388,43 @@ However, existing profiles can be viewed in any modern browser.
+
+ The Firefox Profiler can also import profiles from other profilers, such as
+
+
+ Linux perf
+
+ ,
+
+ Android SimplePerf
+
+ , the
+Chrome performance panel,
+
+ Android Studio
+
+ , or
+any file using the
+
+ dhat format
+
+ .
+
+ Learn how to write your
+own importer
+
+ .
+
You can also compare recordings.
+
+
+ Copy call node label
+
+
+ ctrl
+
+
+ c
+
+
Marker Table
diff --git a/src/test/components/__snapshots__/MarkerTable.test.js.snap b/src/test/components/__snapshots__/MarkerTable.test.js.snap
index a46aaa6901..cb1fe477d0 100644
--- a/src/test/components/__snapshots__/MarkerTable.test.js.snap
+++ b/src/test/components/__snapshots__/MarkerTable.test.js.snap
@@ -88,19 +88,34 @@ exports[`MarkerTable renders some basic markers and updates when needed 1`] = `
>
Start
+
Duration
+
Type
+
@@ -136,22 +151,34 @@ exports[`MarkerTable renders some basic markers and updates when needed 1`] = `
>
0.000s
+
0s
+
UserTiming
+
0.002s
+
+
Paint
+
0.108s
+
unknown
+
IPC
+
0.153s
+
584.00ns
+
Text
+
0.153s
+
584.00ns
+
Text
+
0.158s
+
+
Log
+
0.162s
+
1ms
+
FileIO
+
diff --git a/src/test/components/__snapshots__/ProfileCallTreeView.test.js.snap b/src/test/components/__snapshots__/ProfileCallTreeView.test.js.snap
index 53f3829e06..25b2329a40 100644
--- a/src/test/components/__snapshots__/ProfileCallTreeView.test.js.snap
+++ b/src/test/components/__snapshots__/ProfileCallTreeView.test.js.snap
@@ -150,9 +150,11 @@ exports[`ProfileCallTreeView with JS Allocations matches the snapshot for JS all
>
Total Size (bytes)
+
Self (bytes)
+
+
100%
15
+
—
+
+
@@ -821,9 +928,11 @@ exports[`ProfileCallTreeView with balanced native allocations matches the snapsh
>
Total Size (bytes)
+
Self (bytes)
+
+
-100%
-15
+
—
+
+
@@ -1492,9 +1706,11 @@ exports[`ProfileCallTreeView with balanced native allocations matches the snapsh
>
Total Size (bytes)
+
Self (bytes)
+
+
100%
41
+
—
+
+
@@ -2151,9 +2472,11 @@ exports[`ProfileCallTreeView with unbalanced native allocations matches the snap
>
Total Size (bytes)
+
Self (bytes)
+
+
100%
15
+
—
+
+
@@ -2810,9 +3238,11 @@ exports[`ProfileCallTreeView with unbalanced native allocations matches the snap
>
Total Size (bytes)
+
Self (bytes)
+
+
-100%
-41
+
—
+
+
@@ -3765,9 +4274,11 @@ exports[`calltree/ProfileCallTreeView renders an inverted call tree 1`] = `
>
Total (samples)
+
Self
+
+
67%
2
+
2
+
+
@@ -4391,9 +5007,11 @@ exports[`calltree/ProfileCallTreeView renders an unfiltered call tree 1`] = `
>
Total (samples)
+
Self
+
+
100%
3
+
—
+
+
@@ -5017,9 +5740,11 @@ exports[`calltree/ProfileCallTreeView renders an unfiltered call tree with filen
>
Total (samples)
+
Self
+
+
100%
1
+
—
+
+
@@ -5466,9 +6257,11 @@ exports[`calltree/ProfileCallTreeView renders call tree with some search strings
>
Total (samples)
+
Self
+
+
100%
3
+
—
+
+
@@ -6092,9 +6990,11 @@ exports[`calltree/ProfileCallTreeView renders call tree with some search strings
>
Total (samples)
+
Self
+
+
100%
2
+
—
+
+
@@ -6659,9 +7651,11 @@ exports[`calltree/ProfileCallTreeView renders call tree with some search strings
>
Total (samples)
+
Self
+
+
100%
2
+
—
+
+
@@ -7226,9 +8312,11 @@ exports[`calltree/ProfileCallTreeView renders call tree with some search strings
>
Total (samples)
+
Self
+
+
100%
1
+
—
+
+
@@ -7676,9 +8830,11 @@ exports[`calltree/ProfileCallTreeView renders call tree with some search strings
>
Total (samples)
+
Self
+
+
100%
1
+
—
+
+
@@ -8126,9 +9348,11 @@ exports[`calltree/ProfileCallTreeView renders call tree with some search strings
>
Total (samples)
+
Self
+
+
100%
2
+
—
+
+
diff --git a/src/test/components/__snapshots__/Timeline.test.js.snap b/src/test/components/__snapshots__/Timeline.test.js.snap
new file mode 100644
index 0000000000..77465d5312
--- /dev/null
+++ b/src/test/components/__snapshots__/Timeline.test.js.snap
@@ -0,0 +1,331 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TimelineSelection renders the vertical line indicating the time position from the mouse cursor 1`] = `
+
+
+
+`;
diff --git a/src/test/components/__snapshots__/TrackPower.test.js.snap b/src/test/components/__snapshots__/TrackPower.test.js.snap
index baeb177d6c..9c8b7ca90f 100644
--- a/src/test/components/__snapshots__/TrackPower.test.js.snap
+++ b/src/test/components/__snapshots__/TrackPower.test.js.snap
@@ -27,7 +27,7 @@ exports[`TrackPower has a tooltip that matches the snapshot 1`] = `
Energy used in the visible range
:
- 7.2 µWh
+ 7.2 µWh (0.003 mg CO₂e)
`;
diff --git a/src/test/fixtures/mocks/domrect.js b/src/test/fixtures/mocks/domrect.js
index 6bd930ed82..1cee7af459 100644
--- a/src/test/fixtures/mocks/domrect.js
+++ b/src/test/fixtures/mocks/domrect.js
@@ -16,7 +16,7 @@ class DOMRect {
height: number;
constructor(x: number, y: number, width: number, height: number) {
- this.x = y;
+ this.x = x;
this.y = y;
this.width = width;
this.height = height;
diff --git a/src/test/fixtures/profiles/gecko-profile.js b/src/test/fixtures/profiles/gecko-profile.js
index 9d7d86bfb3..2ec9204c65 100644
--- a/src/test/fixtures/profiles/gecko-profile.js
+++ b/src/test/fixtures/profiles/gecko-profile.js
@@ -336,7 +336,7 @@ type TestDefinedGeckoMarker = {|
+endTime: Milliseconds | null,
+phase: MarkerPhase,
+category?: IndexIntoCategoryList,
- +data?: MarkerPayload_Gecko,
+ +data?: MarkerPayload_Gecko | null,
|};
function _createGeckoThreadWithMarkers(
diff --git a/src/test/fixtures/profiles/processed-profile.js b/src/test/fixtures/profiles/processed-profile.js
index e7b046d046..8678958473 100644
--- a/src/test/fixtures/profiles/processed-profile.js
+++ b/src/test/fixtures/profiles/processed-profile.js
@@ -75,7 +75,7 @@ export type TestDefinedMarkers = Array<
MarkerName,
MarkerTime, // start time
MarkerTime | null, // end time
- MarkerPayload | MockPayload
+ MarkerPayload | MockPayload | null
]
>;
@@ -86,7 +86,7 @@ export type TestDefinedRawMarker = {|
+endTime: Milliseconds | null,
+phase: MarkerPhase,
+category?: IndexIntoCategoryList,
- +data?: MarkerPayload,
+ +data?: MarkerPayload | null,
|};
export type TestDefinedJsTracerEvent = [
@@ -131,7 +131,7 @@ export function addMarkersToThreadWithCorrespondingSamples(
const startTime = tuple[1];
// Flow doesn't support variadic tuple types.
const maybeEndTime = (tuple: any)[2] || null;
- const payload: MarkerPayload = (tuple: any)[3] || null;
+ const payload: MarkerPayload | null = (tuple: any)[3] || null;
markersTable.name.push(stringTable.indexForString(name));
if (maybeEndTime === null) {
@@ -1042,7 +1042,13 @@ function _buildThreadFromTextOnlyStacks(
/**
* This returns a merged profile from a number of profile strings.
*/
-export function getMergedProfileFromTextSamples(...profileStrings: string[]): {
+export function getMergedProfileFromTextSamples(
+ profileStrings: string[],
+ cpuValuesPerProfile: Array<{|
+ threadCPUDelta: Array,
+ threadCPUDeltaUnit: ThreadCPUDeltaUnit,
+ |} | null> = []
+): {
profile: Profile,
funcNamesPerThread: Array,
funcNamesDictPerThread: Array<{ [funcName: string]: number }>,
@@ -1051,6 +1057,16 @@ export function getMergedProfileFromTextSamples(...profileStrings: string[]): {
getProfileFromTextSamples(str)
);
const profiles = profilesAndFuncNames.map(({ profile }) => profile);
+ cpuValuesPerProfile.forEach((cpuValues, profileIndex) => {
+ if (cpuValues) {
+ addCpuUsageValues(
+ profiles[profileIndex],
+ cpuValues.threadCPUDelta,
+ cpuValues.threadCPUDeltaUnit
+ );
+ }
+ });
+
const profileState = stateFromLocation({
pathname: '/public/fakehash1/',
search: '?thread=0&v=3',
diff --git a/src/test/store/profile-view.test.js b/src/test/store/profile-view.test.js
index 5d02a43a8f..a80f9020ac 100644
--- a/src/test/store/profile-view.test.js
+++ b/src/test/store/profile-view.test.js
@@ -540,10 +540,10 @@ describe('actions/ProfileView', function () {
trackIndex: 2,
};
- const { profile } = getMergedProfileFromTextSamples(
+ const { profile } = getMergedProfileFromTextSamples([
'A B C',
- 'A B B'
- );
+ 'A B B',
+ ]);
const { getState, dispatch } = storeWithProfile(profile);
dispatch(App.changeSelectedTab('flame-graph'));
@@ -2900,18 +2900,18 @@ describe('getTimingsForSidebar', () => {
describe('for a diffing track', function () {
function setup() {
const { profile, funcNamesDictPerThread } =
- getMergedProfileFromTextSamples(
+ getMergedProfileFromTextSamples([
`
- A A A
- B B C
- D[cat:Layout] E F
- `,
+ A A A
+ B B C
+ D[cat:Layout] E F
+ `,
`
- A A A
- B B B
- Gjs I E
- `
- );
+ A A A
+ B B B
+ Gjs I E
+ `,
+ ]);
const store = storeWithProfile(profile);
store.dispatch(ProfileView.changeSelectedThreads(new Set([2])));
diff --git a/src/test/store/receive-profile.test.js b/src/test/store/receive-profile.test.js
index 452023d21d..4b353a557c 100644
--- a/src/test/store/receive-profile.test.js
+++ b/src/test/store/receive-profile.test.js
@@ -3,9 +3,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// @flow
-import type { Profile } from 'firefox-profiler/types';
-
import { oneLineTrim } from 'common-tags';
+import JSZip from 'jszip';
+import { indexedDB } from 'fake-indexeddb';
import { ensureExists } from 'firefox-profiler/utils/flow';
import {
@@ -14,7 +14,6 @@ import {
} from '../../profile-logic/data-structures';
import { getTimeRangeForThread } from '../../profile-logic/profile-data';
import { viewProfileFromPathInZipFile } from '../../actions/zipped-profiles';
-import { blankStore } from '../fixtures/stores';
import * as ProfileViewSelectors from '../../selectors/profile';
import * as ZippedProfilesSelectors from '../../selectors/zipped-profiles';
import * as UrlStateSelectors from '../../selectors/url-state';
@@ -35,10 +34,9 @@ import {
changeTimelineTrackOrganization,
} from '../../actions/receive-profile';
import { SymbolsNotFoundError } from '../../profile-logic/errors';
-import { indexedDB } from 'fake-indexeddb';
import { createGeckoProfile } from '../fixtures/profiles/gecko-profile';
-import JSZip from 'jszip';
+import { blankStore, storeWithProfile } from '../fixtures/stores';
import {
makeProfileSerializable,
processGeckoProfile,
@@ -46,6 +44,7 @@ import {
} from '../../profile-logic/process-profile';
import {
getProfileFromTextSamples,
+ getMergedProfileFromTextSamples,
addMarkersToThreadWithCorrespondingSamples,
getProfileWithMarkers,
getProfileWithThreadCPUDelta,
@@ -58,6 +57,8 @@ import { waitUntilState } from '../fixtures/utils';
import { compress } from '../../utils/gz';
+import type { Profile } from 'firefox-profiler/types';
+
// Mocking SymbolStoreDB. By default the functions will return undefined, which
// will make the symbolication move forward with some bogus information.
// If you need to simulate that it doesn't have the information, use the
@@ -397,6 +398,22 @@ describe('actions/receive-profile', function () {
]);
});
+ it(`won't hide any tracks in a profile resulting from a compare operation`, () => {
+ const { profile } = getMergedProfileFromTextSamples([
+ 'A',
+ 'A '.repeat(100),
+ ]);
+
+ const store = storeWithProfile(profile);
+
+ store.dispatch(viewProfile(profile));
+ expect(getHumanReadableTracks(store.getState())).toEqual([
+ 'show [thread Empty default] SELECTED',
+ 'show [thread Empty default]',
+ 'show [thread Diff between 1 and 2 comparison]',
+ ]);
+ });
+
describe('with threadCPUDelta', function () {
it('will show a thread when the relative CPU usage is above 10%', function () {
const store = blankStore();
@@ -474,6 +491,31 @@ describe('actions/receive-profile', function () {
' - show [thread Thread with 10% CPU] SELECTED', // <- Ensure this thread is not hidden.
]);
});
+
+ it(`won't hide any tracks in a profile resulting from a compare operation`, () => {
+ const { profile } = getMergedProfileFromTextSamples(
+ ['A A A A', 'B B B B'],
+ [
+ {
+ threadCPUDelta: [10, 10, 10, 10_000_000],
+ threadCPUDeltaUnit: 'ns',
+ },
+ {
+ threadCPUDelta: [10, 10_000_000, 10, 25],
+ threadCPUDeltaUnit: 'ns',
+ },
+ ]
+ );
+
+ const store = storeWithProfile(profile);
+
+ store.dispatch(viewProfile(profile));
+ expect(getHumanReadableTracks(store.getState())).toEqual([
+ 'show [thread Empty default] SELECTED',
+ 'show [thread Empty default]',
+ 'show [thread Diff between 1 and 2 comparison]',
+ ]);
+ });
});
describe('too many threads', function () {
diff --git a/src/test/store/useful-tabs.test.js b/src/test/store/useful-tabs.test.js
index 70fe17e467..96a2a890f8 100644
--- a/src/test/store/useful-tabs.test.js
+++ b/src/test/store/useful-tabs.test.js
@@ -51,7 +51,7 @@ describe('getUsefulTabs', function () {
});
it('shows only the call tree when a diffing track is selected', function () {
- const { profile } = getMergedProfileFromTextSamples('A B C', 'A B B');
+ const { profile } = getMergedProfileFromTextSamples(['A B C', 'A B B']);
const { getState, dispatch } = storeWithProfile(profile);
expect(selectedThreadSelectors.getUsefulTabs(getState())).toEqual([
'calltree',
diff --git a/src/test/unit/__snapshots__/profile-conversion.test.js.snap b/src/test/unit/__snapshots__/profile-conversion.test.js.snap
index 942d87f59a..9b27ebba3a 100644
--- a/src/test/unit/__snapshots__/profile-conversion.test.js.snap
+++ b/src/test/unit/__snapshots__/profile-conversion.test.js.snap
@@ -402777,14 +402777,14 @@ Object {
],
"column": Array [
null,
- 1757,
+ 1758,
null,
null,
- 1363,
- 1783,
- 3420,
- 463,
- 1783,
+ 1364,
+ 1784,
+ 3421,
+ 464,
+ 1784,
null,
null,
null,
@@ -402793,14 +402793,14 @@ Object {
null,
null,
null,
- 4543,
- 1326,
+ 4544,
+ 1327,
null,
- 29,
+ 30,
null,
- 5197,
- 5375,
- 296,
+ 5198,
+ 5376,
+ 297,
null,
],
"func": Array [
@@ -402891,14 +402891,14 @@ Object {
"length": 26,
"line": Array [
null,
- 1,
+ 2,
null,
null,
- 1,
- 25,
- 25,
- 1,
- 25,
+ 2,
+ 26,
+ 26,
+ 2,
+ 26,
null,
null,
null,
@@ -402907,14 +402907,14 @@ Object {
null,
null,
null,
- 25,
- 28,
+ 26,
+ 29,
null,
- 1,
+ 2,
null,
- 25,
- 25,
- 1,
+ 26,
+ 26,
+ 2,
null,
],
"nativeSymbol": Array [
@@ -403005,13 +403005,13 @@ Object {
"funcTable": Object {
"columnNumber": Array [
null,
- 1757,
+ 1758,
null,
null,
- 1363,
- 1783,
- 3420,
- 463,
+ 1364,
+ 1784,
+ 3421,
+ 464,
null,
null,
null,
@@ -403020,14 +403020,14 @@ Object {
null,
null,
null,
- 4543,
- 1326,
+ 4544,
+ 1327,
null,
- 29,
+ 30,
null,
- 5197,
- 5375,
- 296,
+ 5198,
+ 5376,
+ 297,
null,
],
"fileName": Array [
@@ -403087,13 +403087,13 @@ Object {
"length": 25,
"lineNumber": Array [
null,
- 1,
+ 2,
null,
null,
- 1,
- 25,
- 25,
- 1,
+ 2,
+ 26,
+ 26,
+ 2,
null,
null,
null,
@@ -403102,14 +403102,14 @@ Object {
null,
null,
null,
- 25,
- 28,
+ 26,
+ 29,
null,
- 1,
+ 2,
null,
- 25,
- 25,
- 1,
+ 26,
+ 26,
+ 2,
null,
],
"name": Array [
@@ -444269,50 +444269,50 @@ Object {
"column": Array [
null,
null,
- 32,
+ 33,
null,
null,
null,
null,
null,
- 22,
- 22,
- 16,
+ 23,
+ 23,
+ 17,
null,
- 34,
- 22,
- 20,
- 20,
- 26,
- 52,
- 29,
+ 35,
+ 23,
+ 21,
+ 21,
+ 27,
+ 53,
+ 30,
null,
null,
null,
+ 26,
25,
- 24,
null,
- 37,
- 21,
- 32,
+ 38,
+ 22,
+ 33,
null,
null,
null,
- 16,
+ 17,
null,
null,
- 16,
- 22,
- 20,
- 29,
- 18,
+ 17,
+ 23,
+ 21,
30,
- 18,
- 74,
- 24,
+ 19,
+ 31,
+ 19,
+ 75,
+ 25,
null,
- 30,
- 30,
+ 31,
+ 31,
null,
],
"func": Array [
@@ -444467,50 +444467,50 @@ Object {
"line": Array [
null,
null,
- 3338,
+ 3339,
null,
null,
null,
null,
null,
- 75674,
- 75624,
- 65,
+ 75675,
+ 75625,
+ 66,
null,
- 70,
- 73232,
- 73084,
- 72908,
- 74574,
- 74061,
- 74281,
+ 71,
+ 73233,
+ 73085,
+ 72909,
+ 74575,
+ 74062,
+ 74282,
null,
null,
null,
- 27197,
- 27498,
+ 27198,
+ 27499,
null,
- 26821,
- 27773,
- 28117,
+ 26822,
+ 27774,
+ 28118,
null,
null,
null,
- 137,
+ 138,
null,
null,
- 32,
- 73232,
- 73084,
- 74281,
- 74613,
- 13073,
- 79762,
- 74085,
- 73822,
+ 33,
+ 73233,
+ 73085,
+ 74282,
+ 74614,
+ 13074,
+ 79763,
+ 74086,
+ 73823,
null,
- 13073,
- 13382,
+ 13074,
+ 13383,
null,
],
"nativeSymbol": Array [
@@ -444665,45 +444665,45 @@ Object {
"columnNumber": Array [
null,
null,
- 32,
+ 33,
null,
null,
null,
null,
- 22,
- 22,
- 16,
+ 23,
+ 23,
+ 17,
null,
- 34,
- 22,
- 20,
- 20,
- 26,
- 52,
- 29,
+ 35,
+ 23,
+ 21,
+ 21,
+ 27,
+ 53,
+ 30,
null,
null,
null,
null,
+ 26,
25,
- 24,
null,
- 37,
- 21,
- 32,
+ 38,
+ 22,
+ 33,
null,
null,
null,
- 16,
+ 17,
null,
null,
- 16,
- 18,
- 30,
- 18,
- 74,
- 24,
- 30,
+ 17,
+ 19,
+ 31,
+ 19,
+ 75,
+ 25,
+ 31,
],
"fileName": Array [
null,
@@ -444795,45 +444795,45 @@ Object {
"lineNumber": Array [
null,
null,
- 3338,
+ 3339,
null,
null,
null,
null,
- 75674,
- 75624,
- 65,
+ 75675,
+ 75625,
+ 66,
null,
- 70,
- 73232,
- 73084,
- 72908,
- 74574,
- 74061,
- 74281,
+ 71,
+ 73233,
+ 73085,
+ 72909,
+ 74575,
+ 74062,
+ 74282,
null,
null,
null,
null,
- 27197,
- 27498,
+ 27198,
+ 27499,
null,
- 26821,
- 27773,
- 28117,
+ 26822,
+ 27774,
+ 28118,
null,
null,
null,
- 137,
+ 138,
null,
null,
- 32,
- 74613,
- 13073,
- 79762,
- 74085,
- 73822,
- 13382,
+ 33,
+ 74614,
+ 13074,
+ 79763,
+ 74086,
+ 73823,
+ 13383,
],
"name": Array [
0,
@@ -447302,50 +447302,50 @@ Object {
"column": Array [
null,
null,
- 32,
+ 33,
null,
null,
null,
null,
null,
- 22,
- 22,
- 16,
+ 23,
+ 23,
+ 17,
null,
- 34,
- 22,
- 20,
- 20,
- 26,
- 52,
- 29,
+ 35,
+ 23,
+ 21,
+ 21,
+ 27,
+ 53,
+ 30,
null,
null,
null,
+ 26,
25,
- 24,
null,
- 37,
- 21,
- 32,
+ 38,
+ 22,
+ 33,
null,
null,
null,
- 16,
+ 17,
null,
null,
- 16,
- 22,
- 20,
- 29,
- 18,
+ 17,
+ 23,
+ 21,
30,
- 18,
- 74,
- 24,
+ 19,
+ 31,
+ 19,
+ 75,
+ 25,
null,
- 30,
- 30,
+ 31,
+ 31,
null,
],
"func": Array [
@@ -447500,50 +447500,50 @@ Object {
"line": Array [
null,
null,
- 3338,
+ 3339,
null,
null,
null,
null,
null,
- 75674,
- 75624,
- 65,
+ 75675,
+ 75625,
+ 66,
null,
- 70,
- 73232,
- 73084,
- 72908,
- 74574,
- 74061,
- 74281,
+ 71,
+ 73233,
+ 73085,
+ 72909,
+ 74575,
+ 74062,
+ 74282,
null,
null,
null,
- 27197,
- 27498,
+ 27198,
+ 27499,
null,
- 26821,
- 27773,
- 28117,
+ 26822,
+ 27774,
+ 28118,
null,
null,
null,
- 137,
+ 138,
null,
null,
- 32,
- 73232,
- 73084,
- 74281,
- 74613,
- 13073,
- 79762,
- 74085,
- 73822,
+ 33,
+ 73233,
+ 73085,
+ 74282,
+ 74614,
+ 13074,
+ 79763,
+ 74086,
+ 73823,
null,
- 13073,
- 13382,
+ 13074,
+ 13383,
null,
],
"nativeSymbol": Array [
@@ -447698,45 +447698,45 @@ Object {
"columnNumber": Array [
null,
null,
- 32,
+ 33,
null,
null,
null,
null,
- 22,
- 22,
- 16,
+ 23,
+ 23,
+ 17,
null,
- 34,
- 22,
- 20,
- 20,
- 26,
- 52,
- 29,
+ 35,
+ 23,
+ 21,
+ 21,
+ 27,
+ 53,
+ 30,
null,
null,
null,
null,
+ 26,
25,
- 24,
null,
- 37,
- 21,
- 32,
+ 38,
+ 22,
+ 33,
null,
null,
null,
- 16,
+ 17,
null,
null,
- 16,
- 18,
- 30,
- 18,
- 74,
- 24,
- 30,
+ 17,
+ 19,
+ 31,
+ 19,
+ 75,
+ 25,
+ 31,
],
"fileName": Array [
null,
@@ -447828,45 +447828,45 @@ Object {
"lineNumber": Array [
null,
null,
- 3338,
+ 3339,
null,
null,
null,
null,
- 75674,
- 75624,
- 65,
+ 75675,
+ 75625,
+ 66,
null,
- 70,
- 73232,
- 73084,
- 72908,
- 74574,
- 74061,
- 74281,
+ 71,
+ 73233,
+ 73085,
+ 72909,
+ 74575,
+ 74062,
+ 74282,
null,
null,
null,
null,
- 27197,
- 27498,
+ 27198,
+ 27499,
null,
- 26821,
- 27773,
- 28117,
+ 26822,
+ 27774,
+ 28118,
null,
null,
null,
- 137,
+ 138,
null,
null,
- 32,
- 74613,
- 13073,
- 79762,
- 74085,
- 73822,
- 13382,
+ 33,
+ 74614,
+ 13074,
+ 79763,
+ 74086,
+ 73823,
+ 13383,
],
"name": Array [
0,
diff --git a/src/test/unit/merge-compare.test.js b/src/test/unit/merge-compare.test.js
index 57fd303484..a35983e0d3 100644
--- a/src/test/unit/merge-compare.test.js
+++ b/src/test/unit/merge-compare.test.js
@@ -481,7 +481,7 @@ describe('mergeThreads function', function () {
// Check if we properly merged the string tables and have the correct url fields.
const markerUrlsAfterMerge = mergedMarkers.data.map((markerData) =>
- markerData && 'url' in markerData && markerData.url
+ markerData && 'url' in markerData && typeof markerData.url === 'number'
? markerData.url
: null
);
diff --git a/src/test/unit/process-profile.test.js b/src/test/unit/process-profile.test.js
index 1ffe854ded..d95b7169cc 100644
--- a/src/test/unit/process-profile.test.js
+++ b/src/test/unit/process-profile.test.js
@@ -664,14 +664,15 @@ describe('profile meta processing', function () {
});
describe('visualMetrics processing', function () {
- function checkVisualMetricsForThread(thread: Thread) {
- const metrics = [
- { name: 'Visual', changeMarkerLength: 7 },
- { name: 'ContentfulSpeedIndex', changeMarkerLength: 6 },
- { name: 'PerceptualSpeedIndex', changeMarkerLength: 6 },
- ];
-
- for (const { name, changeMarkerLength } of metrics) {
+ function checkVisualMetricsForThread(
+ thread: Thread,
+ metrics: Array<{|
+ name: string,
+ hasProgressMarker: boolean,
+ changeMarkerLength: number,
+ |}>
+ ) {
+ for (const { name, hasProgressMarker, changeMarkerLength } of metrics) {
// Check the visual metric progress markers.
const metricProgressMarkerIndex = thread.stringTable.indexForString(
`${name} Progress`
@@ -679,7 +680,12 @@ describe('visualMetrics processing', function () {
const metricProgressMarker = thread.markers.name.find(
(name) => name === metricProgressMarkerIndex
);
- expect(metricProgressMarker).toBeTruthy();
+
+ if (hasProgressMarker) {
+ expect(metricProgressMarker).toBeTruthy();
+ } else {
+ expect(metricProgressMarker).toBeFalsy();
+ }
// Check the visual metric change markers.
const metricChangeMarkerIndex = thread.stringTable.indexForString(
@@ -712,7 +718,19 @@ describe('visualMetrics processing', function () {
throw new Error('Could not find the parent process main thread.');
}
- checkVisualMetricsForThread(parentProcessMainThread);
+ checkVisualMetricsForThread(parentProcessMainThread, [
+ { name: 'Visual', hasProgressMarker: true, changeMarkerLength: 7 },
+ {
+ name: 'ContentfulSpeedIndex',
+ hasProgressMarker: true,
+ changeMarkerLength: 6,
+ },
+ {
+ name: 'PerceptualSpeedIndex',
+ hasProgressMarker: true,
+ changeMarkerLength: 6,
+ },
+ ]);
});
it('adds markers to the tab process', function () {
@@ -734,6 +752,67 @@ describe('visualMetrics processing', function () {
throw new Error('Could not find the tab process main thread.');
}
- checkVisualMetricsForThread(tabProcessMainThread);
+ checkVisualMetricsForThread(tabProcessMainThread, [
+ { name: 'Visual', hasProgressMarker: true, changeMarkerLength: 7 },
+ {
+ name: 'ContentfulSpeedIndex',
+ hasProgressMarker: true,
+ changeMarkerLength: 6,
+ },
+ {
+ name: 'PerceptualSpeedIndex',
+ hasProgressMarker: true,
+ changeMarkerLength: 6,
+ },
+ ]);
+ });
+
+ it('does not add markers to the parent process if metric timestamps are null', function () {
+ const geckoProfile = createGeckoProfile();
+ const visualMetrics = getVisualMetrics();
+
+ // Make all the VisualProgress timestamps null on purpose.
+ visualMetrics.VisualProgress = visualMetrics.VisualProgress.map(
+ (progress) => ({ ...progress, timestamp: null })
+ );
+ // Make only one ContentfulSpeedIndexProgress timestamp null on purpose.
+ ensureExists(visualMetrics.ContentfulSpeedIndexProgress)[0].timestamp =
+ null;
+
+ // Add the visual metrics to the profile.
+ geckoProfile.meta.visualMetrics = visualMetrics;
+ // Make sure that the visual metrics are not changed.
+ expect(visualMetrics.VisualProgress).toHaveLength(7);
+ expect(visualMetrics.ContentfulSpeedIndexProgress).toHaveLength(6);
+ expect(visualMetrics.PerceptualSpeedIndexProgress).toHaveLength(6);
+
+ // Processing the profile.
+ const processedProfile = processGeckoProfile(geckoProfile);
+ const parentProcessMainThread = processedProfile.threads.find(
+ (thread) =>
+ thread.name === 'GeckoMain' && thread.processType === 'default'
+ );
+
+ if (!parentProcessMainThread) {
+ throw new Error('Could not find the parent process main thread.');
+ }
+
+ checkVisualMetricsForThread(parentProcessMainThread, [
+ // Instead of 7, we should have 0 markers for Visual because we made all
+ // the timestamps null.
+ { name: 'Visual', hasProgressMarker: false, changeMarkerLength: 0 },
+ // Instead of 6, we should have 5 markers for ContentfulSpeedIndex.
+ {
+ name: 'ContentfulSpeedIndex',
+ hasProgressMarker: true,
+ changeMarkerLength: 5,
+ },
+ // We didn't change the PerceptualSpeedIndexProgress, so we should have 6.
+ {
+ name: 'PerceptualSpeedIndex',
+ hasProgressMarker: true,
+ changeMarkerLength: 6,
+ },
+ ]);
});
});
diff --git a/src/test/unit/profile-tree.test.js b/src/test/unit/profile-tree.test.js
index 12a242ff21..284e4a4f6e 100644
--- a/src/test/unit/profile-tree.test.js
+++ b/src/test/unit/profile-tree.test.js
@@ -562,18 +562,18 @@ describe('inverted call tree', function () {
describe('diffing trees', function () {
function getProfile() {
- const { profile } = getMergedProfileFromTextSamples(
+ const { profile } = getMergedProfileFromTextSamples([
`
- A A A
- B B C
- D E F
- `,
+ A A A
+ B B C
+ D E F
+ `,
`
- A A A
- B B B
- G I E
- `
- );
+ A A A
+ B B B
+ G I E
+ `,
+ ]);
return profile;
}
diff --git a/src/test/unit/sanitize.test.js b/src/test/unit/sanitize.test.js
index 87e6337769..8265d53d18 100644
--- a/src/test/unit/sanitize.test.js
+++ b/src/test/unit/sanitize.test.js
@@ -65,10 +65,55 @@ describe('sanitizePII', function () {
}
);
+ const markerSchemaByName = {
+ FileIO: {
+ name: 'FileIO',
+ display: ['marker-chart', 'marker-table', 'timeline-fileio'],
+ data: [
+ {
+ key: 'operation',
+ label: 'Operation',
+ format: 'string',
+ searchable: true,
+ },
+ {
+ key: 'source',
+ label: 'Source',
+ format: 'string',
+ searchable: true,
+ },
+ {
+ key: 'filename',
+ label: 'Filename',
+ format: 'file-path',
+ searchable: true,
+ },
+ {
+ key: 'threadId',
+ label: 'Thread ID',
+ format: 'string',
+ searchable: true,
+ },
+ ],
+ },
+ Url: {
+ name: 'Url',
+ tableLabel: '{marker.name} - {marker.data.url}',
+ display: ['marker-chart', 'marker-table'],
+ data: [
+ {
+ key: 'url',
+ format: 'url',
+ },
+ ],
+ },
+ };
+
const sanitizedProfile = sanitizePII(
originalProfile,
derivedMarkerInfoForAllThreads,
- PIIToRemove
+ PIIToRemove,
+ markerSchemaByName
).profile;
return {
@@ -548,6 +593,38 @@ describe('sanitizePII', function () {
expect(marker2.filename).toBe('\\' + marker2File);
});
+ it('should sanitize the URL properties in markers', function () {
+ const { sanitizedProfile } = setup(
+ {
+ shouldRemoveUrls: true,
+ },
+ getProfileWithMarkers([
+ [
+ 'SpeculativeConnect',
+ 1,
+ 2,
+ {
+ type: 'Url',
+ url: 'https://img-getpocket.cdn.mozilla.net',
+ },
+ ],
+ ])
+ );
+ expect(sanitizedProfile.threads.length).toEqual(1);
+ const thread = sanitizedProfile.threads[0];
+ expect(thread.markers.length).toEqual(1);
+
+ const marker = thread.markers.data[0];
+
+ // The url fields should still be there
+ if (!marker || !marker.url) {
+ throw new Error('Failed to find url property in the payload');
+ }
+
+ // Now check the url fields and make sure they are sanitized.
+ expect(marker.url).toBe('https://');
+ });
+
it('should sanitize the eTLD+1 field if urls are supposed to be sanitized', function () {
// Create a simple profile with eTLD+1 field in its thread.
const { profile } = getProfileFromTextSamples('A');
diff --git a/src/types/actions.js b/src/types/actions.js
index fbdc6ca10a..17435ba2c8 100644
--- a/src/types/actions.js
+++ b/src/types/actions.js
@@ -37,6 +37,7 @@ import type {
State,
UploadedProfileInformation,
SourceLoadingError,
+ TableViewOptions,
} from './state';
import type { CssPixels, StartEndRange, Milliseconds } from './units';
import type { BrowserConnectionStatus } from '../app-logic/browser-connection';
@@ -170,6 +171,15 @@ export type Localization = ReactLocalization;
// stored in the same property to accommodate all OSes.
export type KeyboardModifiers = {| ctrlOrMeta: boolean, shift: boolean |};
+/**
+ * This type gives some context about the action leading to a selection.
+ */
+export type SelectionContext = {|
+ // This is the source for this selection: is it a keyboard or a pointer event,
+ // or is it the result of some automatic selection.
+ +source: 'keyboard' | 'pointer' | 'auto',
+|};
+
type ProfileAction =
| {|
+type: 'ROUTE_NOT_FOUND',
@@ -185,6 +195,7 @@ type ProfileAction =
+threadsKey: ThreadsKey,
+selectedCallNodePath: CallNodePath,
+optionalExpandedToCallNodePath: ?CallNodePath,
+ +context: SelectionContext,
|}
| {|
+type: 'UPDATE_TRACK_THREAD_HEIGHT',
@@ -208,11 +219,13 @@ type ProfileAction =
+type: 'CHANGE_SELECTED_MARKER',
+threadsKey: ThreadsKey,
+selectedMarker: MarkerIndex | null,
+ +context: SelectionContext,
|}
| {|
+type: 'CHANGE_SELECTED_NETWORK_MARKER',
+threadsKey: ThreadsKey,
+selectedNetworkMarker: MarkerIndex | null,
+ +context: SelectionContext,
|}
| {|
+type: 'CHANGE_RIGHT_CLICKED_MARKER',
@@ -503,6 +516,11 @@ type UrlStateAction =
+type: 'CHANGE_MOUSE_TIME_POSITION',
+mouseTimePosition: Milliseconds | null,
|}
+ | {|
+ +type: 'CHANGE_TABLE_VIEW_OPTIONS',
+ +tab: TabSlug,
+ +tableViewOptions: TableViewOptions,
+ |}
| {|
+type: 'TOGGLE_RESOURCES_PANEL',
+selectedThreadIndexes: Set,
diff --git a/src/types/gecko-profile.js b/src/types/gecko-profile.js
index 54337729d5..dd5b05ef2d 100644
--- a/src/types/gecko-profile.js
+++ b/src/types/gecko-profile.js
@@ -44,7 +44,7 @@ export type GeckoMarkerTuple = [
Milliseconds | null,
MarkerPhase,
IndexIntoCategoryList,
- MarkerPayload_Gecko
+ MarkerPayload_Gecko | null
];
export type GeckoMarkers = {
@@ -70,7 +70,7 @@ export type GeckoMarkerStruct = {|
startTime: Milliseconds[],
endTime: Milliseconds[],
phase: MarkerPhase[],
- data: MarkerPayload_Gecko[],
+ data: Array,
category: IndexIntoCategoryList[],
length: number,
|};
diff --git a/src/types/libdef/npm/@tgwf/co2_vx.x.x.js b/src/types/libdef/npm/@tgwf/co2_vx.x.x.js
new file mode 100644
index 0000000000..ceedac16bc
--- /dev/null
+++ b/src/types/libdef/npm/@tgwf/co2_vx.x.x.js
@@ -0,0 +1,18 @@
+// flow-typed signature: b2f76cdf45a9f020ccc81f4258e36209
+// flow-typed version: <>/@tgwf/co2_v0.11.4/flow_v0.96.0
+
+/**
+ * This is an autogenerated libdef stub for:
+ *
+ * '@tgwf/co2'
+ *
+ * Fill this stub out by replacing all the `any` types.
+ *
+ * Once filled out, we encourage you to share your work with the
+ * community by sending a pull request to:
+ * https://github.com/flowtype/flow-typed
+ */
+
+declare module '@tgwf/co2' {
+ declare module.exports: any;
+}
\ No newline at end of file
diff --git a/src/types/markers.js b/src/types/markers.js
index a996bd64c6..bfe9c1b383 100644
--- a/src/types/markers.js
+++ b/src/types/markers.js
@@ -20,9 +20,7 @@ export type MarkerFormatType =
// String types.
// Show the URL, and handle PII sanitization
- // TODO Handle PII sanitization. Issue #2757
| 'url'
- // TODO Handle PII sanitization. Issue #2757
// Show the file path, and handle PII sanitization.
| 'file-path'
// Important, do not put URL or file path information here, as it will not be
@@ -136,7 +134,7 @@ export type CauseBacktrace = {|
// `tid` is optional because older processed profiles may not have it.
// No upgrader was written for this change.
tid?: Tid,
- time: Milliseconds,
+ time?: Milliseconds,
stack: IndexIntoStackTable,
|};
@@ -734,6 +732,11 @@ export type NoPayloadUserData = {|
innerWindowID?: number,
|};
+export type UrlMarkerPayload = {|
+ type: 'Url',
+ url: string,
+|};
+
/**
* The union of all the different marker payloads that profiler.firefox.com knows about,
* this is not guaranteed to be all the payloads that we actually get from the Gecko
@@ -767,7 +770,7 @@ export type MarkerPayload =
| JankPayload
| BrowsertimeMarkerPayload
| NoPayloadUserData
- | null;
+ | UrlMarkerPayload;
export type MarkerPayload_Gecko =
| GPUMarkerPayload
@@ -789,12 +792,11 @@ export type MarkerPayload_Gecko =
| IPCMarkerPayload_Gecko
| MediaSampleMarkerPayload
| NoPayloadUserData
+ | UrlMarkerPayload
// The following payloads come in with a stack property. During the profile processing
// the "stack" property is are converted into a "cause". See the CauseBacktrace type
// for more information.
| $ReplaceCauseWithStack
| $ReplaceCauseWithStack
| $ReplaceCauseWithStack
- | $ReplaceCauseWithStack
- // Payloads can be null.
- | null;
+ | $ReplaceCauseWithStack;
diff --git a/src/types/profile-derived.js b/src/types/profile-derived.js
index 2b86b325a6..d8f413437c 100644
--- a/src/types/profile-derived.js
+++ b/src/types/profile-derived.js
@@ -197,7 +197,7 @@ export type Marker = {|
name: string,
category: IndexIntoCategoryList,
threadId: Tid | null,
- data: MarkerPayload,
+ data: MarkerPayload | null,
incomplete?: boolean,
|};
diff --git a/src/types/profile.js b/src/types/profile.js
index ff995f28fa..e57a2d92be 100644
--- a/src/types/profile.js
+++ b/src/types/profile.js
@@ -253,7 +253,7 @@ export type ProfilerMarkerPayload = {
* it into a structured marker.
*/
export type RawMarkerTable = {|
- data: MarkerPayload[],
+ data: Array,
name: IndexIntoStringTable[],
startTime: Array,
endTime: Array,
@@ -681,7 +681,8 @@ export type ProgressGraphData = {|
// A percentage that describes the visual completeness of the webpage, ranging from 0% - 100%
percent: number,
// The time in milliseconds which the sample was taken.
- timestamp: Milliseconds,
+ // This can be null due to https://github.com/sitespeedio/browsertime/issues/1746.
+ timestamp: Milliseconds | null,
|};
/**
diff --git a/src/types/state.js b/src/types/state.js
index 9cd8fb2a23..6c423b4984 100644
--- a/src/types/state.js
+++ b/src/types/state.js
@@ -57,6 +57,12 @@ export type ThreadViewOptions = {|
export type ThreadViewOptionsPerThreads = { [ThreadsKey]: ThreadViewOptions };
+export type TableViewOptions = {|
+ +fixedColumnWidths: Array | null,
+|};
+
+export type TableViewOptionsPerTab = { [TabSlug]: TableViewOptions };
+
export type RightClickedCallNode = {|
+threadsKey: ThreadsKey,
+callNodePath: CallNodePath,
@@ -108,6 +114,7 @@ export type ProfileViewState = {
rightClickedMarker: MarkerReference | null,
hoveredMarker: MarkerReference | null,
mouseTimePosition: Milliseconds | null,
+ perTab: TableViewOptionsPerTab,
|},
+profile: Profile | null,
+full: FullProfileViewState,
diff --git a/yarn.lock b/yarn.lock
index b7950daf4b..be90746f4a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -31,10 +31,10 @@
jsonpointer "^5.0.0"
leven "^3.1.0"
-"@babel/cli@^7.19.3":
- version "7.19.3"
- resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.19.3.tgz#55914ed388e658e0b924b3a95da1296267e278e2"
- integrity sha512-643/TybmaCAe101m2tSVHi9UKpETXP9c/Ff4mD2tAwkdP6esKIfaauZFc67vGEM6r9fekbEGid+sZhbEnSe3dg==
+"@babel/cli@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.20.7.tgz#8fc12e85c744a1a617680eacb488fab1fcd35b7c"
+ integrity sha512-WylgcELHB66WwQqItxNILsMlaTd8/SO6SgTTjMp4uCI7P4QyH1r3nqgFmO3BfM4AtfniHgFMH3EpYFj/zynBkQ==
dependencies:
"@jridgewell/trace-mapping" "^0.3.8"
commander "^4.0.1"
@@ -54,26 +54,26 @@
dependencies:
"@babel/highlight" "^7.18.6"
-"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1":
- version "7.20.1"
- resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30"
- integrity sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==
+"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5":
+ version "7.20.10"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.10.tgz#9d92fa81b87542fff50e848ed585b4212c1d34ec"
+ integrity sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==
-"@babel/core@^7.0.0", "@babel/core@^7.11.1", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.20.5":
- version "7.20.5"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.5.tgz#45e2114dc6cd4ab167f81daf7820e8fa1250d113"
- integrity sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==
+"@babel/core@^7.0.0", "@babel/core@^7.11.1", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.7.tgz#37072f951bd4d28315445f66e0ec9f6ae0c8c35f"
+ integrity sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==
dependencies:
"@ampproject/remapping" "^2.1.0"
"@babel/code-frame" "^7.18.6"
- "@babel/generator" "^7.20.5"
- "@babel/helper-compilation-targets" "^7.20.0"
- "@babel/helper-module-transforms" "^7.20.2"
- "@babel/helpers" "^7.20.5"
- "@babel/parser" "^7.20.5"
- "@babel/template" "^7.18.10"
- "@babel/traverse" "^7.20.5"
- "@babel/types" "^7.20.5"
+ "@babel/generator" "^7.20.7"
+ "@babel/helper-compilation-targets" "^7.20.7"
+ "@babel/helper-module-transforms" "^7.20.7"
+ "@babel/helpers" "^7.20.7"
+ "@babel/parser" "^7.20.7"
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.20.7"
+ "@babel/types" "^7.20.7"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
@@ -96,12 +96,12 @@
dependencies:
eslint-rule-composer "^0.3.0"
-"@babel/generator@^7.20.5", "@babel/generator@^7.7.2":
- version "7.20.5"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95"
- integrity sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==
+"@babel/generator@^7.20.7", "@babel/generator@^7.7.2":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.7.tgz#f8ef57c8242665c5929fe2e8d82ba75460187b4a"
+ integrity sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==
dependencies:
- "@babel/types" "^7.20.5"
+ "@babel/types" "^7.20.7"
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
@@ -120,14 +120,15 @@
"@babel/helper-explode-assignable-expression" "^7.18.6"
"@babel/types" "^7.18.6"
-"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.0":
- version "7.20.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a"
- integrity sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==
+"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.0", "@babel/helper-compilation-targets@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb"
+ integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==
dependencies:
- "@babel/compat-data" "^7.20.0"
+ "@babel/compat-data" "^7.20.5"
"@babel/helper-validator-option" "^7.18.6"
browserslist "^4.21.3"
+ lru-cache "^5.1.1"
semver "^6.3.0"
"@babel/helper-create-class-features-plugin@^7.18.6":
@@ -204,19 +205,19 @@
dependencies:
"@babel/types" "^7.18.6"
-"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.20.2":
- version "7.20.2"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz#ac53da669501edd37e658602a21ba14c08748712"
- integrity sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==
+"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.20.7":
+ version "7.20.11"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz#df4c7af713c557938c50ea3ad0117a7944b2f1b0"
+ integrity sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==
dependencies:
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-module-imports" "^7.18.6"
"@babel/helper-simple-access" "^7.20.2"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/helper-validator-identifier" "^7.19.1"
- "@babel/template" "^7.18.10"
- "@babel/traverse" "^7.20.1"
- "@babel/types" "^7.20.2"
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.20.10"
+ "@babel/types" "^7.20.7"
"@babel/helper-optimise-call-expression@^7.18.6":
version "7.18.6"
@@ -297,14 +298,14 @@
"@babel/traverse" "^7.18.11"
"@babel/types" "^7.18.10"
-"@babel/helpers@^7.20.5":
- version "7.20.6"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.6.tgz#e64778046b70e04779dfbdf924e7ebb45992c763"
- integrity sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==
+"@babel/helpers@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.7.tgz#04502ff0feecc9f20ecfaad120a18f011a8e6dce"
+ integrity sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==
dependencies:
- "@babel/template" "^7.18.10"
- "@babel/traverse" "^7.20.5"
- "@babel/types" "^7.20.5"
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.20.7"
+ "@babel/types" "^7.20.7"
"@babel/highlight@^7.18.6", "@babel/highlight@^7.9.0":
version "7.18.6"
@@ -315,10 +316,10 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
-"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.20.5", "@babel/parser@^7.7.0":
- version "7.20.5"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8"
- integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==
+"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.7.0":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.7.tgz#66fe23b3c8569220817d5feb8b9dcdc95bb4f71b"
+ integrity sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6"
@@ -1016,35 +1017,35 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/template@^7.18.10", "@babel/template@^7.3.3":
- version "7.18.10"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71"
- integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==
+"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8"
+ integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==
dependencies:
"@babel/code-frame" "^7.18.6"
- "@babel/parser" "^7.18.10"
- "@babel/types" "^7.18.10"
+ "@babel/parser" "^7.20.7"
+ "@babel/types" "^7.20.7"
-"@babel/traverse@^7.18.11", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.20.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2":
- version "7.20.5"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.5.tgz#78eb244bea8270fdda1ef9af22a5d5e5b7e57133"
- integrity sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==
+"@babel/traverse@^7.18.11", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.10", "@babel/traverse@^7.20.7", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2":
+ version "7.20.10"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.10.tgz#2bf98239597fcec12f842756f186a9dde6d09230"
+ integrity sha512-oSf1juCgymrSez8NI4A2sr4+uB/mFd9MXplYGPEBnfAuWmmyeVcHa6xLPiaRBcXkcb/28bgxmQLTVwFKE1yfsg==
dependencies:
"@babel/code-frame" "^7.18.6"
- "@babel/generator" "^7.20.5"
+ "@babel/generator" "^7.20.7"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.19.0"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
- "@babel/parser" "^7.20.5"
- "@babel/types" "^7.20.5"
+ "@babel/parser" "^7.20.7"
+ "@babel/types" "^7.20.7"
debug "^4.1.0"
globals "^11.1.0"
-"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
- version "7.20.5"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84"
- integrity sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==
+"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f"
+ integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==
dependencies:
"@babel/helper-string-parser" "^7.19.4"
"@babel/helper-validator-identifier" "^7.19.1"
@@ -1073,10 +1074,10 @@
"@codemirror/language" "^6.0.0"
"@lezer/cpp" "^1.0.0"
-"@codemirror/lang-javascript@^6.1.1":
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/@codemirror/lang-javascript/-/lang-javascript-6.1.1.tgz#f920192db30531927a02b8a1af9cf3c3d895101c"
- integrity sha512-F4+kiuC5d5dUSJmff96tJQwpEXs/tX/4bapMRnZWW6bHKK1Fx6MunTzopkCUWRa9bF87GPmb9m7Qtg7Yv8f3uQ==
+"@codemirror/lang-javascript@^6.1.2":
+ version "6.1.2"
+ resolved "https://registry.yarnpkg.com/@codemirror/lang-javascript/-/lang-javascript-6.1.2.tgz#a11812ca1d21301cdeb80e51b4c007edcf55f813"
+ integrity sha512-OcwLfZXdQ1OHrLiIcKCn7MqZ7nx205CMKlhe+vL88pe2ymhT9+2P+QhwkYGxMICj8TDHyp8HFKVwpiisUT7iEQ==
dependencies:
"@codemirror/autocomplete" "^6.0.0"
"@codemirror/language" "^6.0.0"
@@ -1094,10 +1095,10 @@
"@codemirror/language" "^6.0.0"
"@lezer/rust" "^1.0.0"
-"@codemirror/language@^6.0.0", "@codemirror/language@^6.3.1":
- version "6.3.1"
- resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.3.1.tgz#1d61f33aa5de9aa74a713ee1f5ce600adc74df6b"
- integrity sha512-MK+G1QKaGfSEUg9YEFaBkMBI6j1ge4VMBPZv9fDYotw7w695c42x5Ba1mmwBkesYnzYFBfte6Hh9TDcKa6xORQ==
+"@codemirror/language@^6.0.0", "@codemirror/language@^6.3.2":
+ version "6.3.2"
+ resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.3.2.tgz#a3d5796d17a2cd3110bac0f5126db67c7e90a0f3"
+ integrity sha512-g42uHhOcEMAXjmozGG+rdom5UsbyfMxQFh7AbkeoaNImddL6Xt4cQDL0+JxmG7+as18rUAvZaqzP/TjsciVIrA==
dependencies:
"@codemirror/state" "^6.0.0"
"@codemirror/view" "^6.0.0"
@@ -1115,15 +1116,15 @@
"@codemirror/view" "^6.0.0"
crelt "^1.0.5"
-"@codemirror/state@^6.0.0", "@codemirror/state@^6.1.4":
- version "6.1.4"
- resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.1.4.tgz#2b654ae233ac4f41ee89ce095509ea35ecdf1031"
- integrity sha512-g+3OJuRylV5qsXuuhrc6Cvs1NQluNioepYMM2fhnpYkNk7NgX+j0AFuevKSVKzTDmDyt9+Puju+zPdHNECzCNQ==
+"@codemirror/state@^6.0.0", "@codemirror/state@^6.1.4", "@codemirror/state@^6.2.0":
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.2.0.tgz#a0fb08403ced8c2a68d1d0acee926bd20be922f2"
+ integrity sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==
-"@codemirror/view@^6.0.0", "@codemirror/view@^6.5.1":
- version "6.5.1"
- resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.5.1.tgz#f4533cd796f0569508822d0012bee9a15dc4b3f9"
- integrity sha512-xBKP8N3AXOs06VcKvIuvIQoUlGs7Hb78ftJWahLaRX909jKPMgGxR5XjvrawzTTZMSTU3DzdjDNPwG6fPM/ypQ==
+"@codemirror/view@^6.0.0", "@codemirror/view@^6.7.1":
+ version "6.7.1"
+ resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.7.1.tgz#370e95d6f001e7f5cadc459807974b4f0a6eb225"
+ integrity sha512-kYtS+uqYw/q/0ytYxpkqE1JVuK5NsbmBklWYhwLFTKO9gVuTdh/kDEeZPKorbqHcJ+P+ucrhcsS1czVweOpT2g==
dependencies:
"@codemirror/state" "^6.1.4"
style-mod "^4.0.0"
@@ -1139,15 +1140,15 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
-"@eslint/eslintrc@^1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.3.tgz#2b044ab39fdfa75b4688184f9e573ce3c5b0ff95"
- integrity sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==
+"@eslint/eslintrc@^1.4.0":
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e"
+ integrity sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
espree "^9.4.0"
- globals "^13.15.0"
+ globals "^13.19.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
js-yaml "^4.1.0"
@@ -1185,10 +1186,10 @@
resolved "https://registry.yarnpkg.com/@fluent/sequence/-/sequence-0.7.0.tgz#2fbfa872e87293cca209ba4a586d1a8fbaa2e5c4"
integrity sha512-aH1xMwX8DFvKOQSKUKpB3zMsnJ2rRKt7MajLNnx/r3V3DWDo2nzEfm21d7UyOgwEckPIjPmhxdW1MEmsGUxYIw==
-"@humanwhocodes/config-array@^0.11.6":
- version "0.11.7"
- resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f"
- integrity sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==
+"@humanwhocodes/config-array@^0.11.8":
+ version "0.11.8"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9"
+ integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==
dependencies:
"@humanwhocodes/object-schema" "^1.2.1"
debug "^4.1.1"
@@ -1794,14 +1795,14 @@
dependencies:
defer-to-connect "^2.0.1"
-"@testing-library/dom@^8.11.1", "@testing-library/dom@^8.19.0", "@testing-library/dom@^8.5.0":
- version "8.19.0"
- resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.19.0.tgz#bd3f83c217ebac16694329e413d9ad5fdcfd785f"
- integrity sha512-6YWYPPpxG3e/xOo6HIWwB/58HukkwIVTOaZ0VwdMVjhRUX/01E4FtQbck9GazOOj7MXHc5RBzMrU86iBJHbI+A==
+"@testing-library/dom@^8.11.1", "@testing-library/dom@^8.19.1", "@testing-library/dom@^8.5.0":
+ version "8.19.1"
+ resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.19.1.tgz#0e2dafd281dedb930bb235eac1045470b4129d0e"
+ integrity sha512-P6iIPyYQ+qH8CvGauAqanhVnjrnRe0IZFSYCeGkSRW9q3u8bdVn2NPI+lasFyVsEQn1J/IFmp5Aax41+dAP9wg==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.12.5"
- "@types/aria-query" "^4.2.0"
+ "@types/aria-query" "^5.0.1"
aria-query "^5.0.0"
chalk "^4.1.0"
dom-accessibility-api "^0.5.9"
@@ -1832,6 +1833,13 @@
"@testing-library/dom" "^8.5.0"
"@types/react-dom" "^18.0.0"
+"@tgwf/co2@^0.11.4":
+ version "0.11.4"
+ resolved "https://registry.yarnpkg.com/@tgwf/co2/-/co2-0.11.4.tgz#1e919364a3b1ce5ed835fb6449f78a7c7277e3f4"
+ integrity sha512-I6e+WzdDpVbKLkTDJXAA0kJvOJQg1TNfTOu7PA5+FfxLEix9EqdigVK0Qd4flC82fLgTKSw4upuh+nCm4fFCaQ==
+ dependencies:
+ debug "^4.3.4"
+
"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
@@ -1854,10 +1862,10 @@
dependencies:
"@types/estree" "*"
-"@types/aria-query@^4.2.0":
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0"
- integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==
+"@types/aria-query@^5.0.1":
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.1.tgz#3286741fb8f1e1580ac28784add4c7a1d49bdfbc"
+ integrity sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==
"@types/babel__core@^7.1.14":
version "7.1.14"
@@ -2473,20 +2481,20 @@
"@webassemblyjs/ast" "1.11.1"
"@xtuc/long" "4.2.2"
-"@webpack-cli/configtest@^2.0.0":
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.0.0.tgz#5e1bc37064c7d00e1330641fa523f8ff85a39513"
- integrity sha512-war4OU8NGjBqU3DP3bx6ciODXIh7dSXcpQq+P4K2Tqyd8L5OjZ7COx9QXx/QdCIwL2qoX09Wr4Cwf7uS4qdEng==
+"@webpack-cli/configtest@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.0.1.tgz#a69720f6c9bad6aef54a8fa6ba9c3533e7ef4c7f"
+ integrity sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==
-"@webpack-cli/info@^2.0.0":
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.0.tgz#5a58476b129ee9b462117b23393596e726bf3b80"
- integrity sha512-NNxDgbo4VOkNhOlTgY0Elhz3vKpOJq4/PKeKg7r8cmYM+GQA9vDofLYyup8jS6EpUvhNmR30cHTCEIyvXpskwA==
+"@webpack-cli/info@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.1.tgz#eed745799c910d20081e06e5177c2b2569f166c0"
+ integrity sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==
-"@webpack-cli/serve@^2.0.0":
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.0.tgz#f08ea194e01ed45379383a8886e8c85a65a5f26a"
- integrity sha512-Rumq5mHvGXamnOh3O8yLk1sjx8dB30qF1OeR6VC00DIR6SLJ4bwwUGKC4pE7qBFoQyyh0H9sAg3fikYgAqVR0w==
+"@webpack-cli/serve@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.1.tgz#34bdc31727a1889198855913db2f270ace6d7bf8"
+ integrity sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
@@ -2498,13 +2506,13 @@
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
-"@yarnpkg/parsers@^3.0.0-rc.6":
- version "3.0.0-rc.9"
- resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-3.0.0-rc.9.tgz#2d284e4e0c79b1c4e410465e217fa303d98b7be3"
- integrity sha512-JMBE+6OJoNN9AXBzZ72u22/t9M25K8KgUWZIjvk8CU/NsE/m946l8D7SqMhbi3ZaUfYa5QitqCqVWTTFtysGJQ==
+"@yarnpkg/parsers@^3.0.0-rc.32":
+ version "3.0.0-rc.34"
+ resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-3.0.0-rc.34.tgz#db1d16e082e167db6dbc67f1c264639e0b4c5e1a"
+ integrity sha512-NhEA0BusInyk7EiJ7i7qF1Mkrb6gGjZcQQ/W1xxGazxapubEmGO7v5WSll6hWxFXE2ngtLj8lflq1Ff5VtqEww==
dependencies:
js-yaml "^3.10.0"
- tslib "^1.13.0"
+ tslib "^2.4.0"
JSONStream@^1.3.5:
version "1.3.5"
@@ -3317,10 +3325,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001431:
- version "1.0.30001431"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz#e7c59bd1bc518fae03a4656be442ce6c4887a795"
- integrity sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001439:
+ version "1.0.30001439"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz#ab7371faeb4adff4b74dad1718a6fd122e45d9cb"
+ integrity sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A==
ccount@^2.0.0:
version "2.0.1"
@@ -3809,10 +3817,10 @@ core-js-compat@^3.25.1:
dependencies:
browserslist "^4.21.4"
-core-js@^3.0.0, core-js@^3.26.1:
- version "3.26.1"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.26.1.tgz#7a9816dabd9ee846c1c0fe0e8fcad68f3709134e"
- integrity sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==
+core-js@^3.0.0, core-js@^3.27.1:
+ version "3.27.1"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.27.1.tgz#23cc909b315a6bb4e418bf40a52758af2103ba46"
+ integrity sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==
core-util-is@~1.0.0:
version "1.0.3"
@@ -3899,13 +3907,13 @@ css-functions-list@^3.1.0:
resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.1.0.tgz#cf5b09f835ad91a00e5959bcfc627cd498e1321b"
integrity sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==
-css-loader@^6.7.2:
- version "6.7.2"
- resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.2.tgz#26bc22401b5921686a10fbeba75d124228302304"
- integrity sha512-oqGbbVcBJkm8QwmnNzrFrWTnudnRZC+1eXikLJl0n4ljcfotgRifpg2a1lKy8jTrc4/d9A/ap1GFq1jDKG7J+Q==
+css-loader@^6.7.3:
+ version "6.7.3"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.3.tgz#1e8799f3ccc5874fdd55461af51137fcc5befbcd"
+ integrity sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==
dependencies:
icss-utils "^5.1.0"
- postcss "^8.4.18"
+ postcss "^8.4.19"
postcss-modules-extract-imports "^3.0.0"
postcss-modules-local-by-default "^4.0.0"
postcss-modules-scope "^3.0.0"
@@ -4128,10 +4136,10 @@ decode-named-character-reference@^1.0.0:
dependencies:
character-entities "^2.0.0"
-decode-uri-component@^0.2.2:
- version "0.2.2"
- resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
- integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
+decode-uri-component@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz#2ac4859663c704be22bf7db760a1494a49ab2cc5"
+ integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==
decompress-response@^6.0.0:
version "6.0.0"
@@ -4677,10 +4685,10 @@ eslint-plugin-jest-formatting@^3.1.0:
resolved "https://registry.yarnpkg.com/eslint-plugin-jest-formatting/-/eslint-plugin-jest-formatting-3.1.0.tgz#b26dd5a40f432b642dcc880021a771bb1c93dcd2"
integrity sha512-XyysraZ1JSgGbLSDxjj5HzKKh0glgWf+7CkqxbTqb7zEhW7X2WHo5SBQ8cGhnszKN+2Lj3/oevBlHNbHezoc/A==
-eslint-plugin-jest@^27.1.5:
- version "27.1.5"
- resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.1.5.tgz#16fad619cfee6cdf73d56098fbed0761e1f653ce"
- integrity sha512-CK2dekZ5VBdzsOSOH5Fc1rwC+cWXjkcyrmf1RV714nDUDKu+o73TTJiDxpbILG8PtPPpAAl3ywzh5QA7Ft0mjA==
+eslint-plugin-jest@^27.1.7:
+ version "27.1.7"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.1.7.tgz#0351e904afb8d66b7f70452929556dfdc8daba0d"
+ integrity sha512-0QVzf+og4YI1Qr3UoprkqqhezAZjFffdi62b0IurkCXMqPtRW84/UT4CKsYT80h/D82LA9avjO/80Ou1LdgbaQ==
dependencies:
"@typescript-eslint/utils" "^5.10.0"
@@ -4812,13 +4820,13 @@ eslint@^6.0.1:
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
-eslint@^8.27.0:
- version "8.27.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.27.0.tgz#d547e2f7239994ad1faa4bb5d84e5d809db7cf64"
- integrity sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ==
+eslint@^8.30.0:
+ version "8.30.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.30.0.tgz#83a506125d089eef7c5b5910eeea824273a33f50"
+ integrity sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==
dependencies:
- "@eslint/eslintrc" "^1.3.3"
- "@humanwhocodes/config-array" "^0.11.6"
+ "@eslint/eslintrc" "^1.4.0"
+ "@humanwhocodes/config-array" "^0.11.8"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
ajv "^6.10.0"
@@ -4837,7 +4845,7 @@ eslint@^8.27.0:
file-entry-cache "^6.0.1"
find-up "^5.0.0"
glob-parent "^6.0.2"
- globals "^13.15.0"
+ globals "^13.19.0"
grapheme-splitter "^1.0.4"
ignore "^5.2.0"
import-fresh "^3.0.0"
@@ -5044,10 +5052,10 @@ external-editor@^3.0.3:
iconv-lite "^0.4.24"
tmp "^0.0.33"
-fake-indexeddb@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-4.0.0.tgz#1dfb2023a3be175e35a6d84975218b432041934d"
- integrity sha512-oCfWSJ/qvQn1XPZ8SHX6kY3zr1t+bN7faZ/lltGY0SBGhFOPXnWf0+pbO/MOAgfMx6khC2gK3S/bvAgQpuQHDQ==
+fake-indexeddb@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-4.0.1.tgz#09bb2468e21d0832b2177e894765fb109edac8fb"
+ integrity sha512-hFRyPmvEZILYgdcLBxVdHLik4Tj3gDTu/g7s9ZDOiU3sTNiGx+vEu1ri/AMsFJUZ/1sdRbAVrEcKndh3sViBcA==
dependencies:
realistic-structured-clone "^3.0.0"
@@ -5188,10 +5196,10 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
-filter-obj@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b"
- integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs=
+filter-obj@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed"
+ integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==
finalhandler@1.2.0:
version "1.2.0"
@@ -5671,10 +5679,10 @@ globals@^12.1.0:
dependencies:
type-fest "^0.8.1"
-globals@^13.15.0:
- version "13.15.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac"
- integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==
+globals@^13.19.0:
+ version "13.19.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-13.19.0.tgz#7a42de8e6ad4f7242fbcca27ea5b23aca367b5c8"
+ integrity sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==
dependencies:
type-fest "^0.20.2"
@@ -6217,10 +6225,10 @@ ignore@^4.0.6:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
-ignore@^5.0.0, ignore@^5.2.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
- integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
+ignore@^5.0.0, ignore@^5.2.0, ignore@^5.2.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.1.tgz#c2b1f76cb999ede1502f3a226a9310fdfe88d46c"
+ integrity sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==
immediate@~3.0.5:
version "3.0.6"
@@ -7644,22 +7652,22 @@ locate-path@^7.1.0:
dependencies:
p-locate "^6.0.0"
-lockfile-lint-api@^5.4.6:
- version "5.4.6"
- resolved "https://registry.yarnpkg.com/lockfile-lint-api/-/lockfile-lint-api-5.4.6.tgz#9ab5252df1e1383061dbaa58f06c94bdb19710c6"
- integrity sha512-3Sp01oBI0LWZNvKfGQ04G03+dXkjAvEvRiAQNZsvdVmCGP6vOXPCuPEimStf+RSo8PnjApdb65RAUh6aSZXsVQ==
+lockfile-lint-api@^5.5.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/lockfile-lint-api/-/lockfile-lint-api-5.5.0.tgz#9eab162f597121d8fdae13debe553abe1279c7c6"
+ integrity sha512-L/Jk109tOSmTCPHjUEQSH/bYDRt7rgT1E2NN8/p3tmAwJuKwuWplJliVzZ+bBiIRWKmtXBDcfHnXJ9yT1shevQ==
dependencies:
- "@yarnpkg/parsers" "^3.0.0-rc.6"
+ "@yarnpkg/parsers" "^3.0.0-rc.32"
object-hash "^3.0.0"
-lockfile-lint@^4.9.6:
- version "4.9.6"
- resolved "https://registry.yarnpkg.com/lockfile-lint/-/lockfile-lint-4.9.6.tgz#e0204b7eb9767c96fe59672c3fbb53fe9a12ca49"
- integrity sha512-qPI3QcEflvGylCtXyxTh0anhi1oYxD12L3ZwQYaxLjxpWUI73+EbFMsl9esqcbgSKAh4wes5y6fbG8hbQTgjyg==
+lockfile-lint@^4.10.0:
+ version "4.10.0"
+ resolved "https://registry.yarnpkg.com/lockfile-lint/-/lockfile-lint-4.10.0.tgz#388224241426dbfa6e632e96a539e4dfca088a6d"
+ integrity sha512-OM11m0txImBLFzC8DVoNx+Oca11K8jXndwQV9WLwXgMeLDJtlXGbLwRBFINjyo1YQr/STrdsx3OBth3cee+41A==
dependencies:
cosmiconfig "^7.0.1"
debug "^4.1.1"
- lockfile-lint-api "^5.4.6"
+ lockfile-lint-api "^5.5.0"
yargs "^16.0.0"
lodash.assignwith@^4.2.0:
@@ -7746,6 +7754,13 @@ lowercase-keys@^3.0.0:
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2"
integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==
+lru-cache@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
+ integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
+ dependencies:
+ yallist "^3.0.2"
+
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
@@ -9565,14 +9580,14 @@ postcss-discard-overridden@^5.1.0:
resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e"
integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==
-postcss-loader@^7.0.1:
- version "7.0.1"
- resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.0.1.tgz#4c883cc0a1b2bfe2074377b7a74c1cd805684395"
- integrity sha512-VRviFEyYlLjctSM93gAZtcJJ/iSkPZ79zWbN/1fSH+NisBByEiVLqpdVDrPLVSi8DX0oJo12kL/GppTBdKVXiQ==
+postcss-loader@^7.0.2:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.0.2.tgz#b53ff44a26fba3688eee92a048c7f2d4802e23bb"
+ integrity sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg==
dependencies:
cosmiconfig "^7.0.0"
klona "^2.0.5"
- semver "^7.3.7"
+ semver "^7.3.8"
postcss-media-query-parser@^0.2.3:
version "0.2.3"
@@ -9753,10 +9768,10 @@ postcss-safe-parser@^6.0.0:
resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz#bb4c29894171a94bc5c996b9a30317ef402adaa1"
integrity sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==
-postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9:
- version "6.0.10"
- resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
- integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
+postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9:
+ version "6.0.11"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc"
+ integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
@@ -9786,10 +9801,10 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
-postcss@^8.3.11, postcss@^8.4.16, postcss@^8.4.18, postcss@^8.4.19:
- version "8.4.19"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.19.tgz#61178e2add236b17351897c8bcc0b4c8ecab56fc"
- integrity sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==
+postcss@^8.3.11, postcss@^8.4.19, postcss@^8.4.20:
+ version "8.4.20"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.20.tgz#64c52f509644cecad8567e949f4081d98349dc56"
+ integrity sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==
dependencies:
nanoid "^3.3.4"
picocolors "^1.0.0"
@@ -9817,10 +9832,10 @@ prettier@^1.19.1:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
-prettier@^2.0.5, prettier@^2.8.0:
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.0.tgz#c7df58393c9ba77d6fba3921ae01faf994fb9dc9"
- integrity sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==
+prettier@^2.0.5, prettier@^2.8.1:
+ version "2.8.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.1.tgz#4e1fd11c34e2421bc1da9aea9bd8127cd0a35efc"
+ integrity sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
version "5.6.0"
@@ -9987,15 +10002,14 @@ qs@6.10.3, qs@^6.5.2:
dependencies:
side-channel "^1.0.4"
-query-string@^7.1.3:
- version "7.1.3"
- resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328"
- integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==
+query-string@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/query-string/-/query-string-8.1.0.tgz#e7f95367737219544cd360a11a4f4ca03836e115"
+ integrity sha512-BFQeWxJOZxZGix7y+SByG3F36dA0AbTy9o6pSmKFcFz7DAj0re9Frkty3saBn3nHo3D0oZJ/+rx3r8H8r8Jbpw==
dependencies:
- decode-uri-component "^0.2.2"
- filter-obj "^1.1.0"
- split-on-first "^1.0.0"
- strict-uri-encode "^2.0.0"
+ decode-uri-component "^0.4.1"
+ filter-obj "^5.1.0"
+ split-on-first "^3.0.0"
querystring@0.2.0:
version "0.2.0"
@@ -11188,10 +11202,10 @@ spdy@^4.0.2:
select-hose "^2.0.0"
spdy-transport "^3.0.0"
-split-on-first@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"
- integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==
+split-on-first@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7"
+ integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==
split-transform-stream@0.1.1:
version "0.1.1"
@@ -11281,11 +11295,6 @@ streaming-json-stringify@3:
json-stringify-safe "5"
readable-stream "2"
-strict-uri-encode@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
- integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
-
string-length@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1"
@@ -11541,10 +11550,10 @@ stylelint-prettier@^2.0.0:
dependencies:
prettier-linter-helpers "^1.0.0"
-stylelint@^14.15.0:
- version "14.15.0"
- resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-14.15.0.tgz#4df55078e734869f81f6b85bbec2d56a4b478ece"
- integrity sha512-JOgDAo5QRsqiOZPZO+B9rKJvBm64S0xasbuRPAbPs6/vQDgDCnZLIiw6XcAS6GQKk9k1sBWR6rmH3Mfj8OknKg==
+stylelint@^14.16.1:
+ version "14.16.1"
+ resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-14.16.1.tgz#b911063530619a1bbe44c2b875fd8181ebdc742d"
+ integrity sha512-ErlzR/T3hhbV+a925/gbfc3f3Fep9/bnspMiJPorfGEmcBbXdS+oo6LrVtoUZ/w9fqD6o6k7PtUlCOsCRdjX/A==
dependencies:
"@csstools/selector-specificity" "^2.0.2"
balanced-match "^2.0.0"
@@ -11559,7 +11568,7 @@ stylelint@^14.15.0:
globby "^11.1.0"
globjoin "^0.1.4"
html-tags "^3.2.0"
- ignore "^5.2.0"
+ ignore "^5.2.1"
import-lazy "^4.0.0"
imurmurhash "^0.1.4"
is-plain-object "^5.0.0"
@@ -11573,7 +11582,7 @@ stylelint@^14.15.0:
postcss-media-query-parser "^0.2.3"
postcss-resolve-nested-selector "^0.1.1"
postcss-safe-parser "^6.0.0"
- postcss-selector-parser "^6.0.10"
+ postcss-selector-parser "^6.0.11"
postcss-value-parser "^4.2.0"
resolve-from "^5.0.0"
string-width "^4.2.3"
@@ -11935,15 +11944,15 @@ tsconfig-paths@^3.14.1:
minimist "^1.2.6"
strip-bom "^3.0.0"
-tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0:
+tslib@^1.8.1, tslib@^1.9.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2.0.3:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
- integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
+tslib@^2.0.3, tslib@^2.4.0:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
+ integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
tsscmp@1.0.6:
version "1.0.6"
@@ -12582,15 +12591,15 @@ webidl-conversions@^7.0.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
-webpack-cli@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.0.0.tgz#bd380a9653e0cd1a08916c4ff1adea17201ef68f"
- integrity sha512-AACDTo20yG+xn6HPW5xjbn2Be4KUzQPebWXsDMHwPPyKh9OnTOJgZN2Nc+g/FZKV3ObRTYsGvibAvc+5jAUrVA==
+webpack-cli@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.0.1.tgz#95fc0495ac4065e9423a722dec9175560b6f2d9a"
+ integrity sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==
dependencies:
"@discoveryjs/json-ext" "^0.5.0"
- "@webpack-cli/configtest" "^2.0.0"
- "@webpack-cli/info" "^2.0.0"
- "@webpack-cli/serve" "^2.0.0"
+ "@webpack-cli/configtest" "^2.0.1"
+ "@webpack-cli/info" "^2.0.1"
+ "@webpack-cli/serve" "^2.0.1"
colorette "^2.0.14"
commander "^9.4.1"
cross-spawn "^7.0.3"
@@ -13086,6 +13095,11 @@ y18n@^5.0.5:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18"
integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==
+yallist@^3.0.2:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
+ integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"